blob: 50bb53c37fbb27598dc58f048bce9e3a2d110334 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070022import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import re
24import shutil
25import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070026import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070027import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020028import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080029import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070030import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070031
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070033from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070034from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
35 ID_RE
Remy Bohmer16c13282020-09-10 10:38:04 +020036from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040037from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080038from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070039import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040040import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040041from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050043from 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 -070044
David Pursehouse59bbb582013-05-17 10:49:33 +090045from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040046if is_python3():
47 import urllib.parse
48else:
49 import imp
50 import urlparse
51 urllib = imp.new_module('urllib')
52 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090053 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053054
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070055
George Engelbrecht9bc283e2020-04-02 12:36:09 -060056# Maximum sleep time allowed during retries.
57MAXIMUM_RETRY_SLEEP_SEC = 3600.0
58# +-10% random jitter is added to each Fetches retry sleep duration.
59RETRY_JITTER_PERCENT = 0.1
60
61
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062def _lwrite(path, content):
63 lock = '%s.lock' % path
64
Mike Frysinger3164d402019-11-11 05:40:22 -050065 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070067
68 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070069 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070070 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080071 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070072 raise
73
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070074
Shawn O. Pearce48244782009-04-16 08:25:57 -070075def _error(fmt, *args):
76 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070077 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070078
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070079
David Pursehousef33929d2015-08-24 14:39:14 +090080def _warn(fmt, *args):
81 msg = fmt % args
82 print('warn: %s' % msg, file=sys.stderr)
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070085def not_rev(r):
86 return '^' + r
87
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070088
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080089def sq(r):
90 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080091
David Pursehouse819827a2020-02-12 15:20:19 +090092
Jonathan Nieder93719792015-03-17 11:29:58 -070093_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070094
95
Jonathan Nieder93719792015-03-17 11:29:58 -070096def _ProjectHooks():
97 """List the hooks present in the 'hooks' directory.
98
99 These hooks are project hooks and are copied to the '.git/hooks' directory
100 of all subprojects.
101
102 This function caches the list of hooks (based on the contents of the
103 'repo/hooks' directory) on the first call.
104
105 Returns:
106 A list of absolute paths to all of the files in the hooks directory.
107 """
108 global _project_hook_list
109 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700110 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700111 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700112 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700113 return _project_hook_list
114
115
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700116class DownloadedChange(object):
117 _commit_cache = None
118
119 def __init__(self, project, base, change_id, ps_id, commit):
120 self.project = project
121 self.base = base
122 self.change_id = change_id
123 self.ps_id = ps_id
124 self.commit = commit
125
126 @property
127 def commits(self):
128 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700129 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
130 '--abbrev-commit',
131 '--pretty=oneline',
132 '--reverse',
133 '--date-order',
134 not_rev(self.base),
135 self.commit,
136 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700137 return self._commit_cache
138
139
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140class ReviewableBranch(object):
141 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400142 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700143
144 def __init__(self, project, branch, base):
145 self.project = project
146 self.branch = branch
147 self.base = base
148
149 @property
150 def name(self):
151 return self.branch.name
152
153 @property
154 def commits(self):
155 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400156 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
157 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
158 try:
159 self._commit_cache = self.project.bare_git.rev_list(*args)
160 except GitError:
161 # We weren't able to probe the commits for this branch. Was it tracking
162 # a branch that no longer exists? If so, return no commits. Otherwise,
163 # rethrow the error as we don't know what's going on.
164 if self.base_exists:
165 raise
166
167 self._commit_cache = []
168
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169 return self._commit_cache
170
171 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800172 def unabbrev_commits(self):
173 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700174 for commit in self.project.bare_git.rev_list(not_rev(self.base),
175 R_HEADS + self.name,
176 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800177 r[commit[0:8]] = commit
178 return r
179
180 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700182 return self.project.bare_git.log('--pretty=format:%cd',
183 '-n', '1',
184 R_HEADS + self.name,
185 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186
Mike Frysinger6da17752019-09-11 18:43:17 -0400187 @property
188 def base_exists(self):
189 """Whether the branch we're tracking exists.
190
191 Normally it should, but sometimes branches we track can get deleted.
192 """
193 if self._base_exists is None:
194 try:
195 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
196 # If we're still here, the base branch exists.
197 self._base_exists = True
198 except GitError:
199 # If we failed to verify, the base branch doesn't exist.
200 self._base_exists = False
201
202 return self._base_exists
203
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700204 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500205 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700206 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500207 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500208 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200209 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700210 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200211 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200212 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800213 validate_certs=True,
214 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500215 self.project.UploadForReview(branch=self.name,
216 people=people,
217 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700218 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500219 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500220 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200221 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700222 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200223 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200224 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800225 validate_certs=validate_certs,
226 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700228 def GetPublishedRefs(self):
229 refs = {}
230 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231 self.branch.remote.SshReviewUrl(self.project.UserEmail),
232 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700233 for line in output.split('\n'):
234 try:
235 (sha, ref) = line.split()
236 refs[sha] = ref
237 except ValueError:
238 pass
239
240 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700242
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700244
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 def __init__(self, config):
246 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100247 self.project = self.printer('header', attr='bold')
248 self.branch = self.printer('header', attr='bold')
249 self.nobranch = self.printer('nobranch', fg='red')
250 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
Anthony King7bdac712014-07-16 12:56:40 +0100252 self.added = self.printer('added', fg='green')
253 self.changed = self.printer('changed', fg='red')
254 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255
256
257class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700258
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259 def __init__(self, config):
260 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100261 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400262 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Anthony King7bdac712014-07-16 12:56:40 +0100265class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
James W. Mills24c13082012-04-12 15:04:13 -0500267 def __init__(self, name, value, keep):
268 self.name = name
269 self.value = value
270 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700271
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700272
Mike Frysingere6a202f2019-08-02 15:57:57 -0400273def _SafeExpandPath(base, subpath, skipfinal=False):
274 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700275
Mike Frysingere6a202f2019-08-02 15:57:57 -0400276 We make sure no intermediate symlinks are traversed, and that the final path
277 is not a special file (e.g. not a socket or fifo).
278
279 NB: We rely on a number of paths already being filtered out while parsing the
280 manifest. See the validation logic in manifest_xml.py for more details.
281 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500282 # Split up the path by its components. We can't use os.path.sep exclusively
283 # as some platforms (like Windows) will convert / to \ and that bypasses all
284 # our constructed logic here. Especially since manifest authors only use
285 # / in their paths.
286 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
287 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400288 if skipfinal:
289 # Whether the caller handles the final component itself.
290 finalpart = components.pop()
291
292 path = base
293 for part in components:
294 if part in {'.', '..'}:
295 raise ManifestInvalidPathError(
296 '%s: "%s" not allowed in paths' % (subpath, part))
297
298 path = os.path.join(path, part)
299 if platform_utils.islink(path):
300 raise ManifestInvalidPathError(
301 '%s: traversing symlinks not allow' % (path,))
302
303 if os.path.exists(path):
304 if not os.path.isfile(path) and not platform_utils.isdir(path):
305 raise ManifestInvalidPathError(
306 '%s: only regular files & directories allowed' % (path,))
307
308 if skipfinal:
309 path = os.path.join(path, finalpart)
310
311 return path
312
313
314class _CopyFile(object):
315 """Container for <copyfile> manifest element."""
316
317 def __init__(self, git_worktree, src, topdir, dest):
318 """Register a <copyfile> request.
319
320 Args:
321 git_worktree: Absolute path to the git project checkout.
322 src: Relative path under |git_worktree| of file to read.
323 topdir: Absolute path to the top of the repo client checkout.
324 dest: Relative path under |topdir| of file to write.
325 """
326 self.git_worktree = git_worktree
327 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328 self.src = src
329 self.dest = dest
330
331 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400332 src = _SafeExpandPath(self.git_worktree, self.src)
333 dest = _SafeExpandPath(self.topdir, self.dest)
334
335 if platform_utils.isdir(src):
336 raise ManifestInvalidPathError(
337 '%s: copying from directory not supported' % (self.src,))
338 if platform_utils.isdir(dest):
339 raise ManifestInvalidPathError(
340 '%s: copying to directory not allowed' % (self.dest,))
341
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700342 # copy file if it does not exist or is out of date
343 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
344 try:
345 # remove existing file first, since it might be read-only
346 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800347 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400348 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200349 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700350 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200351 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700352 shutil.copy(src, dest)
353 # make the file read-only
354 mode = os.stat(dest)[stat.ST_MODE]
355 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
356 os.chmod(dest, mode)
357 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700358 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700359
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700360
Anthony King7bdac712014-07-16 12:56:40 +0100361class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400362 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700363
Mike Frysingere6a202f2019-08-02 15:57:57 -0400364 def __init__(self, git_worktree, src, topdir, dest):
365 """Register a <linkfile> request.
366
367 Args:
368 git_worktree: Absolute path to the git project checkout.
369 src: Target of symlink relative to path under |git_worktree|.
370 topdir: Absolute path to the top of the repo client checkout.
371 dest: Relative path under |topdir| of symlink to create.
372 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700373 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400374 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500375 self.src = src
376 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377
Wink Saville4c426ef2015-06-03 08:05:17 -0700378 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700380 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 try:
382 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800383 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800384 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500385 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700386 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700387 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500388 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700389 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500390 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700391 _error('Cannot link file %s to %s', relSrc, absDest)
392
393 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400394 """Link the self.src & self.dest paths.
395
396 Handles wild cards on the src linking all of the files in the source in to
397 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700398 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500399 # Some people use src="." to create stable links to projects. Lets allow
400 # that but reject all other uses of "." to keep things simple.
401 if self.src == '.':
402 src = self.git_worktree
403 else:
404 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300406 if not glob.has_magic(src):
407 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
409 # dest & src are absolute paths at this point. Make sure the target of
410 # the symlink is relative in the context of the repo client checkout.
411 relpath = os.path.relpath(src, os.path.dirname(dest))
412 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700413 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400414 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300415 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400416 if os.path.exists(dest) and not platform_utils.isdir(dest):
417 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700418 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400419 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700420 # Create a releative path from source dir to destination dir
421 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400422 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700423
424 # Get the source file name
425 srcFile = os.path.basename(absSrcFile)
426
427 # Now form the final full paths to srcFile. They will be
428 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400429 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700430 relSrc = os.path.join(relSrcDir, srcFile)
431 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500432
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700433
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700434class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700435
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700436 def __init__(self,
437 name,
Anthony King7bdac712014-07-16 12:56:40 +0100438 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700439 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100440 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700441 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700442 orig_name=None,
443 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700444 self.name = name
445 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700446 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700447 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100448 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700449 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700450 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700451
452class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600453 # These objects can be shared between several working trees.
454 shareable_files = ['description', 'info']
455 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
456 # These objects can only be used by a single working tree.
457 working_tree_files = ['config', 'packed-refs', 'shallow']
458 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700459
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700460 def __init__(self,
461 manifest,
462 name,
463 remote,
464 gitdir,
David James8d201162013-10-11 17:03:19 -0700465 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700466 worktree,
467 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700468 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800469 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100470 rebase=True,
471 groups=None,
472 sync_c=False,
473 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900474 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100475 clone_depth=None,
476 upstream=None,
477 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500478 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100479 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900480 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700481 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600482 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700483 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800484 """Init a Project object.
485
486 Args:
487 manifest: The XmlManifest object.
488 name: The `name` attribute of manifest.xml's project element.
489 remote: RemoteSpec object specifying its remote's properties.
490 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700491 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800492 worktree: Absolute path of git working tree.
493 relpath: Relative path of git working tree to repo's top directory.
494 revisionExpr: The `revision` attribute of manifest.xml's project element.
495 revisionId: git commit id for checking out.
496 rebase: The `rebase` attribute of manifest.xml's project element.
497 groups: The `groups` attribute of manifest.xml's project element.
498 sync_c: The `sync-c` attribute of manifest.xml's project element.
499 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900500 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800501 upstream: The `upstream` attribute of manifest.xml's project element.
502 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500503 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800504 is_derived: False if the project was explicitly defined in the manifest;
505 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400506 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900507 optimized_fetch: If True, when a project is set to a sha1 revision, only
508 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600509 retry_fetches: Retry remote fetches n times upon receiving transient error
510 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700511 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800512 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513 self.manifest = manifest
514 self.name = name
515 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800516 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700517 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800518 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700519 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800520 else:
521 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700523 self.revisionExpr = revisionExpr
524
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700525 if revisionId is None \
526 and revisionExpr \
527 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700528 self.revisionId = revisionExpr
529 else:
530 self.revisionId = revisionId
531
Mike Pontillod3153822012-02-28 11:53:24 -0800532 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700533 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700534 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800535 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900536 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900537 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700538 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800539 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500540 # NB: Do not use this setting in __init__ to change behavior so that the
541 # manifest.git checkout can inspect & change it after instantiating. See
542 # the XmlManifest init code for more info.
543 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800544 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900545 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600546 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800547 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700549 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700550 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500551 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500552 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700553 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
554 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800556 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700557 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800558 else:
559 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700560 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700561 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700562 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400563 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700564 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565
Doug Anderson37282b42011-03-04 11:54:18 -0800566 # This will be filled in if a project is later identified to be the
567 # project containing repo hooks.
568 self.enabled_repo_hooks = []
569
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700570 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800571 def Derived(self):
572 return self.is_derived
573
574 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700576 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700577
578 @property
579 def CurrentBranch(self):
580 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400581
582 The branch name omits the 'refs/heads/' prefix.
583 None is returned if the project is on a detached HEAD, or if the work_git is
584 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700585 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400586 try:
587 b = self.work_git.GetHead()
588 except NoManifestException:
589 # If the local checkout is in a bad state, don't barf. Let the callers
590 # process this like the head is unreadable.
591 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700592 if b.startswith(R_HEADS):
593 return b[len(R_HEADS):]
594 return None
595
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700596 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500597 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
598 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
599 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200600
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601 def IsDirty(self, consider_untracked=True):
602 """Is the working directory modified in some way?
603 """
604 self.work_git.update_index('-q',
605 '--unmerged',
606 '--ignore-missing',
607 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900608 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609 return True
610 if self.work_git.DiffZ('diff-files'):
611 return True
612 if consider_untracked and self.work_git.LsOthers():
613 return True
614 return False
615
616 _userident_name = None
617 _userident_email = None
618
619 @property
620 def UserName(self):
621 """Obtain the user's personal name.
622 """
623 if self._userident_name is None:
624 self._LoadUserIdentity()
625 return self._userident_name
626
627 @property
628 def UserEmail(self):
629 """Obtain the user's email address. This is very likely
630 to be their Gerrit login.
631 """
632 if self._userident_email is None:
633 self._LoadUserIdentity()
634 return self._userident_email
635
636 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900637 u = self.bare_git.var('GIT_COMMITTER_IDENT')
638 m = re.compile("^(.*) <([^>]*)> ").match(u)
639 if m:
640 self._userident_name = m.group(1)
641 self._userident_email = m.group(2)
642 else:
643 self._userident_name = ''
644 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645
646 def GetRemote(self, name):
647 """Get the configuration for a single remote.
648 """
649 return self.config.GetRemote(name)
650
651 def GetBranch(self, name):
652 """Get the configuration for a single branch.
653 """
654 return self.config.GetBranch(name)
655
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700656 def GetBranches(self):
657 """Get all existing local branches.
658 """
659 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900660 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700661 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530663 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700664 if name.startswith(R_HEADS):
665 name = name[len(R_HEADS):]
666 b = self.GetBranch(name)
667 b.current = name == current
668 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900669 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700670 heads[name] = b
671
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530672 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700673 if name.startswith(R_PUB):
674 name = name[len(R_PUB):]
675 b = heads.get(name)
676 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900677 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700678
679 return heads
680
Colin Cross5acde752012-03-28 20:15:45 -0700681 def MatchesGroups(self, manifest_groups):
682 """Returns true if the manifest groups specified at init should cause
683 this project to be synced.
684 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700685 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700686
687 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700688 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700689 manifest_groups: "-group1,group2"
690 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500691
692 The special manifest group "default" will match any project that
693 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700694 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500695 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700696 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700697 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500698 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700699
Conley Owens971de8e2012-04-16 10:36:08 -0700700 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700701 for group in expanded_manifest_groups:
702 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700703 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700704 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700705 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700706
Conley Owens971de8e2012-04-16 10:36:08 -0700707 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700709# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700710 def UncommitedFiles(self, get_all=True):
711 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700713 Args:
714 get_all: a boolean, if True - get information about all different
715 uncommitted files. If False - return as soon as any kind of
716 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500717 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700718 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500719 self.work_git.update_index('-q',
720 '--unmerged',
721 '--ignore-missing',
722 '--refresh')
723 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700724 details.append("rebase in progress")
725 if not get_all:
726 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500727
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700728 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
729 if changes:
730 details.extend(changes)
731 if not get_all:
732 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500733
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700734 changes = self.work_git.DiffZ('diff-files').keys()
735 if changes:
736 details.extend(changes)
737 if not get_all:
738 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500739
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700740 changes = self.work_git.LsOthers()
741 if changes:
742 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500743
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700744 return details
745
746 def HasChanges(self):
747 """Returns true if there are uncommitted changes.
748 """
749 if self.UncommitedFiles(get_all=False):
750 return True
751 else:
752 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500753
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600754 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200756
757 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200758 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600759 quiet: If True then only print the project name. Do not print
760 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700761 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700762 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700763 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200764 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700765 print(file=output_redir)
766 print('project %s/' % self.relpath, file=output_redir)
767 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 return
769
770 self.work_git.update_index('-q',
771 '--unmerged',
772 '--ignore-missing',
773 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700774 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
776 df = self.work_git.DiffZ('diff-files')
777 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100778 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700779 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
781 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700782 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200783 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700784 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700785
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600786 if quiet:
787 out.nl()
788 return 'DIRTY'
789
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790 branch = self.CurrentBranch
791 if branch is None:
792 out.nobranch('(*** NO BRANCH ***)')
793 else:
794 out.branch('branch %s', branch)
795 out.nl()
796
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700797 if rb:
798 out.important('prior sync failed; rebase still in progress')
799 out.nl()
800
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801 paths = list()
802 paths.extend(di.keys())
803 paths.extend(df.keys())
804 paths.extend(do)
805
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530806 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900807 try:
808 i = di[p]
809 except KeyError:
810 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900812 try:
813 f = df[p]
814 except KeyError:
815 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200816
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900817 if i:
818 i_status = i.status.upper()
819 else:
820 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700821
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900822 if f:
823 f_status = f.status.lower()
824 else:
825 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700826
827 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800828 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700829 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830 else:
831 line = ' %s%s\t%s' % (i_status, f_status, p)
832
833 if i and not f:
834 out.added('%s', line)
835 elif (i and f) or (not i and f):
836 out.changed('%s', line)
837 elif not i and not f:
838 out.untracked('%s', line)
839 else:
840 out.write('%s', line)
841 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200842
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700843 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844
pelyad67872d2012-03-28 14:49:58 +0300845 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 """Prints the status of the repository to stdout.
847 """
848 out = DiffColoring(self.config)
849 cmd = ['diff']
850 if out.is_on:
851 cmd.append('--color')
852 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300853 if absolute_paths:
854 cmd.append('--src-prefix=a/%s/' % self.relpath)
855 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400857 try:
858 p = GitCommand(self,
859 cmd,
860 capture_stdout=True,
861 capture_stderr=True)
862 except GitError as e:
863 out.nl()
864 out.project('project %s/' % self.relpath)
865 out.nl()
866 out.fail('%s', str(e))
867 out.nl()
868 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700869 has_diff = False
870 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -0400871 if not hasattr(line, 'encode'):
872 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 if not has_diff:
874 out.nl()
875 out.project('project %s/' % self.relpath)
876 out.nl()
877 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700878 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400879 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700881# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900882 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700883 """Was the branch published (uploaded) for code review?
884 If so, returns the SHA-1 hash of the last published
885 state for the branch.
886 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700887 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900888 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700889 try:
890 return self.bare_git.rev_parse(key)
891 except GitError:
892 return None
893 else:
894 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900895 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700896 except KeyError:
897 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898
David Pursehouse8a68ff92012-09-24 12:15:13 +0900899 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700900 """Prunes any stale published refs.
901 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900902 if all_refs is None:
903 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904 heads = set()
905 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530906 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 if name.startswith(R_HEADS):
908 heads.add(name)
909 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900910 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530912 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700913 n = name[len(R_PUB):]
914 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700917 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700918 """List any branches which can be uploaded for review.
919 """
920 heads = {}
921 pubed = {}
922
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530923 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900925 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900927 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928
929 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530930 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900931 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700932 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700933 if selected_branch and branch != selected_branch:
934 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800936 rb = self.GetUploadableBranch(branch)
937 if rb:
938 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700939 return ready
940
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800941 def GetUploadableBranch(self, branch_name):
942 """Get a single uploadable branch, or None.
943 """
944 branch = self.GetBranch(branch_name)
945 base = branch.LocalMerge
946 if branch.LocalMerge:
947 rb = ReviewableBranch(self, branch, base)
948 if rb.commits:
949 return rb
950 return None
951
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700952 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100953 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500954 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700955 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500956 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500957 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200958 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700959 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200960 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200961 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800962 validate_certs=True,
963 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 """Uploads the named branch for code review.
965 """
966 if branch is None:
967 branch = self.CurrentBranch
968 if branch is None:
969 raise GitError('not currently on a branch')
970
971 branch = self.GetBranch(branch)
972 if not branch.LocalMerge:
973 raise GitError('branch %s does not track a remote' % branch.name)
974 if not branch.remote.review:
975 raise GitError('remote %s has no review url' % branch.remote.name)
976
Bryan Jacobsf609f912013-05-06 13:36:24 -0400977 if dest_branch is None:
978 dest_branch = self.dest_branch
979 if dest_branch is None:
980 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 if not dest_branch.startswith(R_HEADS):
982 dest_branch = R_HEADS + dest_branch
983
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800984 if not branch.remote.projectname:
985 branch.remote.projectname = self.name
986 branch.remote.Save()
987
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200988 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800989 if url is None:
990 raise UploadError('review not configured')
991 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500992 if dryrun:
993 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800994
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800995 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800996 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700997
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800998 for push_option in (push_options or []):
999 cmd.append('-o')
1000 cmd.append(push_option)
1001
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001002 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001003
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001004 if dest_branch.startswith(R_HEADS):
1005 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001006
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001007 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001008 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001009 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001010 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001011 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001012 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001013
David Pursehousef25a3702018-11-14 19:01:22 -08001014 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001015 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001016 if notify:
1017 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001018 if private:
1019 opts += ['private']
1020 if wip:
1021 opts += ['wip']
1022 if opts:
1023 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001024 cmd.append(ref_spec)
1025
Anthony King7bdac712014-07-16 12:56:40 +01001026 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001027 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001028
1029 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1030 self.bare_git.UpdateRef(R_PUB + branch.name,
1031 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001032 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001033
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001034# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001035 def _ExtractArchive(self, tarpath, path=None):
1036 """Extract the given tar on its current location
1037
1038 Args:
1039 - tarpath: The path to the actual tar file
1040
1041 """
1042 try:
1043 with tarfile.open(tarpath, 'r') as tar:
1044 tar.extractall(path=path)
1045 return True
1046 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001047 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001048 return False
1049
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001050 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001051 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001052 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001053 is_new=None,
1054 current_branch_only=False,
1055 force_sync=False,
1056 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001057 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001058 archive=False,
1059 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001060 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001061 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001062 submodules=False,
1063 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001064 """Perform only the network IO portion of the sync process.
1065 Local working directory/branch state is not affected.
1066 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001067 if archive and not isinstance(self, MetaProject):
1068 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001069 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001070 return False
1071
1072 name = self.relpath.replace('\\', '/')
1073 name = name.replace('/', '_')
1074 tarpath = '%s.tar' % name
1075 topdir = self.manifest.topdir
1076
1077 try:
1078 self._FetchArchive(tarpath, cwd=topdir)
1079 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001080 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001081 return False
1082
1083 # From now on, we only need absolute tarpath
1084 tarpath = os.path.join(topdir, tarpath)
1085
1086 if not self._ExtractArchive(tarpath, path=topdir):
1087 return False
1088 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001089 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001090 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001091 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001092 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001093 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001094 if is_new is None:
1095 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001096 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001097 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001098 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001099 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001101
1102 if is_new:
1103 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1104 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001105 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001106 # This works for both absolute and relative alternate directories.
1107 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001108 except IOError:
1109 alt_dir = None
1110 else:
1111 alt_dir = None
1112
Mike Frysingere50b6a72020-02-19 01:45:48 -05001113 if (clone_bundle
1114 and alt_dir is None
1115 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001116 is_new = False
1117
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001118 if not current_branch_only:
1119 if self.sync_c:
1120 current_branch_only = True
1121 elif not self.manifest._loaded:
1122 # Manifest cannot check defaults until it syncs.
1123 current_branch_only = False
1124 elif self.manifest.default.sync_c:
1125 current_branch_only = True
1126
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001127 if not self.sync_tags:
1128 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001129
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001130 if self.clone_depth:
1131 depth = self.clone_depth
1132 else:
1133 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1134
Mike Frysinger521d01b2020-02-17 01:51:49 -05001135 # See if we can skip the network fetch entirely.
1136 if not (optimized_fetch and
1137 (ID_RE.match(self.revisionExpr) and
1138 self._CheckForImmutableRevision())):
1139 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001140 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1141 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001142 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001143 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001144 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001145 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001146
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001147 mp = self.manifest.manifestProject
1148 dissociate = mp.config.GetBoolean('repo.dissociate')
1149 if dissociate:
1150 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1151 if os.path.exists(alternates_file):
1152 cmd = ['repack', '-a', '-d']
1153 if GitCommand(self, cmd, bare=True).Wait() != 0:
1154 return False
1155 platform_utils.remove(alternates_file)
1156
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001157 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001158 self._InitMRef()
1159 else:
1160 self._InitMirrorHead()
1161 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001162 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001163 except OSError:
1164 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001166
1167 def PostRepoUpgrade(self):
1168 self._InitHooks()
1169
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001170 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001171 if self.manifest.isGitcClient:
1172 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001173 for copyfile in self.copyfiles:
1174 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001175 for linkfile in self.linkfiles:
1176 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001177
Julien Camperguedd654222014-01-09 16:21:37 +01001178 def GetCommitRevisionId(self):
1179 """Get revisionId of a commit.
1180
1181 Use this method instead of GetRevisionId to get the id of the commit rather
1182 than the id of the current git object (for example, a tag)
1183
1184 """
1185 if not self.revisionExpr.startswith(R_TAGS):
1186 return self.GetRevisionId(self._allrefs)
1187
1188 try:
1189 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1190 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001191 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1192 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001193
David Pursehouse8a68ff92012-09-24 12:15:13 +09001194 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001195 if self.revisionId:
1196 return self.revisionId
1197
1198 rem = self.GetRemote(self.remote.name)
1199 rev = rem.ToLocal(self.revisionExpr)
1200
David Pursehouse8a68ff92012-09-24 12:15:13 +09001201 if all_refs is not None and rev in all_refs:
1202 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001203
1204 try:
1205 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1206 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001207 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1208 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001209
Martin Kellye4e94d22017-03-21 16:05:12 -07001210 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001211 """Perform only the local IO portion of the sync process.
1212 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001214 if not os.path.exists(self.gitdir):
1215 syncbuf.fail(self,
1216 'Cannot checkout %s due to missing network sync; Run '
1217 '`repo sync -n %s` first.' %
1218 (self.name, self.name))
1219 return
1220
Martin Kellye4e94d22017-03-21 16:05:12 -07001221 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001222 all_refs = self.bare_ref.all
1223 self.CleanPublishedCache(all_refs)
1224 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001225
David Pursehouse1d947b32012-10-25 12:23:11 +09001226 def _doff():
1227 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001228 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001229
Martin Kellye4e94d22017-03-21 16:05:12 -07001230 def _dosubmodules():
1231 self._SyncSubmodules(quiet=True)
1232
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001233 head = self.work_git.GetHead()
1234 if head.startswith(R_HEADS):
1235 branch = head[len(R_HEADS):]
1236 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001237 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001238 except KeyError:
1239 head = None
1240 else:
1241 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001243 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001244 # Currently on a detached HEAD. The user is assumed to
1245 # not have any local modifications worth worrying about.
1246 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001247 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001248 syncbuf.fail(self, _PriorSyncFailedError())
1249 return
1250
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001251 if head == revid:
1252 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001253 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001254 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001255 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001256 # The copy/linkfile config may have changed.
1257 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001258 return
1259 else:
1260 lost = self._revlist(not_rev(revid), HEAD)
1261 if lost:
1262 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001263
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001264 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001265 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001266 if submodules:
1267 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001268 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001269 syncbuf.fail(self, e)
1270 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001271 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001272 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001274 if head == revid:
1275 # No changes; don't do anything further.
1276 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001277 # The copy/linkfile config may have changed.
1278 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001279 return
1280
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001283 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001285 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001287 syncbuf.info(self,
1288 "leaving %s; does not track upstream",
1289 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001291 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001292 if submodules:
1293 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001294 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001295 syncbuf.fail(self, e)
1296 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001297 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001298 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001300 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001301
1302 # See if we can perform a fast forward merge. This can happen if our
1303 # branch isn't in the exact same state as we last published.
1304 try:
1305 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1306 # Skip the published logic.
1307 pub = False
1308 except GitError:
1309 pub = self.WasPublished(branch.name, all_refs)
1310
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001312 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313 if not_merged:
1314 if upstream_gain:
1315 # The user has published this branch and some of those
1316 # commits are not yet merged upstream. We do not want
1317 # to rewrite the published commits so we punt.
1318 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001319 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001320 "branch %s is published (but not merged) and is now "
1321 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001322 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001323 elif pub == head:
1324 # All published commits are merged, and thus we are a
1325 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001326 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001327 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001328 if submodules:
1329 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001330 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001332 # Examine the local commits not in the remote. Find the
1333 # last one attributed to this user, if any.
1334 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001335 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001336 last_mine = None
1337 cnt_mine = 0
1338 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001339 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001340 if committer_email == self.UserEmail:
1341 last_mine = commit_id
1342 cnt_mine += 1
1343
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001344 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001345 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001346
1347 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001348 syncbuf.fail(self, _DirtyError())
1349 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001351 # If the upstream switched on us, warn the user.
1352 #
1353 if branch.merge != self.revisionExpr:
1354 if branch.merge and self.revisionExpr:
1355 syncbuf.info(self,
1356 'manifest switched %s...%s',
1357 branch.merge,
1358 self.revisionExpr)
1359 elif branch.merge:
1360 syncbuf.info(self,
1361 'manifest no longer tracks %s',
1362 branch.merge)
1363
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001364 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001366 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001367 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001368 syncbuf.info(self,
1369 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001370 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001371
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001372 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001373 if not ID_RE.match(self.revisionExpr):
1374 # in case of manifest sync the revisionExpr might be a SHA1
1375 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001376 if not branch.merge.startswith('refs/'):
1377 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001378 branch.Save()
1379
Mike Pontillod3153822012-02-28 11:53:24 -08001380 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001381 def _docopyandlink():
1382 self._CopyAndLinkFiles()
1383
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001384 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001385 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001386 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001387 if submodules:
1388 syncbuf.later2(self, _dosubmodules)
1389 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001390 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001392 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001393 if submodules:
1394 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001395 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001396 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001397 syncbuf.fail(self, e)
1398 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001399 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001400 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001401 if submodules:
1402 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001403
Mike Frysingere6a202f2019-08-02 15:57:57 -04001404 def AddCopyFile(self, src, dest, topdir):
1405 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406
Mike Frysingere6a202f2019-08-02 15:57:57 -04001407 No filesystem changes occur here. Actual copying happens later on.
1408
1409 Paths should have basic validation run on them before being queued.
1410 Further checking will be handled when the actual copy happens.
1411 """
1412 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1413
1414 def AddLinkFile(self, src, dest, topdir):
1415 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1416
1417 No filesystem changes occur here. Actual linking happens later on.
1418
1419 Paths should have basic validation run on them before being queued.
1420 Further checking will be handled when the actual link happens.
1421 """
1422 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001423
James W. Mills24c13082012-04-12 15:04:13 -05001424 def AddAnnotation(self, name, value, keep):
1425 self.annotations.append(_Annotation(name, value, keep))
1426
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001427 def DownloadPatchSet(self, change_id, patch_id):
1428 """Download a single patch set of a single change to FETCH_HEAD.
1429 """
1430 remote = self.GetRemote(self.remote.name)
1431
1432 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001433 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001434 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001435 if GitCommand(self, cmd, bare=True).Wait() != 0:
1436 return None
1437 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001438 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001439 change_id,
1440 patch_id,
1441 self.bare_git.rev_parse('FETCH_HEAD'))
1442
Mike Frysingerc0d18662020-02-19 19:19:18 -05001443 def DeleteWorktree(self, quiet=False, force=False):
1444 """Delete the source checkout and any other housekeeping tasks.
1445
1446 This currently leaves behind the internal .repo/ cache state. This helps
1447 when switching branches or manifest changes get reverted as we don't have
1448 to redownload all the git objects. But we should do some GC at some point.
1449
1450 Args:
1451 quiet: Whether to hide normal messages.
1452 force: Always delete tree even if dirty.
1453
1454 Returns:
1455 True if the worktree was completely cleaned out.
1456 """
1457 if self.IsDirty():
1458 if force:
1459 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1460 (self.relpath,), file=sys.stderr)
1461 else:
1462 print('error: %s: Cannot remove project: uncommitted changes are '
1463 'present.\n' % (self.relpath,), file=sys.stderr)
1464 return False
1465
1466 if not quiet:
1467 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1468
1469 # Unlock and delink from the main worktree. We don't use git's worktree
1470 # remove because it will recursively delete projects -- we handle that
1471 # ourselves below. https://crbug.com/git/48
1472 if self.use_git_worktrees:
1473 needle = platform_utils.realpath(self.gitdir)
1474 # Find the git worktree commondir under .repo/worktrees/.
1475 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1476 assert output.startswith('worktree '), output
1477 commondir = output[9:]
1478 # Walk each of the git worktrees to see where they point.
1479 configs = os.path.join(commondir, 'worktrees')
1480 for name in os.listdir(configs):
1481 gitdir = os.path.join(configs, name, 'gitdir')
1482 with open(gitdir) as fp:
1483 relpath = fp.read().strip()
1484 # Resolve the checkout path and see if it matches this project.
1485 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1486 if fullpath == needle:
1487 platform_utils.rmtree(os.path.join(configs, name))
1488
1489 # Delete the .git directory first, so we're less likely to have a partially
1490 # working git repository around. There shouldn't be any git projects here,
1491 # so rmtree works.
1492
1493 # Try to remove plain files first in case of git worktrees. If this fails
1494 # for any reason, we'll fall back to rmtree, and that'll display errors if
1495 # it can't remove things either.
1496 try:
1497 platform_utils.remove(self.gitdir)
1498 except OSError:
1499 pass
1500 try:
1501 platform_utils.rmtree(self.gitdir)
1502 except OSError as e:
1503 if e.errno != errno.ENOENT:
1504 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1505 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1506 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1507 return False
1508
1509 # Delete everything under the worktree, except for directories that contain
1510 # another git project.
1511 dirs_to_remove = []
1512 failed = False
1513 for root, dirs, files in platform_utils.walk(self.worktree):
1514 for f in files:
1515 path = os.path.join(root, f)
1516 try:
1517 platform_utils.remove(path)
1518 except OSError as e:
1519 if e.errno != errno.ENOENT:
1520 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1521 failed = True
1522 dirs[:] = [d for d in dirs
1523 if not os.path.lexists(os.path.join(root, d, '.git'))]
1524 dirs_to_remove += [os.path.join(root, d) for d in dirs
1525 if os.path.join(root, d) not in dirs_to_remove]
1526 for d in reversed(dirs_to_remove):
1527 if platform_utils.islink(d):
1528 try:
1529 platform_utils.remove(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 elif not platform_utils.listdir(d):
1535 try:
1536 platform_utils.rmdir(d)
1537 except OSError as e:
1538 if e.errno != errno.ENOENT:
1539 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1540 failed = True
1541 if failed:
1542 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1543 file=sys.stderr)
1544 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1545 return False
1546
1547 # Try deleting parent dirs if they are empty.
1548 path = self.worktree
1549 while path != self.manifest.topdir:
1550 try:
1551 platform_utils.rmdir(path)
1552 except OSError as e:
1553 if e.errno != errno.ENOENT:
1554 break
1555 path = os.path.dirname(path)
1556
1557 return True
1558
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001559# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001560 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001561 """Create a new branch off the manifest's revision.
1562 """
Simran Basib9a1b732015-08-20 12:19:28 -07001563 if not branch_merge:
1564 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001565 head = self.work_git.GetHead()
1566 if head == (R_HEADS + name):
1567 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001568
David Pursehouse8a68ff92012-09-24 12:15:13 +09001569 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001570 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001571 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001572 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001573 capture_stdout=True,
1574 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001575
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001576 branch = self.GetBranch(name)
1577 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001578 branch.merge = branch_merge
1579 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1580 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001581
1582 if revision is None:
1583 revid = self.GetRevisionId(all_refs)
1584 else:
1585 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001586
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001587 if head.startswith(R_HEADS):
1588 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001589 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001590 except KeyError:
1591 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001592 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001593 ref = R_HEADS + name
1594 self.work_git.update_ref(ref, revid)
1595 self.work_git.symbolic_ref(HEAD, ref)
1596 branch.Save()
1597 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001598
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001599 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001600 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001601 capture_stdout=True,
1602 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001603 branch.Save()
1604 return True
1605 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606
Wink Saville02d79452009-04-10 13:01:24 -07001607 def CheckoutBranch(self, name):
1608 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001609
1610 Args:
1611 name: The name of the branch to checkout.
1612
1613 Returns:
1614 True if the checkout succeeded; False if it didn't; None if the branch
1615 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001616 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001617 rev = R_HEADS + name
1618 head = self.work_git.GetHead()
1619 if head == rev:
1620 # Already on the branch
1621 #
1622 return True
Wink Saville02d79452009-04-10 13:01:24 -07001623
David Pursehouse8a68ff92012-09-24 12:15:13 +09001624 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001625 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001626 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001627 except KeyError:
1628 # Branch does not exist in this project
1629 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001630 return None
Wink Saville02d79452009-04-10 13:01:24 -07001631
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001632 if head.startswith(R_HEADS):
1633 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001634 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001635 except KeyError:
1636 head = None
1637
1638 if head == revid:
1639 # Same revision; just update HEAD to point to the new
1640 # target branch, but otherwise take no other action.
1641 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001642 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1643 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001644 return True
1645
1646 return GitCommand(self,
1647 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001648 capture_stdout=True,
1649 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001650
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001651 def AbandonBranch(self, name):
1652 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001653
1654 Args:
1655 name: The name of the branch to abandon.
1656
1657 Returns:
1658 True if the abandon succeeded; False if it didn't; None if the branch
1659 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001660 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001661 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001662 all_refs = self.bare_ref.all
1663 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001664 # Doesn't exist
1665 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001666
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001667 head = self.work_git.GetHead()
1668 if head == rev:
1669 # We can't destroy the branch while we are sitting
1670 # on it. Switch to a detached HEAD.
1671 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001672 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001673
David Pursehouse8a68ff92012-09-24 12:15:13 +09001674 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001675 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001676 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001677 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001678 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001679
1680 return GitCommand(self,
1681 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001682 capture_stdout=True,
1683 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001684
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 def PruneHeads(self):
1686 """Prune any topic branches already merged into upstream.
1687 """
1688 cb = self.CurrentBranch
1689 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001690 left = self._allrefs
1691 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 if name.startswith(R_HEADS):
1693 name = name[len(R_HEADS):]
1694 if cb is None or name != cb:
1695 kill.append(name)
1696
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001697 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001698 if cb is not None \
1699 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001700 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 self.work_git.DetachHead(HEAD)
1702 kill.append(cb)
1703
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001705 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001707 try:
1708 self.bare_git.DetachHead(rev)
1709
1710 b = ['branch', '-d']
1711 b.extend(kill)
1712 b = GitCommand(self, b, bare=True,
1713 capture_stdout=True,
1714 capture_stderr=True)
1715 b.Wait()
1716 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001717 if ID_RE.match(old):
1718 self.bare_git.DetachHead(old)
1719 else:
1720 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001721 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001722
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001723 for branch in kill:
1724 if (R_HEADS + branch) not in left:
1725 self.CleanPublishedCache()
1726 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001727
1728 if cb and cb not in kill:
1729 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001730 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001731
1732 kept = []
1733 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001734 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001735 branch = self.GetBranch(branch)
1736 base = branch.LocalMerge
1737 if not base:
1738 base = rev
1739 kept.append(ReviewableBranch(self, branch, base))
1740 return kept
1741
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001742# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001743 def GetRegisteredSubprojects(self):
1744 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001745
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001746 def rec(subprojects):
1747 if not subprojects:
1748 return
1749 result.extend(subprojects)
1750 for p in subprojects:
1751 rec(p.subprojects)
1752 rec(self.subprojects)
1753 return result
1754
1755 def _GetSubmodules(self):
1756 # Unfortunately we cannot call `git submodule status --recursive` here
1757 # because the working tree might not exist yet, and it cannot be used
1758 # without a working tree in its current implementation.
1759
1760 def get_submodules(gitdir, rev):
1761 # Parse .gitmodules for submodule sub_paths and sub_urls
1762 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1763 if not sub_paths:
1764 return []
1765 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1766 # revision of submodule repository
1767 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1768 submodules = []
1769 for sub_path, sub_url in zip(sub_paths, sub_urls):
1770 try:
1771 sub_rev = sub_revs[sub_path]
1772 except KeyError:
1773 # Ignore non-exist submodules
1774 continue
1775 submodules.append((sub_rev, sub_path, sub_url))
1776 return submodules
1777
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001778 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1779 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001780
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001781 def parse_gitmodules(gitdir, rev):
1782 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1783 try:
Anthony King7bdac712014-07-16 12:56:40 +01001784 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1785 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001786 except GitError:
1787 return [], []
1788 if p.Wait() != 0:
1789 return [], []
1790
1791 gitmodules_lines = []
1792 fd, temp_gitmodules_path = tempfile.mkstemp()
1793 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001794 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001795 os.close(fd)
1796 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001797 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1798 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001799 if p.Wait() != 0:
1800 return [], []
1801 gitmodules_lines = p.stdout.split('\n')
1802 except GitError:
1803 return [], []
1804 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001805 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001806
1807 names = set()
1808 paths = {}
1809 urls = {}
1810 for line in gitmodules_lines:
1811 if not line:
1812 continue
1813 m = re_path.match(line)
1814 if m:
1815 names.add(m.group(1))
1816 paths[m.group(1)] = m.group(2)
1817 continue
1818 m = re_url.match(line)
1819 if m:
1820 names.add(m.group(1))
1821 urls[m.group(1)] = m.group(2)
1822 continue
1823 names = sorted(names)
1824 return ([paths.get(name, '') for name in names],
1825 [urls.get(name, '') for name in names])
1826
1827 def git_ls_tree(gitdir, rev, paths):
1828 cmd = ['ls-tree', rev, '--']
1829 cmd.extend(paths)
1830 try:
Anthony King7bdac712014-07-16 12:56:40 +01001831 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1832 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001833 except GitError:
1834 return []
1835 if p.Wait() != 0:
1836 return []
1837 objects = {}
1838 for line in p.stdout.split('\n'):
1839 if not line.strip():
1840 continue
1841 object_rev, object_path = line.split()[2:4]
1842 objects[object_path] = object_rev
1843 return objects
1844
1845 try:
1846 rev = self.GetRevisionId()
1847 except GitError:
1848 return []
1849 return get_submodules(self.gitdir, rev)
1850
1851 def GetDerivedSubprojects(self):
1852 result = []
1853 if not self.Exists:
1854 # If git repo does not exist yet, querying its submodules will
1855 # mess up its states; so return here.
1856 return result
1857 for rev, path, url in self._GetSubmodules():
1858 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001859 relpath, worktree, gitdir, objdir = \
1860 self.manifest.GetSubprojectPaths(self, name, path)
1861 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001862 if project:
1863 result.extend(project.GetDerivedSubprojects())
1864 continue
David James8d201162013-10-11 17:03:19 -07001865
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001866 if url.startswith('..'):
1867 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001868 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001869 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001870 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001871 review=self.remote.review,
1872 revision=self.remote.revision)
1873 subproject = Project(manifest=self.manifest,
1874 name=name,
1875 remote=remote,
1876 gitdir=gitdir,
1877 objdir=objdir,
1878 worktree=worktree,
1879 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001880 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001881 revisionId=rev,
1882 rebase=self.rebase,
1883 groups=self.groups,
1884 sync_c=self.sync_c,
1885 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001886 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001887 parent=self,
1888 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001889 result.append(subproject)
1890 result.extend(subproject.GetDerivedSubprojects())
1891 return result
1892
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001893# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001894 def EnableRepositoryExtension(self, key, value='true', version=1):
1895 """Enable git repository extension |key| with |value|.
1896
1897 Args:
1898 key: The extension to enabled. Omit the "extensions." prefix.
1899 value: The value to use for the extension.
1900 version: The minimum git repository version needed.
1901 """
1902 # Make sure the git repo version is new enough already.
1903 found_version = self.config.GetInt('core.repositoryFormatVersion')
1904 if found_version is None:
1905 found_version = 0
1906 if found_version < version:
1907 self.config.SetString('core.repositoryFormatVersion', str(version))
1908
1909 # Enable the extension!
1910 self.config.SetString('extensions.%s' % (key,), value)
1911
Mike Frysinger50a81de2020-09-06 15:51:21 -04001912 def ResolveRemoteHead(self, name=None):
1913 """Find out what the default branch (HEAD) points to.
1914
1915 Normally this points to refs/heads/master, but projects are moving to main.
1916 Support whatever the server uses rather than hardcoding "master" ourselves.
1917 """
1918 if name is None:
1919 name = self.remote.name
1920
1921 # The output will look like (NB: tabs are separators):
1922 # ref: refs/heads/master HEAD
1923 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1924 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1925
1926 for line in output.splitlines():
1927 lhs, rhs = line.split('\t', 1)
1928 if rhs == 'HEAD' and lhs.startswith('ref:'):
1929 return lhs[4:].strip()
1930
1931 return None
1932
Zac Livingstone4332262017-06-16 08:56:09 -06001933 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001934 try:
1935 # if revision (sha or tag) is not present then following function
1936 # throws an error.
1937 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1938 return True
1939 except GitError:
1940 # There is no such persistent revision. We have to fetch it.
1941 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001942
Julien Campergue335f5ef2013-10-16 11:02:35 +02001943 def _FetchArchive(self, tarpath, cwd=None):
1944 cmd = ['archive', '-v', '-o', tarpath]
1945 cmd.append('--remote=%s' % self.remote.url)
1946 cmd.append('--prefix=%s/' % self.relpath)
1947 cmd.append(self.revisionExpr)
1948
1949 command = GitCommand(self, cmd, cwd=cwd,
1950 capture_stdout=True,
1951 capture_stderr=True)
1952
1953 if command.Wait() != 0:
1954 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1955
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001956 def _RemoteFetch(self, name=None,
1957 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001958 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001959 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001960 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001961 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001962 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001963 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001964 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04001965 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07001966 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001967 clone_filter=None,
1968 retry_fetches=2,
1969 retry_sleep_initial_sec=4.0,
1970 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001971 is_sha1 = False
1972 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001973 # The depth should not be used when fetching to a mirror because
1974 # it will result in a shallow repository that cannot be cloned or
1975 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001976 # The repo project should also never be synced with partial depth.
1977 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1978 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001979
Shawn Pearce69e04d82014-01-29 12:48:54 -08001980 if depth:
1981 current_branch_only = True
1982
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001983 if ID_RE.match(self.revisionExpr) is not None:
1984 is_sha1 = True
1985
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001986 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001987 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001988 # this is a tag and its sha1 value should never change
1989 tag_name = self.revisionExpr[len(R_TAGS):]
1990
1991 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06001992 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05001993 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02001994 print('Skipped fetching project %s (already have persistent ref)'
1995 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001996 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001997 if is_sha1 and not depth:
1998 # When syncing a specific commit and --depth is not set:
1999 # * if upstream is explicitly specified and is not a sha1, fetch only
2000 # upstream as users expect only upstream to be fetch.
2001 # Note: The commit might not be in upstream in which case the sync
2002 # will fail.
2003 # * otherwise, fetch all branches to make sure we end up with the
2004 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002005 if self.upstream:
2006 current_branch_only = not ID_RE.match(self.upstream)
2007 else:
2008 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002009
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002010 if not name:
2011 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002012
2013 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002014 remote = self.GetRemote(name)
2015 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002016 ssh_proxy = True
2017
Shawn O. Pearce88443382010-10-08 10:02:09 +02002018 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002019 if alt_dir and 'objects' == os.path.basename(alt_dir):
2020 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002021 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2022 remote = self.GetRemote(name)
2023
David Pursehouse8a68ff92012-09-24 12:15:13 +09002024 all_refs = self.bare_ref.all
2025 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 tmp = set()
2027
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302028 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002029 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002030 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002031 all_refs[r] = ref_id
2032 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002033 continue
2034
David Pursehouse8a68ff92012-09-24 12:15:13 +09002035 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002036 continue
2037
David Pursehouse8a68ff92012-09-24 12:15:13 +09002038 r = 'refs/_alt/%s' % ref_id
2039 all_refs[r] = ref_id
2040 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002041 tmp.add(r)
2042
heping3d7bbc92017-04-12 19:51:47 +08002043 tmp_packed_lines = []
2044 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002045
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302046 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002047 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002048 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002049 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002050 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002051
heping3d7bbc92017-04-12 19:51:47 +08002052 tmp_packed = ''.join(tmp_packed_lines)
2053 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002054 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002055 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002056 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002057
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002058 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002059
Xin Li745be2e2019-06-03 11:24:30 -07002060 if clone_filter:
2061 git_require((2, 19, 0), fail=True, msg='partial clones')
2062 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002063 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002064
Conley Owensf97e8382015-01-21 11:12:46 -08002065 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002066 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002067 else:
2068 # If this repo has shallow objects, then we don't know which refs have
2069 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2070 # do this with projects that don't have shallow objects, since it is less
2071 # efficient.
2072 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2073 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002074
Mike Frysinger4847e052020-02-22 00:07:35 -05002075 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002076 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002077 if not quiet and sys.stdout.isatty():
2078 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002079 if not self.worktree:
2080 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002081 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002082
Mike Frysingere57f1142019-03-18 21:27:54 -04002083 if force_sync:
2084 cmd.append('--force')
2085
David Pursehouse74cfd272015-10-14 10:50:15 +09002086 if prune:
2087 cmd.append('--prune')
2088
Martin Kellye4e94d22017-03-21 16:05:12 -07002089 if submodules:
2090 cmd.append('--recurse-submodules=on-demand')
2091
Kuang-che Wu6856f982019-11-25 12:37:55 +08002092 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002093 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002094 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002095 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002096 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002097 spec.append('tag')
2098 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002099
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302100 if self.manifest.IsMirror and not current_branch_only:
2101 branch = None
2102 else:
2103 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002104 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002105 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002106 # Shallow checkout of a specific commit, fetch from that commit and not
2107 # the heads only as the commit might be deeper in the history.
2108 spec.append(branch)
2109 else:
2110 if is_sha1:
2111 branch = self.upstream
2112 if branch is not None and branch.strip():
2113 if not branch.startswith('refs/'):
2114 branch = R_HEADS + branch
2115 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2116
2117 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2118 # whole repo.
2119 if self.manifest.IsMirror and not spec:
2120 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2121
2122 # If using depth then we should not get all the tags since they may
2123 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002124 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002125 cmd.append('--no-tags')
2126 else:
2127 cmd.append('--tags')
2128 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2129
Conley Owens80b87fe2014-05-09 17:13:44 -07002130 cmd.extend(spec)
2131
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002132 # At least one retry minimum due to git remote prune.
2133 retry_fetches = max(retry_fetches, 2)
2134 retry_cur_sleep = retry_sleep_initial_sec
2135 ok = prune_tried = False
2136 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002137 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002138 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002139 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002140 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002141 ok = True
2142 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002143
2144 # Retry later due to HTTP 429 Too Many Requests.
2145 elif ('error:' in gitcmd.stderr and
2146 'HTTP 429' in gitcmd.stderr):
2147 if not quiet:
2148 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2149 file=sys.stderr)
2150 time.sleep(retry_cur_sleep)
2151 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2152 MAXIMUM_RETRY_SLEEP_SEC)
2153 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2154 RETRY_JITTER_PERCENT))
2155 continue
2156
2157 # If this is not last attempt, try 'git remote prune'.
2158 elif (try_n < retry_fetches - 1 and
2159 'error:' in gitcmd.stderr and
2160 'git remote prune' in gitcmd.stderr and
2161 not prune_tried):
2162 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002163 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002164 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002165 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002166 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002167 break
2168 continue
Brian Harring14a66742012-09-28 20:21:57 -07002169 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002170 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2171 # in sha1 mode, we just tried sync'ing from the upstream field; it
2172 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002173 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002174 elif ret < 0:
2175 # Git died with a signal, exit immediately
2176 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002177 if not verbose:
2178 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002179 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002180
2181 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002182 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002183 if old_packed != '':
2184 _lwrite(packed_refs, old_packed)
2185 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002186 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002187 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002188
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002189 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002190 # We just synced the upstream given branch; verify we
2191 # got what we wanted, else trigger a second run of all
2192 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002193 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002194 # Sync the current branch only with depth set to None.
2195 # We always pass depth=None down to avoid infinite recursion.
2196 return self._RemoteFetch(
2197 name=name, quiet=quiet, verbose=verbose,
2198 current_branch_only=current_branch_only and depth,
2199 initial=False, alt_dir=alt_dir,
2200 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002201
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002202 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002203
Mike Frysingere50b6a72020-02-19 01:45:48 -05002204 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002205 if initial and \
2206 (self.manifest.manifestProject.config.GetString('repo.depth') or
2207 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002208 return False
2209
2210 remote = self.GetRemote(self.remote.name)
2211 bundle_url = remote.url + '/clone.bundle'
2212 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002213 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2214 'persistent-http',
2215 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002216 return False
2217
2218 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2219 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2220
2221 exist_dst = os.path.exists(bundle_dst)
2222 exist_tmp = os.path.exists(bundle_tmp)
2223
2224 if not initial and not exist_dst and not exist_tmp:
2225 return False
2226
2227 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002228 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2229 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002230 if not exist_dst:
2231 return False
2232
2233 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002234 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002235 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002236 if not quiet and sys.stdout.isatty():
2237 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002238 if not self.worktree:
2239 cmd.append('--update-head-ok')
2240 cmd.append(bundle_dst)
2241 for f in remote.fetch:
2242 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002243 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002244
2245 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002246 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002247 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002248 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002249 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002250 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002251
Mike Frysingere50b6a72020-02-19 01:45:48 -05002252 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002253 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002254 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002255
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002256 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002257 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002258 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002259 if os.path.exists(tmpPath):
2260 size = os.stat(tmpPath).st_size
2261 if size >= 1024:
2262 cmd += ['--continue-at', '%d' % (size,)]
2263 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002264 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002265 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002266 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002267 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002268 if proxy:
2269 cmd += ['--proxy', proxy]
2270 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2271 cmd += ['--proxy', os.environ['http_proxy']]
2272 if srcUrl.startswith('persistent-https'):
2273 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2274 elif srcUrl.startswith('persistent-http'):
2275 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002276 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002277
Dave Borowitz137d0132015-01-02 11:12:54 -08002278 if IsTrace():
2279 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002280 if verbose:
2281 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2282 stdout = None if verbose else subprocess.PIPE
2283 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002284 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002285 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002286 except OSError:
2287 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002288
Mike Frysingere50b6a72020-02-19 01:45:48 -05002289 (output, _) = proc.communicate()
2290 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002291
Dave Borowitz137d0132015-01-02 11:12:54 -08002292 if curlret == 22:
2293 # From curl man page:
2294 # 22: HTTP page not retrieved. The requested url was not found or
2295 # returned another error with the HTTP error code being 400 or above.
2296 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002297 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002298 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2299 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002300 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002301 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002302 elif curlret and not verbose and output:
2303 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002304
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002305 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002306 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002307 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002308 return True
2309 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002310 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002311 return False
2312 else:
2313 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002314
Kris Giesingc8d882a2014-12-23 13:02:32 -08002315 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002316 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002317 with open(path, 'rb') as f:
2318 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002319 return True
2320 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002321 if not quiet:
2322 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002323 return False
2324 except OSError:
2325 return False
2326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327 def _Checkout(self, rev, quiet=False):
2328 cmd = ['checkout']
2329 if quiet:
2330 cmd.append('-q')
2331 cmd.append(rev)
2332 cmd.append('--')
2333 if GitCommand(self, cmd).Wait() != 0:
2334 if self._allrefs:
2335 raise GitError('%s checkout %s ' % (self.name, rev))
2336
Mike Frysinger915fda12020-03-22 12:15:20 -04002337 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002338 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002339 if ffonly:
2340 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002341 if record_origin:
2342 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002343 cmd.append(rev)
2344 cmd.append('--')
2345 if GitCommand(self, cmd).Wait() != 0:
2346 if self._allrefs:
2347 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2348
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302349 def _LsRemote(self, refs):
2350 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302351 p = GitCommand(self, cmd, capture_stdout=True)
2352 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002353 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302354 return None
2355
Anthony King7bdac712014-07-16 12:56:40 +01002356 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002357 cmd = ['revert']
2358 cmd.append('--no-edit')
2359 cmd.append(rev)
2360 cmd.append('--')
2361 if GitCommand(self, cmd).Wait() != 0:
2362 if self._allrefs:
2363 raise GitError('%s revert %s ' % (self.name, rev))
2364
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002365 def _ResetHard(self, rev, quiet=True):
2366 cmd = ['reset', '--hard']
2367 if quiet:
2368 cmd.append('-q')
2369 cmd.append(rev)
2370 if GitCommand(self, cmd).Wait() != 0:
2371 raise GitError('%s reset --hard %s ' % (self.name, rev))
2372
Martin Kellye4e94d22017-03-21 16:05:12 -07002373 def _SyncSubmodules(self, quiet=True):
2374 cmd = ['submodule', 'update', '--init', '--recursive']
2375 if quiet:
2376 cmd.append('-q')
2377 if GitCommand(self, cmd).Wait() != 0:
2378 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2379
Anthony King7bdac712014-07-16 12:56:40 +01002380 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002381 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002382 if onto is not None:
2383 cmd.extend(['--onto', onto])
2384 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002385 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002386 raise GitError('%s rebase %s ' % (self.name, upstream))
2387
Pierre Tardy3d125942012-05-04 12:18:12 +02002388 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002389 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002390 if ffonly:
2391 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002392 if GitCommand(self, cmd).Wait() != 0:
2393 raise GitError('%s merge %s ' % (self.name, head))
2394
David Pursehousee8ace262020-02-13 12:41:15 +09002395 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002396 init_git_dir = not os.path.exists(self.gitdir)
2397 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002398 try:
2399 # Initialize the bare repository, which contains all of the objects.
2400 if init_obj_dir:
2401 os.makedirs(self.objdir)
2402 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002403
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002404 if self.use_git_worktrees:
2405 # Set up the m/ space to point to the worktree-specific ref space.
2406 # We'll update the worktree-specific ref space on each checkout.
2407 if self.manifest.branch:
2408 self.bare_git.symbolic_ref(
2409 '-m', 'redirecting to worktree scope',
2410 R_M + self.manifest.branch,
2411 R_WORKTREE_M + self.manifest.branch)
2412
2413 # Enable per-worktree config file support if possible. This is more a
2414 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002415 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002416 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002417
Kevin Degib1a07b82015-07-27 13:33:43 -06002418 # If we have a separate directory to hold refs, initialize it as well.
2419 if self.objdir != self.gitdir:
2420 if init_git_dir:
2421 os.makedirs(self.gitdir)
2422
2423 if init_obj_dir or init_git_dir:
2424 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2425 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002426 try:
2427 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2428 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002429 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002430 print("Retrying clone after deleting %s" %
2431 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002432 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002433 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2434 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002435 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002436 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002437 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2438 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002439 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002440 raise e
2441 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002442
Kevin Degi384b3c52014-10-16 16:02:58 -06002443 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002444 mp = self.manifest.manifestProject
2445 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002446
Kevin Degib1a07b82015-07-27 13:33:43 -06002447 if ref_dir or mirror_git:
2448 if not mirror_git:
2449 mirror_git = os.path.join(ref_dir, self.name + '.git')
2450 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2451 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002452 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2453 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002454
Kevin Degib1a07b82015-07-27 13:33:43 -06002455 if os.path.exists(mirror_git):
2456 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002457 elif os.path.exists(repo_git):
2458 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002459 elif os.path.exists(worktrees_git):
2460 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002461 else:
2462 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002463
Kevin Degib1a07b82015-07-27 13:33:43 -06002464 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002465 if not os.path.isabs(ref_dir):
2466 # The alternate directory is relative to the object database.
2467 ref_dir = os.path.relpath(ref_dir,
2468 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002469 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2470 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002471
David Pursehousee8ace262020-02-13 12:41:15 +09002472 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002473
2474 m = self.manifest.manifestProject.config
2475 for key in ['user.name', 'user.email']:
2476 if m.Has(key, include_defaults=False):
2477 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002478 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002479 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002480 if self.manifest.IsMirror:
2481 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002482 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002483 self.config.SetString('core.bare', None)
2484 except Exception:
2485 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002486 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002487 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002488 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002489 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002490
David Pursehousee8ace262020-02-13 12:41:15 +09002491 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002492 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002493 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002494
David Pursehousee8ace262020-02-13 12:41:15 +09002495 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002496 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002497 if not os.path.exists(hooks):
2498 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002499 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002500 name = os.path.basename(stock_hook)
2501
Victor Boivie65e0f352011-04-18 11:23:29 +02002502 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002503 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002504 # Don't install a Gerrit Code Review hook if this
2505 # project does not appear to use it for reviews.
2506 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002507 # Since the manifest project is one of those, but also
2508 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002509 continue
2510
2511 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002512 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002513 continue
2514 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002515 # If the files are the same, we'll leave it alone. We create symlinks
2516 # below by default but fallback to hardlinks if the OS blocks them.
2517 # So if we're here, it's probably because we made a hardlink below.
2518 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002519 if not quiet:
2520 _warn("%s: Not replacing locally modified %s hook",
2521 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002522 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002523 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002524 platform_utils.symlink(
2525 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002526 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002527 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002528 try:
2529 os.link(stock_hook, dst)
2530 except OSError:
2531 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002532 else:
2533 raise
2534
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002535 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002536 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002538 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002539 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002540 remote.review = self.remote.review
2541 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002542
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002543 if self.worktree:
2544 remote.ResetFetch(mirror=False)
2545 else:
2546 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002547 remote.Save()
2548
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002549 def _InitMRef(self):
2550 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002551 if self.use_git_worktrees:
2552 # We can't update this ref with git worktrees until it exists.
2553 # We'll wait until the initial checkout to set it.
2554 if not os.path.exists(self.worktree):
2555 return
2556
2557 base = R_WORKTREE_M
2558 active_git = self.work_git
2559 else:
2560 base = R_M
2561 active_git = self.bare_git
2562
2563 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002564
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002565 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002566 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002567
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002568 def _InitAnyMRef(self, ref, active_git):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002569 cur = self.bare_ref.symref(ref)
2570
2571 if self.revisionId:
2572 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2573 msg = 'manifest set to %s' % self.revisionId
2574 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002575 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002576 else:
2577 remote = self.GetRemote(self.remote.name)
2578 dst = remote.ToLocal(self.revisionExpr)
2579 if cur != dst:
2580 msg = 'manifest set to %s' % self.revisionExpr
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002581 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002582
Kevin Degi384b3c52014-10-16 16:02:58 -06002583 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002584 # Git worktrees don't use symlinks to share at all.
2585 if self.use_git_worktrees:
2586 return
2587
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002588 symlink_files = self.shareable_files[:]
2589 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002590 if share_refs:
2591 symlink_files += self.working_tree_files
2592 symlink_dirs += self.working_tree_dirs
2593 to_symlink = symlink_files + symlink_dirs
2594 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002595 # Try to self-heal a bit in simple cases.
2596 dst_path = os.path.join(destdir, name)
2597 src_path = os.path.join(srcdir, name)
2598
2599 if name in self.working_tree_dirs:
2600 # If the dir is missing under .repo/projects/, create it.
2601 if not os.path.exists(src_path):
2602 os.makedirs(src_path)
2603
2604 elif name in self.working_tree_files:
2605 # If it's a file under the checkout .git/ and the .repo/projects/ has
2606 # nothing, move the file under the .repo/projects/ tree.
2607 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2608 platform_utils.rename(dst_path, src_path)
2609
2610 # If the path exists under the .repo/projects/ and there's no symlink
2611 # under the checkout .git/, recreate the symlink.
2612 if name in self.working_tree_dirs or name in self.working_tree_files:
2613 if os.path.exists(src_path) and not os.path.exists(dst_path):
2614 platform_utils.symlink(
2615 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2616
2617 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002618 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002619 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002620 # Fail if the links are pointing to the wrong place
2621 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002622 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002623 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002624 'work tree. If you\'re comfortable with the '
2625 'possibility of losing the work tree\'s git metadata,'
2626 ' use `repo sync --force-sync {0}` to '
2627 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002628
David James8d201162013-10-11 17:03:19 -07002629 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2630 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2631
2632 Args:
2633 gitdir: The bare git repository. Must already be initialized.
2634 dotgit: The repository you would like to initialize.
2635 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2636 Only one work tree can store refs under a given |gitdir|.
2637 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2638 This saves you the effort of initializing |dotgit| yourself.
2639 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002640 symlink_files = self.shareable_files[:]
2641 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002642 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002643 symlink_files += self.working_tree_files
2644 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002645 to_symlink = symlink_files + symlink_dirs
2646
2647 to_copy = []
2648 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002649 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002650
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002651 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002652 for name in set(to_copy).union(to_symlink):
2653 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002654 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002655 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002656
Kevin Degi384b3c52014-10-16 16:02:58 -06002657 if os.path.lexists(dst):
2658 continue
David James8d201162013-10-11 17:03:19 -07002659
2660 # If the source dir doesn't exist, create an empty dir.
2661 if name in symlink_dirs and not os.path.lexists(src):
2662 os.makedirs(src)
2663
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002664 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002665 platform_utils.symlink(
2666 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002667 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002668 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002669 shutil.copytree(src, dst)
2670 elif os.path.isfile(src):
2671 shutil.copy(src, dst)
2672
Conley Owens80b87fe2014-05-09 17:13:44 -07002673 # If the source file doesn't exist, ensure the destination
2674 # file doesn't either.
2675 if name in symlink_files and not os.path.lexists(src):
2676 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002677 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002678 except OSError:
2679 pass
2680
David James8d201162013-10-11 17:03:19 -07002681 except OSError as e:
2682 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002683 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002684 else:
2685 raise
2686
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002687 def _InitGitWorktree(self):
2688 """Init the project using git worktrees."""
2689 self.bare_git.worktree('prune')
2690 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2691 self.worktree, self.GetRevisionId())
2692
2693 # Rewrite the internal state files to use relative paths between the
2694 # checkouts & worktrees.
2695 dotgit = os.path.join(self.worktree, '.git')
2696 with open(dotgit, 'r') as fp:
2697 # Figure out the checkout->worktree path.
2698 setting = fp.read()
2699 assert setting.startswith('gitdir:')
2700 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002701 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2702 # of file permissions. Delete it and recreate it from scratch to avoid.
2703 platform_utils.remove(dotgit)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002704 # Use relative path from checkout->worktree.
2705 with open(dotgit, 'w') as fp:
2706 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2707 file=fp)
2708 # Use relative path from worktree->checkout.
2709 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
2710 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