blob: 17c75b4d0ecc00162781849e259edaa473c15f73 [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):
235 Coloring.__init__(self, 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):
249 Coloring.__init__(self, 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
441class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600442 # These objects can be shared between several working trees.
443 shareable_files = ['description', 'info']
444 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
445 # These objects can only be used by a single working tree.
446 working_tree_files = ['config', 'packed-refs', 'shallow']
447 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700448
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700449 def __init__(self,
450 manifest,
451 name,
452 remote,
453 gitdir,
David James8d201162013-10-11 17:03:19 -0700454 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700455 worktree,
456 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700457 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800458 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100459 rebase=True,
460 groups=None,
461 sync_c=False,
462 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900463 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100464 clone_depth=None,
465 upstream=None,
466 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500467 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100468 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900469 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700470 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600471 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700472 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800473 """Init a Project object.
474
475 Args:
476 manifest: The XmlManifest object.
477 name: The `name` attribute of manifest.xml's project element.
478 remote: RemoteSpec object specifying its remote's properties.
479 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700480 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800481 worktree: Absolute path of git working tree.
482 relpath: Relative path of git working tree to repo's top directory.
483 revisionExpr: The `revision` attribute of manifest.xml's project element.
484 revisionId: git commit id for checking out.
485 rebase: The `rebase` attribute of manifest.xml's project element.
486 groups: The `groups` attribute of manifest.xml's project element.
487 sync_c: The `sync-c` attribute of manifest.xml's project element.
488 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900489 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800490 upstream: The `upstream` attribute of manifest.xml's project element.
491 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500492 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800493 is_derived: False if the project was explicitly defined in the manifest;
494 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400495 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900496 optimized_fetch: If True, when a project is set to a sha1 revision, only
497 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600498 retry_fetches: Retry remote fetches n times upon receiving transient error
499 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700500 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800501 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400502 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700503 self.name = name
504 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800505 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700506 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800507 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700508 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800509 else:
510 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700511 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700512 self.revisionExpr = revisionExpr
513
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700514 if revisionId is None \
515 and revisionExpr \
516 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700517 self.revisionId = revisionExpr
518 else:
519 self.revisionId = revisionId
520
Mike Pontillod3153822012-02-28 11:53:24 -0800521 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700522 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700523 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800524 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900525 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900526 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700527 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800528 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500529 # NB: Do not use this setting in __init__ to change behavior so that the
530 # manifest.git checkout can inspect & change it after instantiating. See
531 # the XmlManifest init code for more info.
532 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800533 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900534 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600535 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800536 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800537
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700538 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500540 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500541 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700542 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400543 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700544
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800545 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700546 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800547 else:
548 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700549 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700550 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700551 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400552 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700553 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554
Doug Anderson37282b42011-03-04 11:54:18 -0800555 # This will be filled in if a project is later identified to be the
556 # project containing repo hooks.
557 self.enabled_repo_hooks = []
558
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700559 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800560 def Derived(self):
561 return self.is_derived
562
563 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700565 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700566
567 @property
568 def CurrentBranch(self):
569 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400570
571 The branch name omits the 'refs/heads/' prefix.
572 None is returned if the project is on a detached HEAD, or if the work_git is
573 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700574 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400575 try:
576 b = self.work_git.GetHead()
577 except NoManifestException:
578 # If the local checkout is in a bad state, don't barf. Let the callers
579 # process this like the head is unreadable.
580 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581 if b.startswith(R_HEADS):
582 return b[len(R_HEADS):]
583 return None
584
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700585 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500586 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
587 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
588 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200589
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700590 def IsDirty(self, consider_untracked=True):
591 """Is the working directory modified in some way?
592 """
593 self.work_git.update_index('-q',
594 '--unmerged',
595 '--ignore-missing',
596 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900597 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700598 return True
599 if self.work_git.DiffZ('diff-files'):
600 return True
601 if consider_untracked and self.work_git.LsOthers():
602 return True
603 return False
604
605 _userident_name = None
606 _userident_email = None
607
608 @property
609 def UserName(self):
610 """Obtain the user's personal name.
611 """
612 if self._userident_name is None:
613 self._LoadUserIdentity()
614 return self._userident_name
615
616 @property
617 def UserEmail(self):
618 """Obtain the user's email address. This is very likely
619 to be their Gerrit login.
620 """
621 if self._userident_email is None:
622 self._LoadUserIdentity()
623 return self._userident_email
624
625 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900626 u = self.bare_git.var('GIT_COMMITTER_IDENT')
627 m = re.compile("^(.*) <([^>]*)> ").match(u)
628 if m:
629 self._userident_name = m.group(1)
630 self._userident_email = m.group(2)
631 else:
632 self._userident_name = ''
633 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634
635 def GetRemote(self, name):
636 """Get the configuration for a single remote.
637 """
638 return self.config.GetRemote(name)
639
640 def GetBranch(self, name):
641 """Get the configuration for a single branch.
642 """
643 return self.config.GetBranch(name)
644
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700645 def GetBranches(self):
646 """Get all existing local branches.
647 """
648 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900649 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700650 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700651
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530652 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700653 if name.startswith(R_HEADS):
654 name = name[len(R_HEADS):]
655 b = self.GetBranch(name)
656 b.current = name == current
657 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900658 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700659 heads[name] = b
660
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530661 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662 if name.startswith(R_PUB):
663 name = name[len(R_PUB):]
664 b = heads.get(name)
665 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900666 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700667
668 return heads
669
Colin Cross5acde752012-03-28 20:15:45 -0700670 def MatchesGroups(self, manifest_groups):
671 """Returns true if the manifest groups specified at init should cause
672 this project to be synced.
673 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700674 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700675
676 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700677 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700678 manifest_groups: "-group1,group2"
679 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500680
681 The special manifest group "default" will match any project that
682 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700683 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500684 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700685 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700686 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500687 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700688
Conley Owens971de8e2012-04-16 10:36:08 -0700689 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700690 for group in expanded_manifest_groups:
691 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700692 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700693 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700694 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700695
Conley Owens971de8e2012-04-16 10:36:08 -0700696 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700698# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700699 def UncommitedFiles(self, get_all=True):
700 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700702 Args:
703 get_all: a boolean, if True - get information about all different
704 uncommitted files. If False - return as soon as any kind of
705 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500706 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700707 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500708 self.work_git.update_index('-q',
709 '--unmerged',
710 '--ignore-missing',
711 '--refresh')
712 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700713 details.append("rebase in progress")
714 if not get_all:
715 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500716
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700717 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
718 if changes:
719 details.extend(changes)
720 if not get_all:
721 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500722
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700723 changes = self.work_git.DiffZ('diff-files').keys()
724 if changes:
725 details.extend(changes)
726 if not get_all:
727 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500728
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700729 changes = self.work_git.LsOthers()
730 if changes:
731 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500732
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700733 return details
734
735 def HasChanges(self):
736 """Returns true if there are uncommitted changes.
737 """
738 if self.UncommitedFiles(get_all=False):
739 return True
740 else:
741 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500742
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600743 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700744 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200745
746 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200747 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600748 quiet: If True then only print the project name. Do not print
749 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700751 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700752 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200753 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700754 print(file=output_redir)
755 print('project %s/' % self.relpath, file=output_redir)
756 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700757 return
758
759 self.work_git.update_index('-q',
760 '--unmerged',
761 '--ignore-missing',
762 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700763 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
765 df = self.work_git.DiffZ('diff-files')
766 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100767 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700768 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769
770 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700771 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200772 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700773 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600775 if quiet:
776 out.nl()
777 return 'DIRTY'
778
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700779 branch = self.CurrentBranch
780 if branch is None:
781 out.nobranch('(*** NO BRANCH ***)')
782 else:
783 out.branch('branch %s', branch)
784 out.nl()
785
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700786 if rb:
787 out.important('prior sync failed; rebase still in progress')
788 out.nl()
789
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790 paths = list()
791 paths.extend(di.keys())
792 paths.extend(df.keys())
793 paths.extend(do)
794
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530795 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900796 try:
797 i = di[p]
798 except KeyError:
799 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700800
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900801 try:
802 f = df[p]
803 except KeyError:
804 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200805
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900806 if i:
807 i_status = i.status.upper()
808 else:
809 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900811 if f:
812 f_status = f.status.lower()
813 else:
814 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815
816 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800817 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700818 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819 else:
820 line = ' %s%s\t%s' % (i_status, f_status, p)
821
822 if i and not f:
823 out.added('%s', line)
824 elif (i and f) or (not i and f):
825 out.changed('%s', line)
826 elif not i and not f:
827 out.untracked('%s', line)
828 else:
829 out.write('%s', line)
830 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200831
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700832 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833
pelyad67872d2012-03-28 14:49:58 +0300834 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 """Prints the status of the repository to stdout.
836 """
837 out = DiffColoring(self.config)
838 cmd = ['diff']
839 if out.is_on:
840 cmd.append('--color')
841 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300842 if absolute_paths:
843 cmd.append('--src-prefix=a/%s/' % self.relpath)
844 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400846 try:
847 p = GitCommand(self,
848 cmd,
849 capture_stdout=True,
850 capture_stderr=True)
851 except GitError as e:
852 out.nl()
853 out.project('project %s/' % self.relpath)
854 out.nl()
855 out.fail('%s', str(e))
856 out.nl()
857 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 has_diff = False
859 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -0400860 if not hasattr(line, 'encode'):
861 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 if not has_diff:
863 out.nl()
864 out.project('project %s/' % self.relpath)
865 out.nl()
866 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700867 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400868 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700870# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900871 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700872 """Was the branch published (uploaded) for code review?
873 If so, returns the SHA-1 hash of the last published
874 state for the branch.
875 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700876 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900877 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700878 try:
879 return self.bare_git.rev_parse(key)
880 except GitError:
881 return None
882 else:
883 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900884 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700885 except KeyError:
886 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700887
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 """Prunes any stale published refs.
890 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900891 if all_refs is None:
892 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700893 heads = set()
894 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530895 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 if name.startswith(R_HEADS):
897 heads.add(name)
898 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900899 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530901 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 n = name[len(R_PUB):]
903 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900904 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700906 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 """List any branches which can be uploaded for review.
908 """
909 heads = {}
910 pubed = {}
911
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530912 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900914 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900916 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917
918 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530919 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900920 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700922 if selected_branch and branch != selected_branch:
923 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800925 rb = self.GetUploadableBranch(branch)
926 if rb:
927 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 return ready
929
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800930 def GetUploadableBranch(self, branch_name):
931 """Get a single uploadable branch, or None.
932 """
933 branch = self.GetBranch(branch_name)
934 base = branch.LocalMerge
935 if branch.LocalMerge:
936 rb = ReviewableBranch(self, branch, base)
937 if rb.commits:
938 return rb
939 return None
940
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700941 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100942 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500943 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700944 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500945 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500946 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200947 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700948 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200949 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200950 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800951 validate_certs=True,
952 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 """Uploads the named branch for code review.
954 """
955 if branch is None:
956 branch = self.CurrentBranch
957 if branch is None:
958 raise GitError('not currently on a branch')
959
960 branch = self.GetBranch(branch)
961 if not branch.LocalMerge:
962 raise GitError('branch %s does not track a remote' % branch.name)
963 if not branch.remote.review:
964 raise GitError('remote %s has no review url' % branch.remote.name)
965
Bryan Jacobsf609f912013-05-06 13:36:24 -0400966 if dest_branch is None:
967 dest_branch = self.dest_branch
968 if dest_branch is None:
969 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 if not dest_branch.startswith(R_HEADS):
971 dest_branch = R_HEADS + dest_branch
972
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800973 if not branch.remote.projectname:
974 branch.remote.projectname = self.name
975 branch.remote.Save()
976
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200977 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800978 if url is None:
979 raise UploadError('review not configured')
980 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500981 if dryrun:
982 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800983
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800984 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800985 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700986
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800987 for push_option in (push_options or []):
988 cmd.append('-o')
989 cmd.append(push_option)
990
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800991 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800992
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800993 if dest_branch.startswith(R_HEADS):
994 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700995
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -0500996 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -0800997 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800998 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -0800999 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001000 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001001 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001002
David Pursehousef25a3702018-11-14 19:01:22 -08001003 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001004 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001005 if notify:
1006 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001007 if private:
1008 opts += ['private']
1009 if wip:
1010 opts += ['wip']
1011 if opts:
1012 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001013 cmd.append(ref_spec)
1014
Anthony King7bdac712014-07-16 12:56:40 +01001015 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001016 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001017
Mike Frysingerd7f86832020-11-19 19:18:46 -05001018 if not dryrun:
1019 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1020 self.bare_git.UpdateRef(R_PUB + branch.name,
1021 R_HEADS + branch.name,
1022 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001024# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001025 def _ExtractArchive(self, tarpath, path=None):
1026 """Extract the given tar on its current location
1027
1028 Args:
1029 - tarpath: The path to the actual tar file
1030
1031 """
1032 try:
1033 with tarfile.open(tarpath, 'r') as tar:
1034 tar.extractall(path=path)
1035 return True
1036 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001037 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001038 return False
1039
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001040 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001041 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001042 verbose=False,
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
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001084 if is_new is None:
1085 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001086 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001087 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001088 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001089 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001091
1092 if is_new:
1093 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1094 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001095 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001096 # This works for both absolute and relative alternate directories.
1097 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001098 except IOError:
1099 alt_dir = None
1100 else:
1101 alt_dir = None
1102
Mike Frysingere50b6a72020-02-19 01:45:48 -05001103 if (clone_bundle
1104 and alt_dir is None
1105 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001106 is_new = False
1107
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001108 if not current_branch_only:
1109 if self.sync_c:
1110 current_branch_only = True
1111 elif not self.manifest._loaded:
1112 # Manifest cannot check defaults until it syncs.
1113 current_branch_only = False
1114 elif self.manifest.default.sync_c:
1115 current_branch_only = True
1116
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001117 if not self.sync_tags:
1118 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001119
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001120 if self.clone_depth:
1121 depth = self.clone_depth
1122 else:
1123 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1124
Mike Frysinger521d01b2020-02-17 01:51:49 -05001125 # See if we can skip the network fetch entirely.
1126 if not (optimized_fetch and
1127 (ID_RE.match(self.revisionExpr) and
1128 self._CheckForImmutableRevision())):
1129 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001130 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1131 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001132 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001133 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001134 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001135 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001136
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001137 mp = self.manifest.manifestProject
1138 dissociate = mp.config.GetBoolean('repo.dissociate')
1139 if dissociate:
1140 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1141 if os.path.exists(alternates_file):
1142 cmd = ['repack', '-a', '-d']
1143 if GitCommand(self, cmd, bare=True).Wait() != 0:
1144 return False
1145 platform_utils.remove(alternates_file)
1146
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001147 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001148 self._InitMRef()
1149 else:
1150 self._InitMirrorHead()
1151 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001152 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001153 except OSError:
1154 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001156
1157 def PostRepoUpgrade(self):
1158 self._InitHooks()
1159
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001160 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001161 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001162 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001163 for copyfile in self.copyfiles:
1164 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001165 for linkfile in self.linkfiles:
1166 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
Julien Camperguedd654222014-01-09 16:21:37 +01001168 def GetCommitRevisionId(self):
1169 """Get revisionId of a commit.
1170
1171 Use this method instead of GetRevisionId to get the id of the commit rather
1172 than the id of the current git object (for example, a tag)
1173
1174 """
1175 if not self.revisionExpr.startswith(R_TAGS):
1176 return self.GetRevisionId(self._allrefs)
1177
1178 try:
1179 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1180 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001181 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1182 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001183
David Pursehouse8a68ff92012-09-24 12:15:13 +09001184 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001185 if self.revisionId:
1186 return self.revisionId
1187
1188 rem = self.GetRemote(self.remote.name)
1189 rev = rem.ToLocal(self.revisionExpr)
1190
David Pursehouse8a68ff92012-09-24 12:15:13 +09001191 if all_refs is not None and rev in all_refs:
1192 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001193
1194 try:
1195 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1196 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001197 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1198 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001199
Raman Tenneti6a872c92021-01-14 19:17:50 -08001200 def SetRevisionId(self, revisionId):
1201 self.revisionId = revisionId
1202
Martin Kellye4e94d22017-03-21 16:05:12 -07001203 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204 """Perform only the local IO portion of the sync process.
1205 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001206 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001207 if not os.path.exists(self.gitdir):
1208 syncbuf.fail(self,
1209 'Cannot checkout %s due to missing network sync; Run '
1210 '`repo sync -n %s` first.' %
1211 (self.name, self.name))
1212 return
1213
Martin Kellye4e94d22017-03-21 16:05:12 -07001214 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001215 all_refs = self.bare_ref.all
1216 self.CleanPublishedCache(all_refs)
1217 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001218
David Pursehouse1d947b32012-10-25 12:23:11 +09001219 def _doff():
1220 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001221 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001222
Martin Kellye4e94d22017-03-21 16:05:12 -07001223 def _dosubmodules():
1224 self._SyncSubmodules(quiet=True)
1225
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001226 head = self.work_git.GetHead()
1227 if head.startswith(R_HEADS):
1228 branch = head[len(R_HEADS):]
1229 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001230 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001231 except KeyError:
1232 head = None
1233 else:
1234 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001235
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001236 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237 # Currently on a detached HEAD. The user is assumed to
1238 # not have any local modifications worth worrying about.
1239 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001240 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001241 syncbuf.fail(self, _PriorSyncFailedError())
1242 return
1243
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001244 if head == revid:
1245 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001246 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001247 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001248 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001249 # The copy/linkfile config may have changed.
1250 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001251 return
1252 else:
1253 lost = self._revlist(not_rev(revid), HEAD)
1254 if lost:
1255 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001256
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001257 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001258 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001259 if submodules:
1260 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001261 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001262 syncbuf.fail(self, e)
1263 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001264 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001265 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001266
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001267 if head == revid:
1268 # No changes; don't do anything further.
1269 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001270 # The copy/linkfile config may have changed.
1271 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001272 return
1273
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001276 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001278 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001280 syncbuf.info(self,
1281 "leaving %s; does not track upstream",
1282 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001284 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001285 if submodules:
1286 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001287 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001288 syncbuf.fail(self, e)
1289 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001290 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001291 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001293 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001294
1295 # See if we can perform a fast forward merge. This can happen if our
1296 # branch isn't in the exact same state as we last published.
1297 try:
1298 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1299 # Skip the published logic.
1300 pub = False
1301 except GitError:
1302 pub = self.WasPublished(branch.name, all_refs)
1303
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001305 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 if not_merged:
1307 if upstream_gain:
1308 # The user has published this branch and some of those
1309 # commits are not yet merged upstream. We do not want
1310 # to rewrite the published commits so we punt.
1311 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001312 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001313 "branch %s is published (but not merged) and is now "
1314 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001315 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001316 elif pub == head:
1317 # All published commits are merged, and thus we are a
1318 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001319 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001321 if submodules:
1322 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001323 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001325 # Examine the local commits not in the remote. Find the
1326 # last one attributed to this user, if any.
1327 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001328 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001329 last_mine = None
1330 cnt_mine = 0
1331 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001332 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001333 if committer_email == self.UserEmail:
1334 last_mine = commit_id
1335 cnt_mine += 1
1336
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001337 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001338 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001339
1340 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001341 syncbuf.fail(self, _DirtyError())
1342 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001343
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001344 # If the upstream switched on us, warn the user.
1345 #
1346 if branch.merge != self.revisionExpr:
1347 if branch.merge and self.revisionExpr:
1348 syncbuf.info(self,
1349 'manifest switched %s...%s',
1350 branch.merge,
1351 self.revisionExpr)
1352 elif branch.merge:
1353 syncbuf.info(self,
1354 'manifest no longer tracks %s',
1355 branch.merge)
1356
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001357 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001359 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001360 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001361 syncbuf.info(self,
1362 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001363 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001365 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001366 if not ID_RE.match(self.revisionExpr):
1367 # in case of manifest sync the revisionExpr might be a SHA1
1368 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001369 if not branch.merge.startswith('refs/'):
1370 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371 branch.Save()
1372
Mike Pontillod3153822012-02-28 11:53:24 -08001373 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001374 def _docopyandlink():
1375 self._CopyAndLinkFiles()
1376
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001377 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001378 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001379 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001380 if submodules:
1381 syncbuf.later2(self, _dosubmodules)
1382 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001383 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001384 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001385 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001386 if submodules:
1387 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001388 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001389 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001390 syncbuf.fail(self, e)
1391 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001392 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001393 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001394 if submodules:
1395 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001396
Mike Frysingere6a202f2019-08-02 15:57:57 -04001397 def AddCopyFile(self, src, dest, topdir):
1398 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399
Mike Frysingere6a202f2019-08-02 15:57:57 -04001400 No filesystem changes occur here. Actual copying happens later on.
1401
1402 Paths should have basic validation run on them before being queued.
1403 Further checking will be handled when the actual copy happens.
1404 """
1405 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1406
1407 def AddLinkFile(self, src, dest, topdir):
1408 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1409
1410 No filesystem changes occur here. Actual linking happens later on.
1411
1412 Paths should have basic validation run on them before being queued.
1413 Further checking will be handled when the actual link happens.
1414 """
1415 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001416
James W. Mills24c13082012-04-12 15:04:13 -05001417 def AddAnnotation(self, name, value, keep):
1418 self.annotations.append(_Annotation(name, value, keep))
1419
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001420 def DownloadPatchSet(self, change_id, patch_id):
1421 """Download a single patch set of a single change to FETCH_HEAD.
1422 """
1423 remote = self.GetRemote(self.remote.name)
1424
1425 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001426 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001427 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001428 if GitCommand(self, cmd, bare=True).Wait() != 0:
1429 return None
1430 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001431 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001432 change_id,
1433 patch_id,
1434 self.bare_git.rev_parse('FETCH_HEAD'))
1435
Mike Frysingerc0d18662020-02-19 19:19:18 -05001436 def DeleteWorktree(self, quiet=False, force=False):
1437 """Delete the source checkout and any other housekeeping tasks.
1438
1439 This currently leaves behind the internal .repo/ cache state. This helps
1440 when switching branches or manifest changes get reverted as we don't have
1441 to redownload all the git objects. But we should do some GC at some point.
1442
1443 Args:
1444 quiet: Whether to hide normal messages.
1445 force: Always delete tree even if dirty.
1446
1447 Returns:
1448 True if the worktree was completely cleaned out.
1449 """
1450 if self.IsDirty():
1451 if force:
1452 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1453 (self.relpath,), file=sys.stderr)
1454 else:
1455 print('error: %s: Cannot remove project: uncommitted changes are '
1456 'present.\n' % (self.relpath,), file=sys.stderr)
1457 return False
1458
1459 if not quiet:
1460 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1461
1462 # Unlock and delink from the main worktree. We don't use git's worktree
1463 # remove because it will recursively delete projects -- we handle that
1464 # ourselves below. https://crbug.com/git/48
1465 if self.use_git_worktrees:
1466 needle = platform_utils.realpath(self.gitdir)
1467 # Find the git worktree commondir under .repo/worktrees/.
1468 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1469 assert output.startswith('worktree '), output
1470 commondir = output[9:]
1471 # Walk each of the git worktrees to see where they point.
1472 configs = os.path.join(commondir, 'worktrees')
1473 for name in os.listdir(configs):
1474 gitdir = os.path.join(configs, name, 'gitdir')
1475 with open(gitdir) as fp:
1476 relpath = fp.read().strip()
1477 # Resolve the checkout path and see if it matches this project.
1478 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1479 if fullpath == needle:
1480 platform_utils.rmtree(os.path.join(configs, name))
1481
1482 # Delete the .git directory first, so we're less likely to have a partially
1483 # working git repository around. There shouldn't be any git projects here,
1484 # so rmtree works.
1485
1486 # Try to remove plain files first in case of git worktrees. If this fails
1487 # for any reason, we'll fall back to rmtree, and that'll display errors if
1488 # it can't remove things either.
1489 try:
1490 platform_utils.remove(self.gitdir)
1491 except OSError:
1492 pass
1493 try:
1494 platform_utils.rmtree(self.gitdir)
1495 except OSError as e:
1496 if e.errno != errno.ENOENT:
1497 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1498 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1499 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1500 return False
1501
1502 # Delete everything under the worktree, except for directories that contain
1503 # another git project.
1504 dirs_to_remove = []
1505 failed = False
1506 for root, dirs, files in platform_utils.walk(self.worktree):
1507 for f in files:
1508 path = os.path.join(root, f)
1509 try:
1510 platform_utils.remove(path)
1511 except OSError as e:
1512 if e.errno != errno.ENOENT:
1513 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1514 failed = True
1515 dirs[:] = [d for d in dirs
1516 if not os.path.lexists(os.path.join(root, d, '.git'))]
1517 dirs_to_remove += [os.path.join(root, d) for d in dirs
1518 if os.path.join(root, d) not in dirs_to_remove]
1519 for d in reversed(dirs_to_remove):
1520 if platform_utils.islink(d):
1521 try:
1522 platform_utils.remove(d)
1523 except OSError as e:
1524 if e.errno != errno.ENOENT:
1525 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1526 failed = True
1527 elif not platform_utils.listdir(d):
1528 try:
1529 platform_utils.rmdir(d)
1530 except OSError as e:
1531 if e.errno != errno.ENOENT:
1532 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1533 failed = True
1534 if failed:
1535 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1536 file=sys.stderr)
1537 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1538 return False
1539
1540 # Try deleting parent dirs if they are empty.
1541 path = self.worktree
1542 while path != self.manifest.topdir:
1543 try:
1544 platform_utils.rmdir(path)
1545 except OSError as e:
1546 if e.errno != errno.ENOENT:
1547 break
1548 path = os.path.dirname(path)
1549
1550 return True
1551
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001552# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001553 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554 """Create a new branch off the manifest's revision.
1555 """
Simran Basib9a1b732015-08-20 12:19:28 -07001556 if not branch_merge:
1557 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001558 head = self.work_git.GetHead()
1559 if head == (R_HEADS + name):
1560 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561
David Pursehouse8a68ff92012-09-24 12:15:13 +09001562 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001563 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001564 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001565 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001566 capture_stdout=True,
1567 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001568
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001569 branch = self.GetBranch(name)
1570 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001571 branch.merge = branch_merge
1572 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1573 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001574
1575 if revision is None:
1576 revid = self.GetRevisionId(all_refs)
1577 else:
1578 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001579
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001580 if head.startswith(R_HEADS):
1581 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001582 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001583 except KeyError:
1584 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001585 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001586 ref = R_HEADS + name
1587 self.work_git.update_ref(ref, revid)
1588 self.work_git.symbolic_ref(HEAD, ref)
1589 branch.Save()
1590 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001591
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001592 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001593 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001594 capture_stdout=True,
1595 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001596 branch.Save()
1597 return True
1598 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001599
Wink Saville02d79452009-04-10 13:01:24 -07001600 def CheckoutBranch(self, name):
1601 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001602
1603 Args:
1604 name: The name of the branch to checkout.
1605
1606 Returns:
1607 True if the checkout succeeded; False if it didn't; None if the branch
1608 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001609 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001610 rev = R_HEADS + name
1611 head = self.work_git.GetHead()
1612 if head == rev:
1613 # Already on the branch
1614 #
1615 return True
Wink Saville02d79452009-04-10 13:01:24 -07001616
David Pursehouse8a68ff92012-09-24 12:15:13 +09001617 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001618 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001619 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001620 except KeyError:
1621 # Branch does not exist in this project
1622 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001623 return None
Wink Saville02d79452009-04-10 13:01:24 -07001624
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001625 if head.startswith(R_HEADS):
1626 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001627 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001628 except KeyError:
1629 head = None
1630
1631 if head == revid:
1632 # Same revision; just update HEAD to point to the new
1633 # target branch, but otherwise take no other action.
1634 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001635 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1636 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001637 return True
1638
1639 return GitCommand(self,
1640 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001641 capture_stdout=True,
1642 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001643
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001644 def AbandonBranch(self, name):
1645 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001646
1647 Args:
1648 name: The name of the branch to abandon.
1649
1650 Returns:
1651 True if the abandon succeeded; False if it didn't; None if the branch
1652 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001653 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001654 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001655 all_refs = self.bare_ref.all
1656 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001657 # Doesn't exist
1658 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001659
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001660 head = self.work_git.GetHead()
1661 if head == rev:
1662 # We can't destroy the branch while we are sitting
1663 # on it. Switch to a detached HEAD.
1664 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001665 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001666
David Pursehouse8a68ff92012-09-24 12:15:13 +09001667 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001668 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001669 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001670 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001671 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001672
1673 return GitCommand(self,
1674 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001675 capture_stdout=True,
1676 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 def PruneHeads(self):
1679 """Prune any topic branches already merged into upstream.
1680 """
1681 cb = self.CurrentBranch
1682 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001683 left = self._allrefs
1684 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 if name.startswith(R_HEADS):
1686 name = name[len(R_HEADS):]
1687 if cb is None or name != cb:
1688 kill.append(name)
1689
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001690 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 if cb is not None \
1692 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001693 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694 self.work_git.DetachHead(HEAD)
1695 kill.append(cb)
1696
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001698 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700 try:
1701 self.bare_git.DetachHead(rev)
1702
1703 b = ['branch', '-d']
1704 b.extend(kill)
1705 b = GitCommand(self, b, bare=True,
1706 capture_stdout=True,
1707 capture_stderr=True)
1708 b.Wait()
1709 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001710 if ID_RE.match(old):
1711 self.bare_git.DetachHead(old)
1712 else:
1713 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001714 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001716 for branch in kill:
1717 if (R_HEADS + branch) not in left:
1718 self.CleanPublishedCache()
1719 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001720
1721 if cb and cb not in kill:
1722 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001723 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724
1725 kept = []
1726 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001727 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728 branch = self.GetBranch(branch)
1729 base = branch.LocalMerge
1730 if not base:
1731 base = rev
1732 kept.append(ReviewableBranch(self, branch, base))
1733 return kept
1734
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001735# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001736 def GetRegisteredSubprojects(self):
1737 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001738
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001739 def rec(subprojects):
1740 if not subprojects:
1741 return
1742 result.extend(subprojects)
1743 for p in subprojects:
1744 rec(p.subprojects)
1745 rec(self.subprojects)
1746 return result
1747
1748 def _GetSubmodules(self):
1749 # Unfortunately we cannot call `git submodule status --recursive` here
1750 # because the working tree might not exist yet, and it cannot be used
1751 # without a working tree in its current implementation.
1752
1753 def get_submodules(gitdir, rev):
1754 # Parse .gitmodules for submodule sub_paths and sub_urls
1755 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1756 if not sub_paths:
1757 return []
1758 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1759 # revision of submodule repository
1760 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1761 submodules = []
1762 for sub_path, sub_url in zip(sub_paths, sub_urls):
1763 try:
1764 sub_rev = sub_revs[sub_path]
1765 except KeyError:
1766 # Ignore non-exist submodules
1767 continue
1768 submodules.append((sub_rev, sub_path, sub_url))
1769 return submodules
1770
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001771 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1772 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001773
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001774 def parse_gitmodules(gitdir, rev):
1775 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1776 try:
Anthony King7bdac712014-07-16 12:56:40 +01001777 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1778 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001779 except GitError:
1780 return [], []
1781 if p.Wait() != 0:
1782 return [], []
1783
1784 gitmodules_lines = []
1785 fd, temp_gitmodules_path = tempfile.mkstemp()
1786 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001787 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001788 os.close(fd)
1789 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001790 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1791 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001792 if p.Wait() != 0:
1793 return [], []
1794 gitmodules_lines = p.stdout.split('\n')
1795 except GitError:
1796 return [], []
1797 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001798 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001799
1800 names = set()
1801 paths = {}
1802 urls = {}
1803 for line in gitmodules_lines:
1804 if not line:
1805 continue
1806 m = re_path.match(line)
1807 if m:
1808 names.add(m.group(1))
1809 paths[m.group(1)] = m.group(2)
1810 continue
1811 m = re_url.match(line)
1812 if m:
1813 names.add(m.group(1))
1814 urls[m.group(1)] = m.group(2)
1815 continue
1816 names = sorted(names)
1817 return ([paths.get(name, '') for name in names],
1818 [urls.get(name, '') for name in names])
1819
1820 def git_ls_tree(gitdir, rev, paths):
1821 cmd = ['ls-tree', rev, '--']
1822 cmd.extend(paths)
1823 try:
Anthony King7bdac712014-07-16 12:56:40 +01001824 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1825 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001826 except GitError:
1827 return []
1828 if p.Wait() != 0:
1829 return []
1830 objects = {}
1831 for line in p.stdout.split('\n'):
1832 if not line.strip():
1833 continue
1834 object_rev, object_path = line.split()[2:4]
1835 objects[object_path] = object_rev
1836 return objects
1837
1838 try:
1839 rev = self.GetRevisionId()
1840 except GitError:
1841 return []
1842 return get_submodules(self.gitdir, rev)
1843
1844 def GetDerivedSubprojects(self):
1845 result = []
1846 if not self.Exists:
1847 # If git repo does not exist yet, querying its submodules will
1848 # mess up its states; so return here.
1849 return result
1850 for rev, path, url in self._GetSubmodules():
1851 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001852 relpath, worktree, gitdir, objdir = \
1853 self.manifest.GetSubprojectPaths(self, name, path)
1854 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001855 if project:
1856 result.extend(project.GetDerivedSubprojects())
1857 continue
David James8d201162013-10-11 17:03:19 -07001858
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001859 if url.startswith('..'):
1860 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001861 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001862 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001863 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001864 review=self.remote.review,
1865 revision=self.remote.revision)
1866 subproject = Project(manifest=self.manifest,
1867 name=name,
1868 remote=remote,
1869 gitdir=gitdir,
1870 objdir=objdir,
1871 worktree=worktree,
1872 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001873 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001874 revisionId=rev,
1875 rebase=self.rebase,
1876 groups=self.groups,
1877 sync_c=self.sync_c,
1878 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001879 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001880 parent=self,
1881 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001882 result.append(subproject)
1883 result.extend(subproject.GetDerivedSubprojects())
1884 return result
1885
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001886# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001887 def EnableRepositoryExtension(self, key, value='true', version=1):
1888 """Enable git repository extension |key| with |value|.
1889
1890 Args:
1891 key: The extension to enabled. Omit the "extensions." prefix.
1892 value: The value to use for the extension.
1893 version: The minimum git repository version needed.
1894 """
1895 # Make sure the git repo version is new enough already.
1896 found_version = self.config.GetInt('core.repositoryFormatVersion')
1897 if found_version is None:
1898 found_version = 0
1899 if found_version < version:
1900 self.config.SetString('core.repositoryFormatVersion', str(version))
1901
1902 # Enable the extension!
1903 self.config.SetString('extensions.%s' % (key,), value)
1904
Mike Frysinger50a81de2020-09-06 15:51:21 -04001905 def ResolveRemoteHead(self, name=None):
1906 """Find out what the default branch (HEAD) points to.
1907
1908 Normally this points to refs/heads/master, but projects are moving to main.
1909 Support whatever the server uses rather than hardcoding "master" ourselves.
1910 """
1911 if name is None:
1912 name = self.remote.name
1913
1914 # The output will look like (NB: tabs are separators):
1915 # ref: refs/heads/master HEAD
1916 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1917 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1918
1919 for line in output.splitlines():
1920 lhs, rhs = line.split('\t', 1)
1921 if rhs == 'HEAD' and lhs.startswith('ref:'):
1922 return lhs[4:].strip()
1923
1924 return None
1925
Zac Livingstone4332262017-06-16 08:56:09 -06001926 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001927 try:
1928 # if revision (sha or tag) is not present then following function
1929 # throws an error.
1930 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1931 return True
1932 except GitError:
1933 # There is no such persistent revision. We have to fetch it.
1934 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001935
Julien Campergue335f5ef2013-10-16 11:02:35 +02001936 def _FetchArchive(self, tarpath, cwd=None):
1937 cmd = ['archive', '-v', '-o', tarpath]
1938 cmd.append('--remote=%s' % self.remote.url)
1939 cmd.append('--prefix=%s/' % self.relpath)
1940 cmd.append(self.revisionExpr)
1941
1942 command = GitCommand(self, cmd, cwd=cwd,
1943 capture_stdout=True,
1944 capture_stderr=True)
1945
1946 if command.Wait() != 0:
1947 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1948
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001949 def _RemoteFetch(self, name=None,
1950 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001951 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001952 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001953 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001954 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001955 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001956 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001957 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04001958 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07001959 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001960 clone_filter=None,
1961 retry_fetches=2,
1962 retry_sleep_initial_sec=4.0,
1963 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001964 is_sha1 = False
1965 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001966 # The depth should not be used when fetching to a mirror because
1967 # it will result in a shallow repository that cannot be cloned or
1968 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001969 # The repo project should also never be synced with partial depth.
1970 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1971 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001972
Shawn Pearce69e04d82014-01-29 12:48:54 -08001973 if depth:
1974 current_branch_only = True
1975
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001976 if ID_RE.match(self.revisionExpr) is not None:
1977 is_sha1 = True
1978
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001979 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001980 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001981 # this is a tag and its sha1 value should never change
1982 tag_name = self.revisionExpr[len(R_TAGS):]
1983
1984 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06001985 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05001986 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02001987 print('Skipped fetching project %s (already have persistent ref)'
1988 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001989 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001990 if is_sha1 and not depth:
1991 # When syncing a specific commit and --depth is not set:
1992 # * if upstream is explicitly specified and is not a sha1, fetch only
1993 # upstream as users expect only upstream to be fetch.
1994 # Note: The commit might not be in upstream in which case the sync
1995 # will fail.
1996 # * otherwise, fetch all branches to make sure we end up with the
1997 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02001998 if self.upstream:
1999 current_branch_only = not ID_RE.match(self.upstream)
2000 else:
2001 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002002
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002003 if not name:
2004 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002005
2006 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002007 remote = self.GetRemote(name)
2008 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002009 ssh_proxy = True
2010
Shawn O. Pearce88443382010-10-08 10:02:09 +02002011 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002012 if alt_dir and 'objects' == os.path.basename(alt_dir):
2013 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002014 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2015 remote = self.GetRemote(name)
2016
David Pursehouse8a68ff92012-09-24 12:15:13 +09002017 all_refs = self.bare_ref.all
2018 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002019 tmp = set()
2020
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302021 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002022 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002023 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002024 all_refs[r] = ref_id
2025 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 continue
2027
David Pursehouse8a68ff92012-09-24 12:15:13 +09002028 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002029 continue
2030
David Pursehouse8a68ff92012-09-24 12:15:13 +09002031 r = 'refs/_alt/%s' % ref_id
2032 all_refs[r] = ref_id
2033 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002034 tmp.add(r)
2035
heping3d7bbc92017-04-12 19:51:47 +08002036 tmp_packed_lines = []
2037 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002038
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302039 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002040 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002041 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002042 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002043 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002044
heping3d7bbc92017-04-12 19:51:47 +08002045 tmp_packed = ''.join(tmp_packed_lines)
2046 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002047 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002048 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002049 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002050
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002051 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002052
Xin Li745be2e2019-06-03 11:24:30 -07002053 if clone_filter:
2054 git_require((2, 19, 0), fail=True, msg='partial clones')
2055 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002056 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002057
Conley Owensf97e8382015-01-21 11:12:46 -08002058 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002059 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002060 else:
2061 # If this repo has shallow objects, then we don't know which refs have
2062 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2063 # do this with projects that don't have shallow objects, since it is less
2064 # efficient.
2065 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2066 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002067
Mike Frysinger4847e052020-02-22 00:07:35 -05002068 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002069 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002070 if not quiet and sys.stdout.isatty():
2071 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002072 if not self.worktree:
2073 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002074 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002075
Mike Frysingere57f1142019-03-18 21:27:54 -04002076 if force_sync:
2077 cmd.append('--force')
2078
David Pursehouse74cfd272015-10-14 10:50:15 +09002079 if prune:
2080 cmd.append('--prune')
2081
Martin Kellye4e94d22017-03-21 16:05:12 -07002082 if submodules:
2083 cmd.append('--recurse-submodules=on-demand')
2084
Kuang-che Wu6856f982019-11-25 12:37:55 +08002085 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002086 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002087 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002088 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002089 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002090 spec.append('tag')
2091 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002092
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302093 if self.manifest.IsMirror and not current_branch_only:
2094 branch = None
2095 else:
2096 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002097 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002098 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002099 # Shallow checkout of a specific commit, fetch from that commit and not
2100 # the heads only as the commit might be deeper in the history.
2101 spec.append(branch)
2102 else:
2103 if is_sha1:
2104 branch = self.upstream
2105 if branch is not None and branch.strip():
2106 if not branch.startswith('refs/'):
2107 branch = R_HEADS + branch
2108 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2109
2110 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2111 # whole repo.
2112 if self.manifest.IsMirror and not spec:
2113 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2114
2115 # If using depth then we should not get all the tags since they may
2116 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002117 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002118 cmd.append('--no-tags')
2119 else:
2120 cmd.append('--tags')
2121 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2122
Conley Owens80b87fe2014-05-09 17:13:44 -07002123 cmd.extend(spec)
2124
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002125 # At least one retry minimum due to git remote prune.
2126 retry_fetches = max(retry_fetches, 2)
2127 retry_cur_sleep = retry_sleep_initial_sec
2128 ok = prune_tried = False
2129 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002130 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002131 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002132 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002133 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002134 ok = True
2135 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002136
2137 # Retry later due to HTTP 429 Too Many Requests.
2138 elif ('error:' in gitcmd.stderr and
2139 'HTTP 429' in gitcmd.stderr):
2140 if not quiet:
2141 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2142 file=sys.stderr)
2143 time.sleep(retry_cur_sleep)
2144 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2145 MAXIMUM_RETRY_SLEEP_SEC)
2146 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2147 RETRY_JITTER_PERCENT))
2148 continue
2149
2150 # If this is not last attempt, try 'git remote prune'.
2151 elif (try_n < retry_fetches - 1 and
2152 'error:' in gitcmd.stderr and
2153 'git remote prune' in gitcmd.stderr and
2154 not prune_tried):
2155 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002156 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002157 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002158 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002159 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002160 break
2161 continue
Brian Harring14a66742012-09-28 20:21:57 -07002162 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002163 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2164 # in sha1 mode, we just tried sync'ing from the upstream field; it
2165 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002166 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002167 elif ret < 0:
2168 # Git died with a signal, exit immediately
2169 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002170 if not verbose:
2171 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002172 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002173
2174 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002175 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002176 if old_packed != '':
2177 _lwrite(packed_refs, old_packed)
2178 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002179 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002180 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002181
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002182 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002183 # We just synced the upstream given branch; verify we
2184 # got what we wanted, else trigger a second run of all
2185 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002186 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002187 # Sync the current branch only with depth set to None.
2188 # We always pass depth=None down to avoid infinite recursion.
2189 return self._RemoteFetch(
2190 name=name, quiet=quiet, verbose=verbose,
2191 current_branch_only=current_branch_only and depth,
2192 initial=False, alt_dir=alt_dir,
2193 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002194
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002195 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002196
Mike Frysingere50b6a72020-02-19 01:45:48 -05002197 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002198 if initial and \
2199 (self.manifest.manifestProject.config.GetString('repo.depth') or
2200 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002201 return False
2202
2203 remote = self.GetRemote(self.remote.name)
2204 bundle_url = remote.url + '/clone.bundle'
2205 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002206 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2207 'persistent-http',
2208 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002209 return False
2210
2211 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2212 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2213
2214 exist_dst = os.path.exists(bundle_dst)
2215 exist_tmp = os.path.exists(bundle_tmp)
2216
2217 if not initial and not exist_dst and not exist_tmp:
2218 return False
2219
2220 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002221 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2222 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002223 if not exist_dst:
2224 return False
2225
2226 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002227 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002228 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002229 if not quiet and sys.stdout.isatty():
2230 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002231 if not self.worktree:
2232 cmd.append('--update-head-ok')
2233 cmd.append(bundle_dst)
2234 for f in remote.fetch:
2235 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002236 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002237
2238 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002239 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002240 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002241 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002242 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002243 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002244
Mike Frysingere50b6a72020-02-19 01:45:48 -05002245 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002246 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002247 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002248
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002249 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002250 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002251 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002252 if os.path.exists(tmpPath):
2253 size = os.stat(tmpPath).st_size
2254 if size >= 1024:
2255 cmd += ['--continue-at', '%d' % (size,)]
2256 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002257 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002258 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002259 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002260 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002261 if proxy:
2262 cmd += ['--proxy', proxy]
2263 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2264 cmd += ['--proxy', os.environ['http_proxy']]
2265 if srcUrl.startswith('persistent-https'):
2266 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2267 elif srcUrl.startswith('persistent-http'):
2268 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002269 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002270
Dave Borowitz137d0132015-01-02 11:12:54 -08002271 if IsTrace():
2272 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002273 if verbose:
2274 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2275 stdout = None if verbose else subprocess.PIPE
2276 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002277 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002278 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002279 except OSError:
2280 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002281
Mike Frysingere50b6a72020-02-19 01:45:48 -05002282 (output, _) = proc.communicate()
2283 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002284
Dave Borowitz137d0132015-01-02 11:12:54 -08002285 if curlret == 22:
2286 # From curl man page:
2287 # 22: HTTP page not retrieved. The requested url was not found or
2288 # returned another error with the HTTP error code being 400 or above.
2289 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002290 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002291 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2292 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002293 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002294 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002295 elif curlret and not verbose and output:
2296 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002297
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002298 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002299 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002300 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002301 return True
2302 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002303 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002304 return False
2305 else:
2306 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002307
Kris Giesingc8d882a2014-12-23 13:02:32 -08002308 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002309 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002310 with open(path, 'rb') as f:
2311 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002312 return True
2313 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002314 if not quiet:
2315 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002316 return False
2317 except OSError:
2318 return False
2319
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002320 def _Checkout(self, rev, quiet=False):
2321 cmd = ['checkout']
2322 if quiet:
2323 cmd.append('-q')
2324 cmd.append(rev)
2325 cmd.append('--')
2326 if GitCommand(self, cmd).Wait() != 0:
2327 if self._allrefs:
2328 raise GitError('%s checkout %s ' % (self.name, rev))
2329
Mike Frysinger915fda12020-03-22 12:15:20 -04002330 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002331 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002332 if ffonly:
2333 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002334 if record_origin:
2335 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002336 cmd.append(rev)
2337 cmd.append('--')
2338 if GitCommand(self, cmd).Wait() != 0:
2339 if self._allrefs:
2340 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2341
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302342 def _LsRemote(self, refs):
2343 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302344 p = GitCommand(self, cmd, capture_stdout=True)
2345 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002346 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302347 return None
2348
Anthony King7bdac712014-07-16 12:56:40 +01002349 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002350 cmd = ['revert']
2351 cmd.append('--no-edit')
2352 cmd.append(rev)
2353 cmd.append('--')
2354 if GitCommand(self, cmd).Wait() != 0:
2355 if self._allrefs:
2356 raise GitError('%s revert %s ' % (self.name, rev))
2357
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002358 def _ResetHard(self, rev, quiet=True):
2359 cmd = ['reset', '--hard']
2360 if quiet:
2361 cmd.append('-q')
2362 cmd.append(rev)
2363 if GitCommand(self, cmd).Wait() != 0:
2364 raise GitError('%s reset --hard %s ' % (self.name, rev))
2365
Martin Kellye4e94d22017-03-21 16:05:12 -07002366 def _SyncSubmodules(self, quiet=True):
2367 cmd = ['submodule', 'update', '--init', '--recursive']
2368 if quiet:
2369 cmd.append('-q')
2370 if GitCommand(self, cmd).Wait() != 0:
2371 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2372
Anthony King7bdac712014-07-16 12:56:40 +01002373 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002374 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002375 if onto is not None:
2376 cmd.extend(['--onto', onto])
2377 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002378 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002379 raise GitError('%s rebase %s ' % (self.name, upstream))
2380
Pierre Tardy3d125942012-05-04 12:18:12 +02002381 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002382 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002383 if ffonly:
2384 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002385 if GitCommand(self, cmd).Wait() != 0:
2386 raise GitError('%s merge %s ' % (self.name, head))
2387
David Pursehousee8ace262020-02-13 12:41:15 +09002388 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002389 init_git_dir = not os.path.exists(self.gitdir)
2390 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002391 try:
2392 # Initialize the bare repository, which contains all of the objects.
2393 if init_obj_dir:
2394 os.makedirs(self.objdir)
2395 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002396
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002397 if self.use_git_worktrees:
2398 # Set up the m/ space to point to the worktree-specific ref space.
2399 # We'll update the worktree-specific ref space on each checkout.
2400 if self.manifest.branch:
2401 self.bare_git.symbolic_ref(
2402 '-m', 'redirecting to worktree scope',
2403 R_M + self.manifest.branch,
2404 R_WORKTREE_M + self.manifest.branch)
2405
2406 # Enable per-worktree config file support if possible. This is more a
2407 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002408 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002409 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002410
Kevin Degib1a07b82015-07-27 13:33:43 -06002411 # If we have a separate directory to hold refs, initialize it as well.
2412 if self.objdir != self.gitdir:
2413 if init_git_dir:
2414 os.makedirs(self.gitdir)
2415
2416 if init_obj_dir or init_git_dir:
2417 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2418 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002419 try:
2420 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2421 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002422 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002423 print("Retrying clone after deleting %s" %
2424 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002425 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002426 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2427 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002428 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002429 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002430 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2431 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002432 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002433 raise e
2434 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002435
Kevin Degi384b3c52014-10-16 16:02:58 -06002436 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002437 mp = self.manifest.manifestProject
2438 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002439
Kevin Degib1a07b82015-07-27 13:33:43 -06002440 if ref_dir or mirror_git:
2441 if not mirror_git:
2442 mirror_git = os.path.join(ref_dir, self.name + '.git')
2443 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2444 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002445 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2446 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002447
Kevin Degib1a07b82015-07-27 13:33:43 -06002448 if os.path.exists(mirror_git):
2449 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002450 elif os.path.exists(repo_git):
2451 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002452 elif os.path.exists(worktrees_git):
2453 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002454 else:
2455 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002456
Kevin Degib1a07b82015-07-27 13:33:43 -06002457 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002458 if not os.path.isabs(ref_dir):
2459 # The alternate directory is relative to the object database.
2460 ref_dir = os.path.relpath(ref_dir,
2461 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002462 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2463 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002464
David Pursehousee8ace262020-02-13 12:41:15 +09002465 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002466
2467 m = self.manifest.manifestProject.config
2468 for key in ['user.name', 'user.email']:
2469 if m.Has(key, include_defaults=False):
2470 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002471 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002472 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002473 if self.manifest.IsMirror:
2474 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002475 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002476 self.config.SetString('core.bare', None)
2477 except Exception:
2478 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002479 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002480 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002481 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002482 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002483
David Pursehousee8ace262020-02-13 12:41:15 +09002484 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002485 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002486 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002487
David Pursehousee8ace262020-02-13 12:41:15 +09002488 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002489 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002490 if not os.path.exists(hooks):
2491 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002492 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002493 name = os.path.basename(stock_hook)
2494
Victor Boivie65e0f352011-04-18 11:23:29 +02002495 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002496 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002497 # Don't install a Gerrit Code Review hook if this
2498 # project does not appear to use it for reviews.
2499 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002500 # Since the manifest project is one of those, but also
2501 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002502 continue
2503
2504 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002505 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002506 continue
2507 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002508 # If the files are the same, we'll leave it alone. We create symlinks
2509 # below by default but fallback to hardlinks if the OS blocks them.
2510 # So if we're here, it's probably because we made a hardlink below.
2511 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002512 if not quiet:
2513 _warn("%s: Not replacing locally modified %s hook",
2514 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002515 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002516 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002517 platform_utils.symlink(
2518 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002519 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002520 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002521 try:
2522 os.link(stock_hook, dst)
2523 except OSError:
2524 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002525 else:
2526 raise
2527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002528 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002529 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002530 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002531 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002532 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002533 remote.review = self.remote.review
2534 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002536 if self.worktree:
2537 remote.ResetFetch(mirror=False)
2538 else:
2539 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002540 remote.Save()
2541
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002542 def _InitMRef(self):
2543 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002544 if self.use_git_worktrees:
2545 # We can't update this ref with git worktrees until it exists.
2546 # We'll wait until the initial checkout to set it.
2547 if not os.path.exists(self.worktree):
2548 return
2549
2550 base = R_WORKTREE_M
2551 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002552
2553 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002554 else:
2555 base = R_M
2556 active_git = self.bare_git
2557
2558 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002559
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002560 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002561 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002562
Remy Böhmer1469c282020-12-15 18:49:02 +01002563 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002564 cur = self.bare_ref.symref(ref)
2565
2566 if self.revisionId:
2567 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2568 msg = 'manifest set to %s' % self.revisionId
2569 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002570 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002571 else:
2572 remote = self.GetRemote(self.remote.name)
2573 dst = remote.ToLocal(self.revisionExpr)
2574 if cur != dst:
2575 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002576 if detach:
2577 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2578 else:
2579 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002580
Kevin Degi384b3c52014-10-16 16:02:58 -06002581 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002582 # Git worktrees don't use symlinks to share at all.
2583 if self.use_git_worktrees:
2584 return
2585
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002586 symlink_files = self.shareable_files[:]
2587 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002588 if share_refs:
2589 symlink_files += self.working_tree_files
2590 symlink_dirs += self.working_tree_dirs
2591 to_symlink = symlink_files + symlink_dirs
2592 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002593 # Try to self-heal a bit in simple cases.
2594 dst_path = os.path.join(destdir, name)
2595 src_path = os.path.join(srcdir, name)
2596
2597 if name in self.working_tree_dirs:
2598 # If the dir is missing under .repo/projects/, create it.
2599 if not os.path.exists(src_path):
2600 os.makedirs(src_path)
2601
2602 elif name in self.working_tree_files:
2603 # If it's a file under the checkout .git/ and the .repo/projects/ has
2604 # nothing, move the file under the .repo/projects/ tree.
2605 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2606 platform_utils.rename(dst_path, src_path)
2607
2608 # If the path exists under the .repo/projects/ and there's no symlink
2609 # under the checkout .git/, recreate the symlink.
2610 if name in self.working_tree_dirs or name in self.working_tree_files:
2611 if os.path.exists(src_path) and not os.path.exists(dst_path):
2612 platform_utils.symlink(
2613 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2614
2615 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002616 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002617 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002618 # Fail if the links are pointing to the wrong place
2619 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002620 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002621 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002622 'work tree. If you\'re comfortable with the '
2623 'possibility of losing the work tree\'s git metadata,'
2624 ' use `repo sync --force-sync {0}` to '
2625 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002626
David James8d201162013-10-11 17:03:19 -07002627 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2628 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2629
2630 Args:
2631 gitdir: The bare git repository. Must already be initialized.
2632 dotgit: The repository you would like to initialize.
2633 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2634 Only one work tree can store refs under a given |gitdir|.
2635 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2636 This saves you the effort of initializing |dotgit| yourself.
2637 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002638 symlink_files = self.shareable_files[:]
2639 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002640 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002641 symlink_files += self.working_tree_files
2642 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002643 to_symlink = symlink_files + symlink_dirs
2644
2645 to_copy = []
2646 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002647 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002648
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002649 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002650 for name in set(to_copy).union(to_symlink):
2651 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002652 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002653 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002654
Kevin Degi384b3c52014-10-16 16:02:58 -06002655 if os.path.lexists(dst):
2656 continue
David James8d201162013-10-11 17:03:19 -07002657
2658 # If the source dir doesn't exist, create an empty dir.
2659 if name in symlink_dirs and not os.path.lexists(src):
2660 os.makedirs(src)
2661
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002662 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002663 platform_utils.symlink(
2664 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002665 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002666 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002667 shutil.copytree(src, dst)
2668 elif os.path.isfile(src):
2669 shutil.copy(src, dst)
2670
Conley Owens80b87fe2014-05-09 17:13:44 -07002671 # If the source file doesn't exist, ensure the destination
2672 # file doesn't either.
2673 if name in symlink_files and not os.path.lexists(src):
2674 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002675 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002676 except OSError:
2677 pass
2678
David James8d201162013-10-11 17:03:19 -07002679 except OSError as e:
2680 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002681 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002682 else:
2683 raise
2684
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002685 def _InitGitWorktree(self):
2686 """Init the project using git worktrees."""
2687 self.bare_git.worktree('prune')
2688 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2689 self.worktree, self.GetRevisionId())
2690
2691 # Rewrite the internal state files to use relative paths between the
2692 # checkouts & worktrees.
2693 dotgit = os.path.join(self.worktree, '.git')
2694 with open(dotgit, 'r') as fp:
2695 # Figure out the checkout->worktree path.
2696 setting = fp.read()
2697 assert setting.startswith('gitdir:')
2698 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002699 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2700 # of file permissions. Delete it and recreate it from scratch to avoid.
2701 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002702 # Use relative path from checkout->worktree & maintain Unix line endings
2703 # on all OS's to match git behavior.
2704 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002705 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2706 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002707 # Use relative path from worktree->checkout & maintain Unix line endings
2708 # on all OS's to match git behavior.
2709 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002710 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2711
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002712 self._InitMRef()
2713
Martin Kellye4e94d22017-03-21 16:05:12 -07002714 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002715 realdotgit = os.path.join(self.worktree, '.git')
2716 tmpdotgit = realdotgit + '.tmp'
2717 init_dotgit = not os.path.exists(realdotgit)
2718 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002719 if self.use_git_worktrees:
2720 self._InitGitWorktree()
2721 self._CopyAndLinkFiles()
2722 return
2723
Mike Frysingerf4545122019-11-11 04:34:16 -05002724 dotgit = tmpdotgit
2725 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2726 os.makedirs(tmpdotgit)
2727 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2728 copy_all=False)
2729 else:
2730 dotgit = realdotgit
2731
Kevin Degib1a07b82015-07-27 13:33:43 -06002732 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002733 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2734 except GitError as e:
2735 if force_sync and not init_dotgit:
2736 try:
2737 platform_utils.rmtree(dotgit)
2738 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002739 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002740 raise e
2741 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002742
Mike Frysingerf4545122019-11-11 04:34:16 -05002743 if init_dotgit:
2744 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002745
Mike Frysingerf4545122019-11-11 04:34:16 -05002746 # Now that the .git dir is fully set up, move it to its final home.
2747 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002748
Mike Frysingerf4545122019-11-11 04:34:16 -05002749 # Finish checking out the worktree.
2750 cmd = ['read-tree', '--reset', '-u']
2751 cmd.append('-v')
2752 cmd.append(HEAD)
2753 if GitCommand(self, cmd).Wait() != 0:
2754 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002755
Mike Frysingerf4545122019-11-11 04:34:16 -05002756 if submodules:
2757 self._SyncSubmodules(quiet=True)
2758 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002759
Renaud Paquay788e9622017-01-27 11:41:12 -08002760 def _get_symlink_error_message(self):
2761 if platform_utils.isWindows():
2762 return ('Unable to create symbolic link. Please re-run the command as '
2763 'Administrator, or see '
2764 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2765 'for other options.')
2766 return 'filesystem must support symlinks'
2767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002768 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002769 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002770
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002771 def _revlist(self, *args, **kw):
2772 a = []
2773 a.extend(args)
2774 a.append('--')
2775 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002776
2777 @property
2778 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002779 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002780
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002781 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002782 """Get logs between two revisions of this project."""
2783 comp = '..'
2784 if rev1:
2785 revs = [rev1]
2786 if rev2:
2787 revs.extend([comp, rev2])
2788 cmd = ['log', ''.join(revs)]
2789 out = DiffColoring(self.config)
2790 if out.is_on and color:
2791 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002792 if pretty_format is not None:
2793 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002794 if oneline:
2795 cmd.append('--oneline')
2796
2797 try:
2798 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2799 if log.Wait() == 0:
2800 return log.stdout
2801 except GitError:
2802 # worktree may not exist if groups changed for example. In that case,
2803 # try in gitdir instead.
2804 if not os.path.exists(self.worktree):
2805 return self.bare_git.log(*cmd[1:])
2806 else:
2807 raise
2808 return None
2809
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002810 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2811 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002812 """Get the list of logs from this revision to given revisionId"""
2813 logs = {}
2814 selfId = self.GetRevisionId(self._allrefs)
2815 toId = toProject.GetRevisionId(toProject._allrefs)
2816
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002817 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2818 pretty_format=pretty_format)
2819 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2820 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002821 return logs
2822
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002823 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002824
David James8d201162013-10-11 17:03:19 -07002825 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002826 self._project = project
2827 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002828 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002829
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002830 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2831 def __getstate__(self):
2832 return (self._project, self._bare, self._gitdir)
2833
2834 def __setstate__(self, state):
2835 self._project, self._bare, self._gitdir = state
2836
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002837 def LsOthers(self):
2838 p = GitCommand(self._project,
2839 ['ls-files',
2840 '-z',
2841 '--others',
2842 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002843 bare=False,
David James8d201162013-10-11 17:03:19 -07002844 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002845 capture_stdout=True,
2846 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002847 if p.Wait() == 0:
2848 out = p.stdout
2849 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002850 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002851 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002852 return []
2853
2854 def DiffZ(self, name, *args):
2855 cmd = [name]
2856 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002857 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002858 cmd.extend(args)
2859 p = GitCommand(self._project,
2860 cmd,
David James8d201162013-10-11 17:03:19 -07002861 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002862 bare=False,
2863 capture_stdout=True,
2864 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002865 try:
2866 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002867 if not hasattr(out, 'encode'):
2868 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002869 r = {}
2870 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002871 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002872 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002873 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002874 info = next(out)
2875 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002876 except StopIteration:
2877 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878
2879 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002880
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881 def __init__(self, path, omode, nmode, oid, nid, state):
2882 self.path = path
2883 self.src_path = None
2884 self.old_mode = omode
2885 self.new_mode = nmode
2886 self.old_id = oid
2887 self.new_id = nid
2888
2889 if len(state) == 1:
2890 self.status = state
2891 self.level = None
2892 else:
2893 self.status = state[:1]
2894 self.level = state[1:]
2895 while self.level.startswith('0'):
2896 self.level = self.level[1:]
2897
2898 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002899 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002900 if info.status in ('R', 'C'):
2901 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002902 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002903 r[info.path] = info
2904 return r
2905 finally:
2906 p.Wait()
2907
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002908 def GetDotgitPath(self, subpath=None):
2909 """Return the full path to the .git dir.
2910
2911 As a convenience, append |subpath| if provided.
2912 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002913 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002914 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002915 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002916 dotgit = os.path.join(self._project.worktree, '.git')
2917 if os.path.isfile(dotgit):
2918 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2919 with open(dotgit) as fp:
2920 setting = fp.read()
2921 assert setting.startswith('gitdir:')
2922 gitdir = setting.split(':', 1)[1].strip()
2923 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
2924
2925 return dotgit if subpath is None else os.path.join(dotgit, subpath)
2926
2927 def GetHead(self):
2928 """Return the ref that HEAD points to."""
2929 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002930 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002931 with open(path) as fd:
2932 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002933 except IOError as e:
2934 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002935 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302936 line = line.decode()
2937 except AttributeError:
2938 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002939 if line.startswith('ref: '):
2940 return line[5:-1]
2941 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002942
2943 def SetHead(self, ref, message=None):
2944 cmdv = []
2945 if message is not None:
2946 cmdv.extend(['-m', message])
2947 cmdv.append(HEAD)
2948 cmdv.append(ref)
2949 self.symbolic_ref(*cmdv)
2950
2951 def DetachHead(self, new, message=None):
2952 cmdv = ['--no-deref']
2953 if message is not None:
2954 cmdv.extend(['-m', message])
2955 cmdv.append(HEAD)
2956 cmdv.append(new)
2957 self.update_ref(*cmdv)
2958
2959 def UpdateRef(self, name, new, old=None,
2960 message=None,
2961 detach=False):
2962 cmdv = []
2963 if message is not None:
2964 cmdv.extend(['-m', message])
2965 if detach:
2966 cmdv.append('--no-deref')
2967 cmdv.append(name)
2968 cmdv.append(new)
2969 if old is not None:
2970 cmdv.append(old)
2971 self.update_ref(*cmdv)
2972
2973 def DeleteRef(self, name, old=None):
2974 if not old:
2975 old = self.rev_parse(name)
2976 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002977 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002978
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002979 def rev_list(self, *args, **kw):
2980 if 'format' in kw:
2981 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2982 else:
2983 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984 cmdv.extend(args)
2985 p = GitCommand(self._project,
2986 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002987 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002988 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002989 capture_stdout=True,
2990 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002991 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002992 raise GitError('%s rev-list %s: %s' %
2993 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002994 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995
2996 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002997 """Allow arbitrary git commands using pythonic syntax.
2998
2999 This allows you to do things like:
3000 git_obj.rev_parse('HEAD')
3001
3002 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3003 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003004 Any other positional arguments will be passed to the git command, and the
3005 following keyword arguments are supported:
3006 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003007
3008 Args:
3009 name: The name of the git command to call. Any '_' characters will
3010 be replaced with '-'.
3011
3012 Returns:
3013 A callable object that will try to call git with the named command.
3014 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003015 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003016
Dave Borowitz091f8932012-10-23 17:01:04 -07003017 def runner(*args, **kwargs):
3018 cmdv = []
3019 config = kwargs.pop('config', None)
3020 for k in kwargs:
3021 raise TypeError('%s() got an unexpected keyword argument %r'
3022 % (name, k))
3023 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303024 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003025 cmdv.append('-c')
3026 cmdv.append('%s=%s' % (k, v))
3027 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003028 cmdv.extend(args)
3029 p = GitCommand(self._project,
3030 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003031 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003032 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003033 capture_stdout=True,
3034 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003035 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003036 raise GitError('%s %s: %s' %
3037 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003038 r = p.stdout
3039 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3040 return r[:-1]
3041 return r
3042 return runner
3043
3044
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003045class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003046
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003047 def __str__(self):
3048 return 'prior sync failed; rebase still in progress'
3049
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003050
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003051class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003052
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003053 def __str__(self):
3054 return 'contains uncommitted changes'
3055
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003056
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003057class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003058
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003059 def __init__(self, project, text):
3060 self.project = project
3061 self.text = text
3062
3063 def Print(self, syncbuf):
3064 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3065 syncbuf.out.nl()
3066
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003067
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003068class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003069
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003070 def __init__(self, project, why):
3071 self.project = project
3072 self.why = why
3073
3074 def Print(self, syncbuf):
3075 syncbuf.out.fail('error: %s/: %s',
3076 self.project.relpath,
3077 str(self.why))
3078 syncbuf.out.nl()
3079
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003080
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003081class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003082
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003083 def __init__(self, project, action):
3084 self.project = project
3085 self.action = action
3086
3087 def Run(self, syncbuf):
3088 out = syncbuf.out
3089 out.project('project %s/', self.project.relpath)
3090 out.nl()
3091 try:
3092 self.action()
3093 out.nl()
3094 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003095 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003096 out.nl()
3097 return False
3098
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003099
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003100class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003101
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003102 def __init__(self, config):
3103 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003104 self.project = self.printer('header', attr='bold')
3105 self.info = self.printer('info')
3106 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003107
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003108
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003109class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003110
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003111 def __init__(self, config, detach_head=False):
3112 self._messages = []
3113 self._failures = []
3114 self._later_queue1 = []
3115 self._later_queue2 = []
3116
3117 self.out = _SyncColoring(config)
3118 self.out.redirect(sys.stderr)
3119
3120 self.detach_head = detach_head
3121 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003122 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003123
3124 def info(self, project, fmt, *args):
3125 self._messages.append(_InfoMessage(project, fmt % args))
3126
3127 def fail(self, project, err=None):
3128 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003129 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003130
3131 def later1(self, project, what):
3132 self._later_queue1.append(_Later(project, what))
3133
3134 def later2(self, project, what):
3135 self._later_queue2.append(_Later(project, what))
3136
3137 def Finish(self):
3138 self._PrintMessages()
3139 self._RunLater()
3140 self._PrintMessages()
3141 return self.clean
3142
David Rileye0684ad2017-04-05 00:02:59 -07003143 def Recently(self):
3144 recent_clean = self.recent_clean
3145 self.recent_clean = True
3146 return recent_clean
3147
3148 def _MarkUnclean(self):
3149 self.clean = False
3150 self.recent_clean = False
3151
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003152 def _RunLater(self):
3153 for q in ['_later_queue1', '_later_queue2']:
3154 if not self._RunQueue(q):
3155 return
3156
3157 def _RunQueue(self, queue):
3158 for m in getattr(self, queue):
3159 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003160 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003161 return False
3162 setattr(self, queue, [])
3163 return True
3164
3165 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003166 if self._messages or self._failures:
3167 if os.isatty(2):
3168 self.out.write(progress.CSI_ERASE_LINE)
3169 self.out.write('\r')
3170
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003171 for m in self._messages:
3172 m.Print(self)
3173 for m in self._failures:
3174 m.Print(self)
3175
3176 self._messages = []
3177 self._failures = []
3178
3179
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003180class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003181
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003182 """A special project housed under .repo.
3183 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003184
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003185 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003186 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003187 manifest=manifest,
3188 name=name,
3189 gitdir=gitdir,
3190 objdir=gitdir,
3191 worktree=worktree,
3192 remote=RemoteSpec('origin'),
3193 relpath='.repo/%s' % name,
3194 revisionExpr='refs/heads/master',
3195 revisionId=None,
3196 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003197
3198 def PreSync(self):
3199 if self.Exists:
3200 cb = self.CurrentBranch
3201 if cb:
3202 base = self.GetBranch(cb).merge
3203 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003204 self.revisionExpr = base
3205 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003206
Martin Kelly224a31a2017-07-10 14:46:25 -07003207 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003208 """ Prepare MetaProject for manifest branch switch
3209 """
3210
3211 # detach and delete manifest branch, allowing a new
3212 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003213 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003214 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003215 syncbuf.Finish()
3216
3217 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003218 ['update-ref', '-d', 'refs/heads/default'],
3219 capture_stdout=True,
3220 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003221
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003222 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003223 def LastFetch(self):
3224 try:
3225 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3226 return os.path.getmtime(fh)
3227 except OSError:
3228 return 0
3229
3230 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003231 def HasChanges(self):
3232 """Has the remote received new commits not yet checked out?
3233 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003234 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003235 return False
3236
David Pursehouse8a68ff92012-09-24 12:15:13 +09003237 all_refs = self.bare_ref.all
3238 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003239 head = self.work_git.GetHead()
3240 if head.startswith(R_HEADS):
3241 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003242 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003243 except KeyError:
3244 head = None
3245
3246 if revid == head:
3247 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003248 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003249 return True
3250 return False