blob: a9bca6dbbe899a786cf012ff010d14477d2ef026 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080015import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070016import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070017import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020025import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070028
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070030from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070031from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
32 ID_RE
Remy Bohmer16c13282020-09-10 10:38:04 +020033from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040034from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080035from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070036import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040037import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040038from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050040from 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 -070041
David Pursehouse59bbb582013-05-17 10:49:33 +090042from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040043if is_python3():
44 import urllib.parse
45else:
46 import imp
47 import urlparse
48 urllib = imp.new_module('urllib')
49 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090050 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053051
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070052
George Engelbrecht9bc283e2020-04-02 12:36:09 -060053# Maximum sleep time allowed during retries.
54MAXIMUM_RETRY_SLEEP_SEC = 3600.0
55# +-10% random jitter is added to each Fetches retry sleep duration.
56RETRY_JITTER_PERCENT = 0.1
57
58
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059def _lwrite(path, content):
60 lock = '%s.lock' % path
61
Remy Bohmer169b0212020-11-21 10:57:52 +010062 # Maintain Unix line endings on all OS's to match git behavior.
63 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070064 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070065
66 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070067 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080069 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070070 raise
71
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070072
Shawn O. Pearce48244782009-04-16 08:25:57 -070073def _error(fmt, *args):
74 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070075 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070076
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070077
David Pursehousef33929d2015-08-24 14:39:14 +090078def _warn(fmt, *args):
79 msg = fmt % args
80 print('warn: %s' % msg, file=sys.stderr)
81
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070082
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070083def not_rev(r):
84 return '^' + r
85
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070086
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080087def sq(r):
88 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089
David Pursehouse819827a2020-02-12 15:20:19 +090090
Jonathan Nieder93719792015-03-17 11:29:58 -070091_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070092
93
Jonathan Nieder93719792015-03-17 11:29:58 -070094def _ProjectHooks():
95 """List the hooks present in the 'hooks' directory.
96
97 These hooks are project hooks and are copied to the '.git/hooks' directory
98 of all subprojects.
99
100 This function caches the list of hooks (based on the contents of the
101 'repo/hooks' directory) on the first call.
102
103 Returns:
104 A list of absolute paths to all of the files in the hooks directory.
105 """
106 global _project_hook_list
107 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700108 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700110 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700111 return _project_hook_list
112
113
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700114class DownloadedChange(object):
115 _commit_cache = None
116
117 def __init__(self, project, base, change_id, ps_id, commit):
118 self.project = project
119 self.base = base
120 self.change_id = change_id
121 self.ps_id = ps_id
122 self.commit = commit
123
124 @property
125 def commits(self):
126 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700127 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
128 '--abbrev-commit',
129 '--pretty=oneline',
130 '--reverse',
131 '--date-order',
132 not_rev(self.base),
133 self.commit,
134 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700135 return self._commit_cache
136
137
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700138class ReviewableBranch(object):
139 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400140 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700141
142 def __init__(self, project, branch, base):
143 self.project = project
144 self.branch = branch
145 self.base = base
146
147 @property
148 def name(self):
149 return self.branch.name
150
151 @property
152 def commits(self):
153 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400154 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
155 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
156 try:
157 self._commit_cache = self.project.bare_git.rev_list(*args)
158 except GitError:
159 # We weren't able to probe the commits for this branch. Was it tracking
160 # a branch that no longer exists? If so, return no commits. Otherwise,
161 # rethrow the error as we don't know what's going on.
162 if self.base_exists:
163 raise
164
165 self._commit_cache = []
166
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167 return self._commit_cache
168
169 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800170 def unabbrev_commits(self):
171 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700172 for commit in self.project.bare_git.rev_list(not_rev(self.base),
173 R_HEADS + self.name,
174 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800175 r[commit[0:8]] = commit
176 return r
177
178 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700180 return self.project.bare_git.log('--pretty=format:%cd',
181 '-n', '1',
182 R_HEADS + self.name,
183 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184
Mike Frysinger6da17752019-09-11 18:43:17 -0400185 @property
186 def base_exists(self):
187 """Whether the branch we're tracking exists.
188
189 Normally it should, but sometimes branches we track can get deleted.
190 """
191 if self._base_exists is None:
192 try:
193 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
194 # If we're still here, the base branch exists.
195 self._base_exists = True
196 except GitError:
197 # If we failed to verify, the base branch doesn't exist.
198 self._base_exists = False
199
200 return self._base_exists
201
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500203 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700204 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500205 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500206 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700208 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200209 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200210 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800211 validate_certs=True,
212 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500213 self.project.UploadForReview(branch=self.name,
214 people=people,
215 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700216 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500217 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500218 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700220 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200221 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200222 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800223 validate_certs=validate_certs,
224 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700226 def GetPublishedRefs(self):
227 refs = {}
228 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229 self.branch.remote.SshReviewUrl(self.project.UserEmail),
230 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700231 for line in output.split('\n'):
232 try:
233 (sha, ref) = line.split()
234 refs[sha] = ref
235 except ValueError:
236 pass
237
238 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700242
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 def __init__(self, config):
244 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100245 self.project = self.printer('header', attr='bold')
246 self.branch = self.printer('header', attr='bold')
247 self.nobranch = self.printer('nobranch', fg='red')
248 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
Anthony King7bdac712014-07-16 12:56:40 +0100250 self.added = self.printer('added', fg='green')
251 self.changed = self.printer('changed', fg='red')
252 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
254
255class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700256
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257 def __init__(self, config):
258 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100259 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400260 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
Anthony King7bdac712014-07-16 12:56:40 +0100263class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
James W. Mills24c13082012-04-12 15:04:13 -0500265 def __init__(self, name, value, keep):
266 self.name = name
267 self.value = value
268 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700270
Mike Frysingere6a202f2019-08-02 15:57:57 -0400271def _SafeExpandPath(base, subpath, skipfinal=False):
272 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700273
Mike Frysingere6a202f2019-08-02 15:57:57 -0400274 We make sure no intermediate symlinks are traversed, and that the final path
275 is not a special file (e.g. not a socket or fifo).
276
277 NB: We rely on a number of paths already being filtered out while parsing the
278 manifest. See the validation logic in manifest_xml.py for more details.
279 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500280 # Split up the path by its components. We can't use os.path.sep exclusively
281 # as some platforms (like Windows) will convert / to \ and that bypasses all
282 # our constructed logic here. Especially since manifest authors only use
283 # / in their paths.
284 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
285 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400286 if skipfinal:
287 # Whether the caller handles the final component itself.
288 finalpart = components.pop()
289
290 path = base
291 for part in components:
292 if part in {'.', '..'}:
293 raise ManifestInvalidPathError(
294 '%s: "%s" not allowed in paths' % (subpath, part))
295
296 path = os.path.join(path, part)
297 if platform_utils.islink(path):
298 raise ManifestInvalidPathError(
299 '%s: traversing symlinks not allow' % (path,))
300
301 if os.path.exists(path):
302 if not os.path.isfile(path) and not platform_utils.isdir(path):
303 raise ManifestInvalidPathError(
304 '%s: only regular files & directories allowed' % (path,))
305
306 if skipfinal:
307 path = os.path.join(path, finalpart)
308
309 return path
310
311
312class _CopyFile(object):
313 """Container for <copyfile> manifest element."""
314
315 def __init__(self, git_worktree, src, topdir, dest):
316 """Register a <copyfile> request.
317
318 Args:
319 git_worktree: Absolute path to the git project checkout.
320 src: Relative path under |git_worktree| of file to read.
321 topdir: Absolute path to the top of the repo client checkout.
322 dest: Relative path under |topdir| of file to write.
323 """
324 self.git_worktree = git_worktree
325 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700326 self.src = src
327 self.dest = dest
328
329 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400330 src = _SafeExpandPath(self.git_worktree, self.src)
331 dest = _SafeExpandPath(self.topdir, self.dest)
332
333 if platform_utils.isdir(src):
334 raise ManifestInvalidPathError(
335 '%s: copying from directory not supported' % (self.src,))
336 if platform_utils.isdir(dest):
337 raise ManifestInvalidPathError(
338 '%s: copying to directory not allowed' % (self.dest,))
339
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340 # copy file if it does not exist or is out of date
341 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
342 try:
343 # remove existing file first, since it might be read-only
344 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800345 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400346 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200347 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700348 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200349 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350 shutil.copy(src, dest)
351 # make the file read-only
352 mode = os.stat(dest)[stat.ST_MODE]
353 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
354 os.chmod(dest, mode)
355 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700356 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700358
Anthony King7bdac712014-07-16 12:56:40 +0100359class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400360 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700361
Mike Frysingere6a202f2019-08-02 15:57:57 -0400362 def __init__(self, git_worktree, src, topdir, dest):
363 """Register a <linkfile> request.
364
365 Args:
366 git_worktree: Absolute path to the git project checkout.
367 src: Target of symlink relative to path under |git_worktree|.
368 topdir: Absolute path to the top of the repo client checkout.
369 dest: Relative path under |topdir| of symlink to create.
370 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700371 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400372 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500373 self.src = src
374 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500375
Wink Saville4c426ef2015-06-03 08:05:17 -0700376 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700378 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 try:
380 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800381 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800382 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500383 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700384 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700385 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500386 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700387 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500388 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700389 _error('Cannot link file %s to %s', relSrc, absDest)
390
391 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400392 """Link the self.src & self.dest paths.
393
394 Handles wild cards on the src linking all of the files in the source in to
395 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700396 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500397 # Some people use src="." to create stable links to projects. Lets allow
398 # that but reject all other uses of "." to keep things simple.
399 if self.src == '.':
400 src = self.git_worktree
401 else:
402 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300404 if not glob.has_magic(src):
405 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400406 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
407 # dest & src are absolute paths at this point. Make sure the target of
408 # the symlink is relative in the context of the repo client checkout.
409 relpath = os.path.relpath(src, os.path.dirname(dest))
410 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400412 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300413 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400414 if os.path.exists(dest) and not platform_utils.isdir(dest):
415 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700416 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400417 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700418 # Create a releative path from source dir to destination dir
419 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700421
422 # Get the source file name
423 srcFile = os.path.basename(absSrcFile)
424
425 # Now form the final full paths to srcFile. They will be
426 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400427 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700428 relSrc = os.path.join(relSrcDir, srcFile)
429 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500430
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700431
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700432class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700433
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700434 def __init__(self,
435 name,
Anthony King7bdac712014-07-16 12:56:40 +0100436 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700437 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100438 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700439 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700440 orig_name=None,
441 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700442 self.name = name
443 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700444 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700445 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100446 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700447 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700448 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700449
450class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600451 # These objects can be shared between several working trees.
452 shareable_files = ['description', 'info']
453 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
454 # These objects can only be used by a single working tree.
455 working_tree_files = ['config', 'packed-refs', 'shallow']
456 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700457
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700458 def __init__(self,
459 manifest,
460 name,
461 remote,
462 gitdir,
David James8d201162013-10-11 17:03:19 -0700463 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700464 worktree,
465 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700466 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800467 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100468 rebase=True,
469 groups=None,
470 sync_c=False,
471 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900472 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100473 clone_depth=None,
474 upstream=None,
475 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500476 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100477 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900478 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700479 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600480 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700481 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800482 """Init a Project object.
483
484 Args:
485 manifest: The XmlManifest object.
486 name: The `name` attribute of manifest.xml's project element.
487 remote: RemoteSpec object specifying its remote's properties.
488 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700489 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800490 worktree: Absolute path of git working tree.
491 relpath: Relative path of git working tree to repo's top directory.
492 revisionExpr: The `revision` attribute of manifest.xml's project element.
493 revisionId: git commit id for checking out.
494 rebase: The `rebase` attribute of manifest.xml's project element.
495 groups: The `groups` attribute of manifest.xml's project element.
496 sync_c: The `sync-c` attribute of manifest.xml's project element.
497 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900498 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800499 upstream: The `upstream` attribute of manifest.xml's project element.
500 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500501 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800502 is_derived: False if the project was explicitly defined in the manifest;
503 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400504 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900505 optimized_fetch: If True, when a project is set to a sha1 revision, only
506 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600507 retry_fetches: Retry remote fetches n times upon receiving transient error
508 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700509 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800510 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400511 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.name = name
513 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800514 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700515 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800516 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700517 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800518 else:
519 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700521 self.revisionExpr = revisionExpr
522
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700523 if revisionId is None \
524 and revisionExpr \
525 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700526 self.revisionId = revisionExpr
527 else:
528 self.revisionId = revisionId
529
Mike Pontillod3153822012-02-28 11:53:24 -0800530 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700531 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700532 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800533 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900534 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900535 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700536 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500538 # NB: Do not use this setting in __init__ to change behavior so that the
539 # manifest.git checkout can inspect & change it after instantiating. See
540 # the XmlManifest init code for more info.
541 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800542 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900543 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600544 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800545 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800546
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700547 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500549 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500550 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700551 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400552 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700553
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800554 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700555 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800556 else:
557 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700558 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700559 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700560 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400561 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700562 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700563
Doug Anderson37282b42011-03-04 11:54:18 -0800564 # This will be filled in if a project is later identified to be the
565 # project containing repo hooks.
566 self.enabled_repo_hooks = []
567
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700568 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800569 def Derived(self):
570 return self.is_derived
571
572 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700573 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700574 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575
576 @property
577 def CurrentBranch(self):
578 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400579
580 The branch name omits the 'refs/heads/' prefix.
581 None is returned if the project is on a detached HEAD, or if the work_git is
582 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400584 try:
585 b = self.work_git.GetHead()
586 except NoManifestException:
587 # If the local checkout is in a bad state, don't barf. Let the callers
588 # process this like the head is unreadable.
589 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700590 if b.startswith(R_HEADS):
591 return b[len(R_HEADS):]
592 return None
593
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700594 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500595 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
596 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
597 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200598
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599 def IsDirty(self, consider_untracked=True):
600 """Is the working directory modified in some way?
601 """
602 self.work_git.update_index('-q',
603 '--unmerged',
604 '--ignore-missing',
605 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900606 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700607 return True
608 if self.work_git.DiffZ('diff-files'):
609 return True
610 if consider_untracked and self.work_git.LsOthers():
611 return True
612 return False
613
614 _userident_name = None
615 _userident_email = None
616
617 @property
618 def UserName(self):
619 """Obtain the user's personal name.
620 """
621 if self._userident_name is None:
622 self._LoadUserIdentity()
623 return self._userident_name
624
625 @property
626 def UserEmail(self):
627 """Obtain the user's email address. This is very likely
628 to be their Gerrit login.
629 """
630 if self._userident_email is None:
631 self._LoadUserIdentity()
632 return self._userident_email
633
634 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900635 u = self.bare_git.var('GIT_COMMITTER_IDENT')
636 m = re.compile("^(.*) <([^>]*)> ").match(u)
637 if m:
638 self._userident_name = m.group(1)
639 self._userident_email = m.group(2)
640 else:
641 self._userident_name = ''
642 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700643
644 def GetRemote(self, name):
645 """Get the configuration for a single remote.
646 """
647 return self.config.GetRemote(name)
648
649 def GetBranch(self, name):
650 """Get the configuration for a single branch.
651 """
652 return self.config.GetBranch(name)
653
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654 def GetBranches(self):
655 """Get all existing local branches.
656 """
657 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900658 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700659 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700660
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530661 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700662 if name.startswith(R_HEADS):
663 name = name[len(R_HEADS):]
664 b = self.GetBranch(name)
665 b.current = name == current
666 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700668 heads[name] = b
669
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530670 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700671 if name.startswith(R_PUB):
672 name = name[len(R_PUB):]
673 b = heads.get(name)
674 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900675 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700676
677 return heads
678
Colin Cross5acde752012-03-28 20:15:45 -0700679 def MatchesGroups(self, manifest_groups):
680 """Returns true if the manifest groups specified at init should cause
681 this project to be synced.
682 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700683 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700684
685 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700686 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700687 manifest_groups: "-group1,group2"
688 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500689
690 The special manifest group "default" will match any project that
691 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700692 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500693 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700694 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700695 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500696 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700697
Conley Owens971de8e2012-04-16 10:36:08 -0700698 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700699 for group in expanded_manifest_groups:
700 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700701 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700702 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700703 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700704
Conley Owens971de8e2012-04-16 10:36:08 -0700705 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700707# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700708 def UncommitedFiles(self, get_all=True):
709 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700710
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700711 Args:
712 get_all: a boolean, if True - get information about all different
713 uncommitted files. If False - return as soon as any kind of
714 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500715 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700716 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500717 self.work_git.update_index('-q',
718 '--unmerged',
719 '--ignore-missing',
720 '--refresh')
721 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700722 details.append("rebase in progress")
723 if not get_all:
724 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500725
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700726 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
727 if changes:
728 details.extend(changes)
729 if not get_all:
730 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500731
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700732 changes = self.work_git.DiffZ('diff-files').keys()
733 if changes:
734 details.extend(changes)
735 if not get_all:
736 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500737
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700738 changes = self.work_git.LsOthers()
739 if changes:
740 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500741
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700742 return details
743
744 def HasChanges(self):
745 """Returns true if there are uncommitted changes.
746 """
747 if self.UncommitedFiles(get_all=False):
748 return True
749 else:
750 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500751
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600752 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700753 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200754
755 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200756 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600757 quiet: If True then only print the project name. Do not print
758 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700760 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700761 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200762 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700763 print(file=output_redir)
764 print('project %s/' % self.relpath, file=output_redir)
765 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766 return
767
768 self.work_git.update_index('-q',
769 '--unmerged',
770 '--ignore-missing',
771 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700772 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700773 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
774 df = self.work_git.DiffZ('diff-files')
775 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100776 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700777 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778
779 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700780 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200781 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700782 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600784 if quiet:
785 out.nl()
786 return 'DIRTY'
787
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788 branch = self.CurrentBranch
789 if branch is None:
790 out.nobranch('(*** NO BRANCH ***)')
791 else:
792 out.branch('branch %s', branch)
793 out.nl()
794
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700795 if rb:
796 out.important('prior sync failed; rebase still in progress')
797 out.nl()
798
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799 paths = list()
800 paths.extend(di.keys())
801 paths.extend(df.keys())
802 paths.extend(do)
803
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530804 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900805 try:
806 i = di[p]
807 except KeyError:
808 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900810 try:
811 f = df[p]
812 except KeyError:
813 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200814
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900815 if i:
816 i_status = i.status.upper()
817 else:
818 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700819
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900820 if f:
821 f_status = f.status.lower()
822 else:
823 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700824
825 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800826 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700827 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 else:
829 line = ' %s%s\t%s' % (i_status, f_status, p)
830
831 if i and not f:
832 out.added('%s', line)
833 elif (i and f) or (not i and f):
834 out.changed('%s', line)
835 elif not i and not f:
836 out.untracked('%s', line)
837 else:
838 out.write('%s', line)
839 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200840
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700841 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700842
pelyad67872d2012-03-28 14:49:58 +0300843 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700844 """Prints the status of the repository to stdout.
845 """
846 out = DiffColoring(self.config)
847 cmd = ['diff']
848 if out.is_on:
849 cmd.append('--color')
850 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300851 if absolute_paths:
852 cmd.append('--src-prefix=a/%s/' % self.relpath)
853 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400855 try:
856 p = GitCommand(self,
857 cmd,
858 capture_stdout=True,
859 capture_stderr=True)
860 except GitError as e:
861 out.nl()
862 out.project('project %s/' % self.relpath)
863 out.nl()
864 out.fail('%s', str(e))
865 out.nl()
866 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700867 has_diff = False
868 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -0400869 if not hasattr(line, 'encode'):
870 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 if not has_diff:
872 out.nl()
873 out.project('project %s/' % self.relpath)
874 out.nl()
875 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -0700876 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400877 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700878
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700879# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900880 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700881 """Was the branch published (uploaded) for code review?
882 If so, returns the SHA-1 hash of the last published
883 state for the branch.
884 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700885 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900886 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700887 try:
888 return self.bare_git.rev_parse(key)
889 except GitError:
890 return None
891 else:
892 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900893 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700894 except KeyError:
895 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896
David Pursehouse8a68ff92012-09-24 12:15:13 +0900897 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 """Prunes any stale published refs.
899 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900900 if all_refs is None:
901 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 heads = set()
903 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530904 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 if name.startswith(R_HEADS):
906 heads.add(name)
907 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900908 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700909
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530910 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700911 n = name[len(R_PUB):]
912 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900913 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700915 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 """List any branches which can be uploaded for review.
917 """
918 heads = {}
919 pubed = {}
920
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530921 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900923 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900925 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
927 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530928 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900929 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700930 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700931 if selected_branch and branch != selected_branch:
932 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800934 rb = self.GetUploadableBranch(branch)
935 if rb:
936 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700937 return ready
938
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800939 def GetUploadableBranch(self, branch_name):
940 """Get a single uploadable branch, or None.
941 """
942 branch = self.GetBranch(branch_name)
943 base = branch.LocalMerge
944 if branch.LocalMerge:
945 rb = ReviewableBranch(self, branch, base)
946 if rb.commits:
947 return rb
948 return None
949
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700950 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100951 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500952 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700953 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500954 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500955 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200956 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700957 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200958 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200959 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800960 validate_certs=True,
961 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 """Uploads the named branch for code review.
963 """
964 if branch is None:
965 branch = self.CurrentBranch
966 if branch is None:
967 raise GitError('not currently on a branch')
968
969 branch = self.GetBranch(branch)
970 if not branch.LocalMerge:
971 raise GitError('branch %s does not track a remote' % branch.name)
972 if not branch.remote.review:
973 raise GitError('remote %s has no review url' % branch.remote.name)
974
Bryan Jacobsf609f912013-05-06 13:36:24 -0400975 if dest_branch is None:
976 dest_branch = self.dest_branch
977 if dest_branch is None:
978 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 if not dest_branch.startswith(R_HEADS):
980 dest_branch = R_HEADS + dest_branch
981
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800982 if not branch.remote.projectname:
983 branch.remote.projectname = self.name
984 branch.remote.Save()
985
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200986 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800987 if url is None:
988 raise UploadError('review not configured')
989 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500990 if dryrun:
991 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800992
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800993 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800994 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700995
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800996 for push_option in (push_options or []):
997 cmd.append('-o')
998 cmd.append(push_option)
999
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001000 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001001
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001002 if dest_branch.startswith(R_HEADS):
1003 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001004
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001005 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001006 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001007 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001008 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001009 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001010 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001011
David Pursehousef25a3702018-11-14 19:01:22 -08001012 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001013 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001014 if notify:
1015 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001016 if private:
1017 opts += ['private']
1018 if wip:
1019 opts += ['wip']
1020 if opts:
1021 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001022 cmd.append(ref_spec)
1023
Anthony King7bdac712014-07-16 12:56:40 +01001024 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001025 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026
Mike Frysingerd7f86832020-11-19 19:18:46 -05001027 if not dryrun:
1028 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1029 self.bare_git.UpdateRef(R_PUB + branch.name,
1030 R_HEADS + branch.name,
1031 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001032
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001033# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001034 def _ExtractArchive(self, tarpath, path=None):
1035 """Extract the given tar on its current location
1036
1037 Args:
1038 - tarpath: The path to the actual tar file
1039
1040 """
1041 try:
1042 with tarfile.open(tarpath, 'r') as tar:
1043 tar.extractall(path=path)
1044 return True
1045 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001046 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001047 return False
1048
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001049 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001050 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001051 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001052 is_new=None,
1053 current_branch_only=False,
1054 force_sync=False,
1055 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001056 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001057 archive=False,
1058 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001059 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001060 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001061 submodules=False,
1062 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063 """Perform only the network IO portion of the sync process.
1064 Local working directory/branch state is not affected.
1065 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001066 if archive and not isinstance(self, MetaProject):
1067 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001068 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001069 return False
1070
1071 name = self.relpath.replace('\\', '/')
1072 name = name.replace('/', '_')
1073 tarpath = '%s.tar' % name
1074 topdir = self.manifest.topdir
1075
1076 try:
1077 self._FetchArchive(tarpath, cwd=topdir)
1078 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001079 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001080 return False
1081
1082 # From now on, we only need absolute tarpath
1083 tarpath = os.path.join(topdir, tarpath)
1084
1085 if not self._ExtractArchive(tarpath, path=topdir):
1086 return False
1087 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001088 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001089 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001090 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001091 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001092 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001093 if is_new is None:
1094 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001095 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001096 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001097 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001098 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001099 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001100
1101 if is_new:
1102 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1103 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001104 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001105 # This works for both absolute and relative alternate directories.
1106 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001107 except IOError:
1108 alt_dir = None
1109 else:
1110 alt_dir = None
1111
Mike Frysingere50b6a72020-02-19 01:45:48 -05001112 if (clone_bundle
1113 and alt_dir is None
1114 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001115 is_new = False
1116
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001117 if not current_branch_only:
1118 if self.sync_c:
1119 current_branch_only = True
1120 elif not self.manifest._loaded:
1121 # Manifest cannot check defaults until it syncs.
1122 current_branch_only = False
1123 elif self.manifest.default.sync_c:
1124 current_branch_only = True
1125
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001126 if not self.sync_tags:
1127 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001128
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001129 if self.clone_depth:
1130 depth = self.clone_depth
1131 else:
1132 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1133
Mike Frysinger521d01b2020-02-17 01:51:49 -05001134 # See if we can skip the network fetch entirely.
1135 if not (optimized_fetch and
1136 (ID_RE.match(self.revisionExpr) and
1137 self._CheckForImmutableRevision())):
1138 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001139 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1140 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001141 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001142 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001143 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001144 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001145
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001146 mp = self.manifest.manifestProject
1147 dissociate = mp.config.GetBoolean('repo.dissociate')
1148 if dissociate:
1149 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1150 if os.path.exists(alternates_file):
1151 cmd = ['repack', '-a', '-d']
1152 if GitCommand(self, cmd, bare=True).Wait() != 0:
1153 return False
1154 platform_utils.remove(alternates_file)
1155
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001156 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001157 self._InitMRef()
1158 else:
1159 self._InitMirrorHead()
1160 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001161 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001162 except OSError:
1163 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001164 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001165
1166 def PostRepoUpgrade(self):
1167 self._InitHooks()
1168
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001169 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001170 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001171 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001172 for copyfile in self.copyfiles:
1173 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001174 for linkfile in self.linkfiles:
1175 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001176
Julien Camperguedd654222014-01-09 16:21:37 +01001177 def GetCommitRevisionId(self):
1178 """Get revisionId of a commit.
1179
1180 Use this method instead of GetRevisionId to get the id of the commit rather
1181 than the id of the current git object (for example, a tag)
1182
1183 """
1184 if not self.revisionExpr.startswith(R_TAGS):
1185 return self.GetRevisionId(self._allrefs)
1186
1187 try:
1188 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1189 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001190 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1191 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001192
David Pursehouse8a68ff92012-09-24 12:15:13 +09001193 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001194 if self.revisionId:
1195 return self.revisionId
1196
1197 rem = self.GetRemote(self.remote.name)
1198 rev = rem.ToLocal(self.revisionExpr)
1199
David Pursehouse8a68ff92012-09-24 12:15:13 +09001200 if all_refs is not None and rev in all_refs:
1201 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001202
1203 try:
1204 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1205 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001206 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1207 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001208
Martin Kellye4e94d22017-03-21 16:05:12 -07001209 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210 """Perform only the local IO portion of the sync process.
1211 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001212 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001213 if not os.path.exists(self.gitdir):
1214 syncbuf.fail(self,
1215 'Cannot checkout %s due to missing network sync; Run '
1216 '`repo sync -n %s` first.' %
1217 (self.name, self.name))
1218 return
1219
Martin Kellye4e94d22017-03-21 16:05:12 -07001220 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001221 all_refs = self.bare_ref.all
1222 self.CleanPublishedCache(all_refs)
1223 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001224
David Pursehouse1d947b32012-10-25 12:23:11 +09001225 def _doff():
1226 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001227 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001228
Martin Kellye4e94d22017-03-21 16:05:12 -07001229 def _dosubmodules():
1230 self._SyncSubmodules(quiet=True)
1231
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001232 head = self.work_git.GetHead()
1233 if head.startswith(R_HEADS):
1234 branch = head[len(R_HEADS):]
1235 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001236 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001237 except KeyError:
1238 head = None
1239 else:
1240 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001241
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001242 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001243 # Currently on a detached HEAD. The user is assumed to
1244 # not have any local modifications worth worrying about.
1245 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001246 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001247 syncbuf.fail(self, _PriorSyncFailedError())
1248 return
1249
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001250 if head == revid:
1251 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001252 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001253 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001254 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001255 # The copy/linkfile config may have changed.
1256 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001257 return
1258 else:
1259 lost = self._revlist(not_rev(revid), HEAD)
1260 if lost:
1261 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001262
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001264 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001265 if submodules:
1266 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001267 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001268 syncbuf.fail(self, e)
1269 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001270 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001271 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001272
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001273 if head == revid:
1274 # No changes; don't do anything further.
1275 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001276 # The copy/linkfile config may have changed.
1277 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001278 return
1279
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001281
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001282 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001283 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001284 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001286 syncbuf.info(self,
1287 "leaving %s; does not track upstream",
1288 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001289 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001290 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001291 if submodules:
1292 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001293 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001294 syncbuf.fail(self, e)
1295 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001296 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001297 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001299 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001300
1301 # See if we can perform a fast forward merge. This can happen if our
1302 # branch isn't in the exact same state as we last published.
1303 try:
1304 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1305 # Skip the published logic.
1306 pub = False
1307 except GitError:
1308 pub = self.WasPublished(branch.name, all_refs)
1309
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001311 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 if not_merged:
1313 if upstream_gain:
1314 # The user has published this branch and some of those
1315 # commits are not yet merged upstream. We do not want
1316 # to rewrite the published commits so we punt.
1317 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001318 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001319 "branch %s is published (but not merged) and is now "
1320 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001321 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001322 elif pub == head:
1323 # All published commits are merged, and thus we are a
1324 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001325 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001326 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001327 if submodules:
1328 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001329 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001331 # Examine the local commits not in the remote. Find the
1332 # last one attributed to this user, if any.
1333 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001334 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001335 last_mine = None
1336 cnt_mine = 0
1337 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001338 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001339 if committer_email == self.UserEmail:
1340 last_mine = commit_id
1341 cnt_mine += 1
1342
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001343 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001344 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001345
1346 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001347 syncbuf.fail(self, _DirtyError())
1348 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001349
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001350 # If the upstream switched on us, warn the user.
1351 #
1352 if branch.merge != self.revisionExpr:
1353 if branch.merge and self.revisionExpr:
1354 syncbuf.info(self,
1355 'manifest switched %s...%s',
1356 branch.merge,
1357 self.revisionExpr)
1358 elif branch.merge:
1359 syncbuf.info(self,
1360 'manifest no longer tracks %s',
1361 branch.merge)
1362
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001363 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001364 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001365 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001367 syncbuf.info(self,
1368 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001369 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001371 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001372 if not ID_RE.match(self.revisionExpr):
1373 # in case of manifest sync the revisionExpr might be a SHA1
1374 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001375 if not branch.merge.startswith('refs/'):
1376 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001377 branch.Save()
1378
Mike Pontillod3153822012-02-28 11:53:24 -08001379 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001380 def _docopyandlink():
1381 self._CopyAndLinkFiles()
1382
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001383 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001384 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001385 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001386 if submodules:
1387 syncbuf.later2(self, _dosubmodules)
1388 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001389 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001390 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001391 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001392 if submodules:
1393 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001394 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001395 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001396 syncbuf.fail(self, e)
1397 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001398 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001399 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001400 if submodules:
1401 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402
Mike Frysingere6a202f2019-08-02 15:57:57 -04001403 def AddCopyFile(self, src, dest, topdir):
1404 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001405
Mike Frysingere6a202f2019-08-02 15:57:57 -04001406 No filesystem changes occur here. Actual copying happens later on.
1407
1408 Paths should have basic validation run on them before being queued.
1409 Further checking will be handled when the actual copy happens.
1410 """
1411 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1412
1413 def AddLinkFile(self, src, dest, topdir):
1414 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1415
1416 No filesystem changes occur here. Actual linking happens later on.
1417
1418 Paths should have basic validation run on them before being queued.
1419 Further checking will be handled when the actual link happens.
1420 """
1421 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001422
James W. Mills24c13082012-04-12 15:04:13 -05001423 def AddAnnotation(self, name, value, keep):
1424 self.annotations.append(_Annotation(name, value, keep))
1425
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001426 def DownloadPatchSet(self, change_id, patch_id):
1427 """Download a single patch set of a single change to FETCH_HEAD.
1428 """
1429 remote = self.GetRemote(self.remote.name)
1430
1431 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001432 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001433 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001434 if GitCommand(self, cmd, bare=True).Wait() != 0:
1435 return None
1436 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001437 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001438 change_id,
1439 patch_id,
1440 self.bare_git.rev_parse('FETCH_HEAD'))
1441
Mike Frysingerc0d18662020-02-19 19:19:18 -05001442 def DeleteWorktree(self, quiet=False, force=False):
1443 """Delete the source checkout and any other housekeeping tasks.
1444
1445 This currently leaves behind the internal .repo/ cache state. This helps
1446 when switching branches or manifest changes get reverted as we don't have
1447 to redownload all the git objects. But we should do some GC at some point.
1448
1449 Args:
1450 quiet: Whether to hide normal messages.
1451 force: Always delete tree even if dirty.
1452
1453 Returns:
1454 True if the worktree was completely cleaned out.
1455 """
1456 if self.IsDirty():
1457 if force:
1458 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1459 (self.relpath,), file=sys.stderr)
1460 else:
1461 print('error: %s: Cannot remove project: uncommitted changes are '
1462 'present.\n' % (self.relpath,), file=sys.stderr)
1463 return False
1464
1465 if not quiet:
1466 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1467
1468 # Unlock and delink from the main worktree. We don't use git's worktree
1469 # remove because it will recursively delete projects -- we handle that
1470 # ourselves below. https://crbug.com/git/48
1471 if self.use_git_worktrees:
1472 needle = platform_utils.realpath(self.gitdir)
1473 # Find the git worktree commondir under .repo/worktrees/.
1474 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1475 assert output.startswith('worktree '), output
1476 commondir = output[9:]
1477 # Walk each of the git worktrees to see where they point.
1478 configs = os.path.join(commondir, 'worktrees')
1479 for name in os.listdir(configs):
1480 gitdir = os.path.join(configs, name, 'gitdir')
1481 with open(gitdir) as fp:
1482 relpath = fp.read().strip()
1483 # Resolve the checkout path and see if it matches this project.
1484 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1485 if fullpath == needle:
1486 platform_utils.rmtree(os.path.join(configs, name))
1487
1488 # Delete the .git directory first, so we're less likely to have a partially
1489 # working git repository around. There shouldn't be any git projects here,
1490 # so rmtree works.
1491
1492 # Try to remove plain files first in case of git worktrees. If this fails
1493 # for any reason, we'll fall back to rmtree, and that'll display errors if
1494 # it can't remove things either.
1495 try:
1496 platform_utils.remove(self.gitdir)
1497 except OSError:
1498 pass
1499 try:
1500 platform_utils.rmtree(self.gitdir)
1501 except OSError as e:
1502 if e.errno != errno.ENOENT:
1503 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1504 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1505 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1506 return False
1507
1508 # Delete everything under the worktree, except for directories that contain
1509 # another git project.
1510 dirs_to_remove = []
1511 failed = False
1512 for root, dirs, files in platform_utils.walk(self.worktree):
1513 for f in files:
1514 path = os.path.join(root, f)
1515 try:
1516 platform_utils.remove(path)
1517 except OSError as e:
1518 if e.errno != errno.ENOENT:
1519 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1520 failed = True
1521 dirs[:] = [d for d in dirs
1522 if not os.path.lexists(os.path.join(root, d, '.git'))]
1523 dirs_to_remove += [os.path.join(root, d) for d in dirs
1524 if os.path.join(root, d) not in dirs_to_remove]
1525 for d in reversed(dirs_to_remove):
1526 if platform_utils.islink(d):
1527 try:
1528 platform_utils.remove(d)
1529 except OSError as e:
1530 if e.errno != errno.ENOENT:
1531 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1532 failed = True
1533 elif not platform_utils.listdir(d):
1534 try:
1535 platform_utils.rmdir(d)
1536 except OSError as e:
1537 if e.errno != errno.ENOENT:
1538 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1539 failed = True
1540 if failed:
1541 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1542 file=sys.stderr)
1543 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1544 return False
1545
1546 # Try deleting parent dirs if they are empty.
1547 path = self.worktree
1548 while path != self.manifest.topdir:
1549 try:
1550 platform_utils.rmdir(path)
1551 except OSError as e:
1552 if e.errno != errno.ENOENT:
1553 break
1554 path = os.path.dirname(path)
1555
1556 return True
1557
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001558# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001559 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560 """Create a new branch off the manifest's revision.
1561 """
Simran Basib9a1b732015-08-20 12:19:28 -07001562 if not branch_merge:
1563 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001564 head = self.work_git.GetHead()
1565 if head == (R_HEADS + name):
1566 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567
David Pursehouse8a68ff92012-09-24 12:15:13 +09001568 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001569 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001570 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001571 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001572 capture_stdout=True,
1573 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001574
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001575 branch = self.GetBranch(name)
1576 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001577 branch.merge = branch_merge
1578 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1579 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001580
1581 if revision is None:
1582 revid = self.GetRevisionId(all_refs)
1583 else:
1584 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001585
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001586 if head.startswith(R_HEADS):
1587 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001588 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001589 except KeyError:
1590 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001591 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001592 ref = R_HEADS + name
1593 self.work_git.update_ref(ref, revid)
1594 self.work_git.symbolic_ref(HEAD, ref)
1595 branch.Save()
1596 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001597
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001598 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001599 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001600 capture_stdout=True,
1601 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001602 branch.Save()
1603 return True
1604 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001605
Wink Saville02d79452009-04-10 13:01:24 -07001606 def CheckoutBranch(self, name):
1607 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001608
1609 Args:
1610 name: The name of the branch to checkout.
1611
1612 Returns:
1613 True if the checkout succeeded; False if it didn't; None if the branch
1614 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001615 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001616 rev = R_HEADS + name
1617 head = self.work_git.GetHead()
1618 if head == rev:
1619 # Already on the branch
1620 #
1621 return True
Wink Saville02d79452009-04-10 13:01:24 -07001622
David Pursehouse8a68ff92012-09-24 12:15:13 +09001623 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001624 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001625 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001626 except KeyError:
1627 # Branch does not exist in this project
1628 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001629 return None
Wink Saville02d79452009-04-10 13:01:24 -07001630
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001631 if head.startswith(R_HEADS):
1632 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001633 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001634 except KeyError:
1635 head = None
1636
1637 if head == revid:
1638 # Same revision; just update HEAD to point to the new
1639 # target branch, but otherwise take no other action.
1640 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001641 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1642 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001643 return True
1644
1645 return GitCommand(self,
1646 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001647 capture_stdout=True,
1648 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001649
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001650 def AbandonBranch(self, name):
1651 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001652
1653 Args:
1654 name: The name of the branch to abandon.
1655
1656 Returns:
1657 True if the abandon succeeded; False if it didn't; None if the branch
1658 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001659 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001660 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001661 all_refs = self.bare_ref.all
1662 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001663 # Doesn't exist
1664 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001665
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001666 head = self.work_git.GetHead()
1667 if head == rev:
1668 # We can't destroy the branch while we are sitting
1669 # on it. Switch to a detached HEAD.
1670 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001671 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001672
David Pursehouse8a68ff92012-09-24 12:15:13 +09001673 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001674 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001675 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001676 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001677 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001678
1679 return GitCommand(self,
1680 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001681 capture_stdout=True,
1682 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001683
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684 def PruneHeads(self):
1685 """Prune any topic branches already merged into upstream.
1686 """
1687 cb = self.CurrentBranch
1688 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001689 left = self._allrefs
1690 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001691 if name.startswith(R_HEADS):
1692 name = name[len(R_HEADS):]
1693 if cb is None or name != cb:
1694 kill.append(name)
1695
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001696 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697 if cb is not None \
1698 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001699 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001700 self.work_git.DetachHead(HEAD)
1701 kill.append(cb)
1702
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001703 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001704 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706 try:
1707 self.bare_git.DetachHead(rev)
1708
1709 b = ['branch', '-d']
1710 b.extend(kill)
1711 b = GitCommand(self, b, bare=True,
1712 capture_stdout=True,
1713 capture_stderr=True)
1714 b.Wait()
1715 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001716 if ID_RE.match(old):
1717 self.bare_git.DetachHead(old)
1718 else:
1719 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001720 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001721
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001722 for branch in kill:
1723 if (R_HEADS + branch) not in left:
1724 self.CleanPublishedCache()
1725 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726
1727 if cb and cb not in kill:
1728 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001729 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730
1731 kept = []
1732 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001733 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001734 branch = self.GetBranch(branch)
1735 base = branch.LocalMerge
1736 if not base:
1737 base = rev
1738 kept.append(ReviewableBranch(self, branch, base))
1739 return kept
1740
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001741# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001742 def GetRegisteredSubprojects(self):
1743 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001744
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001745 def rec(subprojects):
1746 if not subprojects:
1747 return
1748 result.extend(subprojects)
1749 for p in subprojects:
1750 rec(p.subprojects)
1751 rec(self.subprojects)
1752 return result
1753
1754 def _GetSubmodules(self):
1755 # Unfortunately we cannot call `git submodule status --recursive` here
1756 # because the working tree might not exist yet, and it cannot be used
1757 # without a working tree in its current implementation.
1758
1759 def get_submodules(gitdir, rev):
1760 # Parse .gitmodules for submodule sub_paths and sub_urls
1761 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1762 if not sub_paths:
1763 return []
1764 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1765 # revision of submodule repository
1766 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1767 submodules = []
1768 for sub_path, sub_url in zip(sub_paths, sub_urls):
1769 try:
1770 sub_rev = sub_revs[sub_path]
1771 except KeyError:
1772 # Ignore non-exist submodules
1773 continue
1774 submodules.append((sub_rev, sub_path, sub_url))
1775 return submodules
1776
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001777 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1778 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001779
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001780 def parse_gitmodules(gitdir, rev):
1781 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1782 try:
Anthony King7bdac712014-07-16 12:56:40 +01001783 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1784 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001785 except GitError:
1786 return [], []
1787 if p.Wait() != 0:
1788 return [], []
1789
1790 gitmodules_lines = []
1791 fd, temp_gitmodules_path = tempfile.mkstemp()
1792 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001793 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001794 os.close(fd)
1795 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001796 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1797 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001798 if p.Wait() != 0:
1799 return [], []
1800 gitmodules_lines = p.stdout.split('\n')
1801 except GitError:
1802 return [], []
1803 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001804 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001805
1806 names = set()
1807 paths = {}
1808 urls = {}
1809 for line in gitmodules_lines:
1810 if not line:
1811 continue
1812 m = re_path.match(line)
1813 if m:
1814 names.add(m.group(1))
1815 paths[m.group(1)] = m.group(2)
1816 continue
1817 m = re_url.match(line)
1818 if m:
1819 names.add(m.group(1))
1820 urls[m.group(1)] = m.group(2)
1821 continue
1822 names = sorted(names)
1823 return ([paths.get(name, '') for name in names],
1824 [urls.get(name, '') for name in names])
1825
1826 def git_ls_tree(gitdir, rev, paths):
1827 cmd = ['ls-tree', rev, '--']
1828 cmd.extend(paths)
1829 try:
Anthony King7bdac712014-07-16 12:56:40 +01001830 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1831 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001832 except GitError:
1833 return []
1834 if p.Wait() != 0:
1835 return []
1836 objects = {}
1837 for line in p.stdout.split('\n'):
1838 if not line.strip():
1839 continue
1840 object_rev, object_path = line.split()[2:4]
1841 objects[object_path] = object_rev
1842 return objects
1843
1844 try:
1845 rev = self.GetRevisionId()
1846 except GitError:
1847 return []
1848 return get_submodules(self.gitdir, rev)
1849
1850 def GetDerivedSubprojects(self):
1851 result = []
1852 if not self.Exists:
1853 # If git repo does not exist yet, querying its submodules will
1854 # mess up its states; so return here.
1855 return result
1856 for rev, path, url in self._GetSubmodules():
1857 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001858 relpath, worktree, gitdir, objdir = \
1859 self.manifest.GetSubprojectPaths(self, name, path)
1860 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001861 if project:
1862 result.extend(project.GetDerivedSubprojects())
1863 continue
David James8d201162013-10-11 17:03:19 -07001864
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001865 if url.startswith('..'):
1866 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001867 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001868 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001869 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001870 review=self.remote.review,
1871 revision=self.remote.revision)
1872 subproject = Project(manifest=self.manifest,
1873 name=name,
1874 remote=remote,
1875 gitdir=gitdir,
1876 objdir=objdir,
1877 worktree=worktree,
1878 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001879 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001880 revisionId=rev,
1881 rebase=self.rebase,
1882 groups=self.groups,
1883 sync_c=self.sync_c,
1884 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001885 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001886 parent=self,
1887 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001888 result.append(subproject)
1889 result.extend(subproject.GetDerivedSubprojects())
1890 return result
1891
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001892# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001893 def EnableRepositoryExtension(self, key, value='true', version=1):
1894 """Enable git repository extension |key| with |value|.
1895
1896 Args:
1897 key: The extension to enabled. Omit the "extensions." prefix.
1898 value: The value to use for the extension.
1899 version: The minimum git repository version needed.
1900 """
1901 # Make sure the git repo version is new enough already.
1902 found_version = self.config.GetInt('core.repositoryFormatVersion')
1903 if found_version is None:
1904 found_version = 0
1905 if found_version < version:
1906 self.config.SetString('core.repositoryFormatVersion', str(version))
1907
1908 # Enable the extension!
1909 self.config.SetString('extensions.%s' % (key,), value)
1910
Mike Frysinger50a81de2020-09-06 15:51:21 -04001911 def ResolveRemoteHead(self, name=None):
1912 """Find out what the default branch (HEAD) points to.
1913
1914 Normally this points to refs/heads/master, but projects are moving to main.
1915 Support whatever the server uses rather than hardcoding "master" ourselves.
1916 """
1917 if name is None:
1918 name = self.remote.name
1919
1920 # The output will look like (NB: tabs are separators):
1921 # ref: refs/heads/master HEAD
1922 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1923 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1924
1925 for line in output.splitlines():
1926 lhs, rhs = line.split('\t', 1)
1927 if rhs == 'HEAD' and lhs.startswith('ref:'):
1928 return lhs[4:].strip()
1929
1930 return None
1931
Zac Livingstone4332262017-06-16 08:56:09 -06001932 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001933 try:
1934 # if revision (sha or tag) is not present then following function
1935 # throws an error.
1936 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
1937 return True
1938 except GitError:
1939 # There is no such persistent revision. We have to fetch it.
1940 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001941
Julien Campergue335f5ef2013-10-16 11:02:35 +02001942 def _FetchArchive(self, tarpath, cwd=None):
1943 cmd = ['archive', '-v', '-o', tarpath]
1944 cmd.append('--remote=%s' % self.remote.url)
1945 cmd.append('--prefix=%s/' % self.relpath)
1946 cmd.append(self.revisionExpr)
1947
1948 command = GitCommand(self, cmd, cwd=cwd,
1949 capture_stdout=True,
1950 capture_stderr=True)
1951
1952 if command.Wait() != 0:
1953 raise GitError('git archive %s: %s' % (self.name, command.stderr))
1954
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001955 def _RemoteFetch(self, name=None,
1956 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07001957 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001958 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001959 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001960 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001961 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001962 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001963 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04001964 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07001965 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001966 clone_filter=None,
1967 retry_fetches=2,
1968 retry_sleep_initial_sec=4.0,
1969 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001970 is_sha1 = False
1971 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001972 # The depth should not be used when fetching to a mirror because
1973 # it will result in a shallow repository that cannot be cloned or
1974 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001975 # The repo project should also never be synced with partial depth.
1976 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1977 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001978
Shawn Pearce69e04d82014-01-29 12:48:54 -08001979 if depth:
1980 current_branch_only = True
1981
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001982 if ID_RE.match(self.revisionExpr) is not None:
1983 is_sha1 = True
1984
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001985 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001986 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001987 # this is a tag and its sha1 value should never change
1988 tag_name = self.revisionExpr[len(R_TAGS):]
1989
1990 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06001991 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05001992 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02001993 print('Skipped fetching project %s (already have persistent ref)'
1994 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001995 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001996 if is_sha1 and not depth:
1997 # When syncing a specific commit and --depth is not set:
1998 # * if upstream is explicitly specified and is not a sha1, fetch only
1999 # upstream as users expect only upstream to be fetch.
2000 # Note: The commit might not be in upstream in which case the sync
2001 # will fail.
2002 # * otherwise, fetch all branches to make sure we end up with the
2003 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002004 if self.upstream:
2005 current_branch_only = not ID_RE.match(self.upstream)
2006 else:
2007 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002008
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002009 if not name:
2010 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002011
2012 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002013 remote = self.GetRemote(name)
2014 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002015 ssh_proxy = True
2016
Shawn O. Pearce88443382010-10-08 10:02:09 +02002017 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002018 if alt_dir and 'objects' == os.path.basename(alt_dir):
2019 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002020 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2021 remote = self.GetRemote(name)
2022
David Pursehouse8a68ff92012-09-24 12:15:13 +09002023 all_refs = self.bare_ref.all
2024 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002025 tmp = set()
2026
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302027 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002028 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002029 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002030 all_refs[r] = ref_id
2031 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002032 continue
2033
David Pursehouse8a68ff92012-09-24 12:15:13 +09002034 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002035 continue
2036
David Pursehouse8a68ff92012-09-24 12:15:13 +09002037 r = 'refs/_alt/%s' % ref_id
2038 all_refs[r] = ref_id
2039 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002040 tmp.add(r)
2041
heping3d7bbc92017-04-12 19:51:47 +08002042 tmp_packed_lines = []
2043 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002044
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302045 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002046 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002047 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002048 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002049 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002050
heping3d7bbc92017-04-12 19:51:47 +08002051 tmp_packed = ''.join(tmp_packed_lines)
2052 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002053 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002054 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002055 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002056
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002057 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002058
Xin Li745be2e2019-06-03 11:24:30 -07002059 if clone_filter:
2060 git_require((2, 19, 0), fail=True, msg='partial clones')
2061 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002062 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002063
Conley Owensf97e8382015-01-21 11:12:46 -08002064 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002065 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002066 else:
2067 # If this repo has shallow objects, then we don't know which refs have
2068 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2069 # do this with projects that don't have shallow objects, since it is less
2070 # efficient.
2071 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2072 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002073
Mike Frysinger4847e052020-02-22 00:07:35 -05002074 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002075 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002076 if not quiet and sys.stdout.isatty():
2077 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002078 if not self.worktree:
2079 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002080 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002081
Mike Frysingere57f1142019-03-18 21:27:54 -04002082 if force_sync:
2083 cmd.append('--force')
2084
David Pursehouse74cfd272015-10-14 10:50:15 +09002085 if prune:
2086 cmd.append('--prune')
2087
Martin Kellye4e94d22017-03-21 16:05:12 -07002088 if submodules:
2089 cmd.append('--recurse-submodules=on-demand')
2090
Kuang-che Wu6856f982019-11-25 12:37:55 +08002091 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002092 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002093 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002094 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002095 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002096 spec.append('tag')
2097 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002098
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302099 if self.manifest.IsMirror and not current_branch_only:
2100 branch = None
2101 else:
2102 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002103 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002104 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002105 # Shallow checkout of a specific commit, fetch from that commit and not
2106 # the heads only as the commit might be deeper in the history.
2107 spec.append(branch)
2108 else:
2109 if is_sha1:
2110 branch = self.upstream
2111 if branch is not None and branch.strip():
2112 if not branch.startswith('refs/'):
2113 branch = R_HEADS + branch
2114 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2115
2116 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2117 # whole repo.
2118 if self.manifest.IsMirror and not spec:
2119 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2120
2121 # If using depth then we should not get all the tags since they may
2122 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002123 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002124 cmd.append('--no-tags')
2125 else:
2126 cmd.append('--tags')
2127 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2128
Conley Owens80b87fe2014-05-09 17:13:44 -07002129 cmd.extend(spec)
2130
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002131 # At least one retry minimum due to git remote prune.
2132 retry_fetches = max(retry_fetches, 2)
2133 retry_cur_sleep = retry_sleep_initial_sec
2134 ok = prune_tried = False
2135 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002136 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002137 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002138 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002139 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002140 ok = True
2141 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002142
2143 # Retry later due to HTTP 429 Too Many Requests.
2144 elif ('error:' in gitcmd.stderr and
2145 'HTTP 429' in gitcmd.stderr):
2146 if not quiet:
2147 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2148 file=sys.stderr)
2149 time.sleep(retry_cur_sleep)
2150 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2151 MAXIMUM_RETRY_SLEEP_SEC)
2152 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2153 RETRY_JITTER_PERCENT))
2154 continue
2155
2156 # If this is not last attempt, try 'git remote prune'.
2157 elif (try_n < retry_fetches - 1 and
2158 'error:' in gitcmd.stderr and
2159 'git remote prune' in gitcmd.stderr and
2160 not prune_tried):
2161 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002162 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002163 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002164 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002165 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002166 break
2167 continue
Brian Harring14a66742012-09-28 20:21:57 -07002168 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002169 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2170 # in sha1 mode, we just tried sync'ing from the upstream field; it
2171 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002172 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002173 elif ret < 0:
2174 # Git died with a signal, exit immediately
2175 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002176 if not verbose:
2177 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002178 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002179
2180 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002181 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002182 if old_packed != '':
2183 _lwrite(packed_refs, old_packed)
2184 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002185 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002186 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002187
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002188 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002189 # We just synced the upstream given branch; verify we
2190 # got what we wanted, else trigger a second run of all
2191 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002192 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002193 # Sync the current branch only with depth set to None.
2194 # We always pass depth=None down to avoid infinite recursion.
2195 return self._RemoteFetch(
2196 name=name, quiet=quiet, verbose=verbose,
2197 current_branch_only=current_branch_only and depth,
2198 initial=False, alt_dir=alt_dir,
2199 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002200
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002201 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002202
Mike Frysingere50b6a72020-02-19 01:45:48 -05002203 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002204 if initial and \
2205 (self.manifest.manifestProject.config.GetString('repo.depth') or
2206 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002207 return False
2208
2209 remote = self.GetRemote(self.remote.name)
2210 bundle_url = remote.url + '/clone.bundle'
2211 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002212 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2213 'persistent-http',
2214 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002215 return False
2216
2217 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2218 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2219
2220 exist_dst = os.path.exists(bundle_dst)
2221 exist_tmp = os.path.exists(bundle_tmp)
2222
2223 if not initial and not exist_dst and not exist_tmp:
2224 return False
2225
2226 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002227 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2228 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002229 if not exist_dst:
2230 return False
2231
2232 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002233 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002234 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002235 if not quiet and sys.stdout.isatty():
2236 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002237 if not self.worktree:
2238 cmd.append('--update-head-ok')
2239 cmd.append(bundle_dst)
2240 for f in remote.fetch:
2241 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002242 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002243
2244 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002245 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002246 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002247 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002248 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002249 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002250
Mike Frysingere50b6a72020-02-19 01:45:48 -05002251 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002252 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002253 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002254
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002255 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002256 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002257 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002258 if os.path.exists(tmpPath):
2259 size = os.stat(tmpPath).st_size
2260 if size >= 1024:
2261 cmd += ['--continue-at', '%d' % (size,)]
2262 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002263 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002264 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002265 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002266 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002267 if proxy:
2268 cmd += ['--proxy', proxy]
2269 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2270 cmd += ['--proxy', os.environ['http_proxy']]
2271 if srcUrl.startswith('persistent-https'):
2272 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2273 elif srcUrl.startswith('persistent-http'):
2274 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002275 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002276
Dave Borowitz137d0132015-01-02 11:12:54 -08002277 if IsTrace():
2278 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002279 if verbose:
2280 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2281 stdout = None if verbose else subprocess.PIPE
2282 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002283 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002284 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002285 except OSError:
2286 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002287
Mike Frysingere50b6a72020-02-19 01:45:48 -05002288 (output, _) = proc.communicate()
2289 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002290
Dave Borowitz137d0132015-01-02 11:12:54 -08002291 if curlret == 22:
2292 # From curl man page:
2293 # 22: HTTP page not retrieved. The requested url was not found or
2294 # returned another error with the HTTP error code being 400 or above.
2295 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002296 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002297 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2298 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002299 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002300 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002301 elif curlret and not verbose and output:
2302 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002303
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002304 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002305 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002306 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002307 return True
2308 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002309 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002310 return False
2311 else:
2312 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002313
Kris Giesingc8d882a2014-12-23 13:02:32 -08002314 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002315 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002316 with open(path, 'rb') as f:
2317 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002318 return True
2319 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002320 if not quiet:
2321 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002322 return False
2323 except OSError:
2324 return False
2325
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002326 def _Checkout(self, rev, quiet=False):
2327 cmd = ['checkout']
2328 if quiet:
2329 cmd.append('-q')
2330 cmd.append(rev)
2331 cmd.append('--')
2332 if GitCommand(self, cmd).Wait() != 0:
2333 if self._allrefs:
2334 raise GitError('%s checkout %s ' % (self.name, rev))
2335
Mike Frysinger915fda12020-03-22 12:15:20 -04002336 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002337 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002338 if ffonly:
2339 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002340 if record_origin:
2341 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002342 cmd.append(rev)
2343 cmd.append('--')
2344 if GitCommand(self, cmd).Wait() != 0:
2345 if self._allrefs:
2346 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2347
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302348 def _LsRemote(self, refs):
2349 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302350 p = GitCommand(self, cmd, capture_stdout=True)
2351 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002352 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302353 return None
2354
Anthony King7bdac712014-07-16 12:56:40 +01002355 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002356 cmd = ['revert']
2357 cmd.append('--no-edit')
2358 cmd.append(rev)
2359 cmd.append('--')
2360 if GitCommand(self, cmd).Wait() != 0:
2361 if self._allrefs:
2362 raise GitError('%s revert %s ' % (self.name, rev))
2363
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002364 def _ResetHard(self, rev, quiet=True):
2365 cmd = ['reset', '--hard']
2366 if quiet:
2367 cmd.append('-q')
2368 cmd.append(rev)
2369 if GitCommand(self, cmd).Wait() != 0:
2370 raise GitError('%s reset --hard %s ' % (self.name, rev))
2371
Martin Kellye4e94d22017-03-21 16:05:12 -07002372 def _SyncSubmodules(self, quiet=True):
2373 cmd = ['submodule', 'update', '--init', '--recursive']
2374 if quiet:
2375 cmd.append('-q')
2376 if GitCommand(self, cmd).Wait() != 0:
2377 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2378
Anthony King7bdac712014-07-16 12:56:40 +01002379 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002380 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002381 if onto is not None:
2382 cmd.extend(['--onto', onto])
2383 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002384 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002385 raise GitError('%s rebase %s ' % (self.name, upstream))
2386
Pierre Tardy3d125942012-05-04 12:18:12 +02002387 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002388 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002389 if ffonly:
2390 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002391 if GitCommand(self, cmd).Wait() != 0:
2392 raise GitError('%s merge %s ' % (self.name, head))
2393
David Pursehousee8ace262020-02-13 12:41:15 +09002394 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002395 init_git_dir = not os.path.exists(self.gitdir)
2396 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002397 try:
2398 # Initialize the bare repository, which contains all of the objects.
2399 if init_obj_dir:
2400 os.makedirs(self.objdir)
2401 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002402
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002403 if self.use_git_worktrees:
2404 # Set up the m/ space to point to the worktree-specific ref space.
2405 # We'll update the worktree-specific ref space on each checkout.
2406 if self.manifest.branch:
2407 self.bare_git.symbolic_ref(
2408 '-m', 'redirecting to worktree scope',
2409 R_M + self.manifest.branch,
2410 R_WORKTREE_M + self.manifest.branch)
2411
2412 # Enable per-worktree config file support if possible. This is more a
2413 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002414 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002415 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002416
Kevin Degib1a07b82015-07-27 13:33:43 -06002417 # If we have a separate directory to hold refs, initialize it as well.
2418 if self.objdir != self.gitdir:
2419 if init_git_dir:
2420 os.makedirs(self.gitdir)
2421
2422 if init_obj_dir or init_git_dir:
2423 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2424 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002425 try:
2426 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2427 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002428 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002429 print("Retrying clone after deleting %s" %
2430 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002431 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002432 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2433 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002434 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002435 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002436 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2437 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002438 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002439 raise e
2440 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002441
Kevin Degi384b3c52014-10-16 16:02:58 -06002442 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002443 mp = self.manifest.manifestProject
2444 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002445
Kevin Degib1a07b82015-07-27 13:33:43 -06002446 if ref_dir or mirror_git:
2447 if not mirror_git:
2448 mirror_git = os.path.join(ref_dir, self.name + '.git')
2449 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2450 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002451 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2452 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002453
Kevin Degib1a07b82015-07-27 13:33:43 -06002454 if os.path.exists(mirror_git):
2455 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002456 elif os.path.exists(repo_git):
2457 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002458 elif os.path.exists(worktrees_git):
2459 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002460 else:
2461 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002462
Kevin Degib1a07b82015-07-27 13:33:43 -06002463 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002464 if not os.path.isabs(ref_dir):
2465 # The alternate directory is relative to the object database.
2466 ref_dir = os.path.relpath(ref_dir,
2467 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002468 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2469 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002470
David Pursehousee8ace262020-02-13 12:41:15 +09002471 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002472
2473 m = self.manifest.manifestProject.config
2474 for key in ['user.name', 'user.email']:
2475 if m.Has(key, include_defaults=False):
2476 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002477 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002478 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002479 if self.manifest.IsMirror:
2480 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002481 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002482 self.config.SetString('core.bare', None)
2483 except Exception:
2484 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002485 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002486 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002487 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002488 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002489
David Pursehousee8ace262020-02-13 12:41:15 +09002490 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002491 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002492 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002493
David Pursehousee8ace262020-02-13 12:41:15 +09002494 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002495 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002496 if not os.path.exists(hooks):
2497 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002498 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002499 name = os.path.basename(stock_hook)
2500
Victor Boivie65e0f352011-04-18 11:23:29 +02002501 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002502 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002503 # Don't install a Gerrit Code Review hook if this
2504 # project does not appear to use it for reviews.
2505 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002506 # Since the manifest project is one of those, but also
2507 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002508 continue
2509
2510 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002511 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002512 continue
2513 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002514 # If the files are the same, we'll leave it alone. We create symlinks
2515 # below by default but fallback to hardlinks if the OS blocks them.
2516 # So if we're here, it's probably because we made a hardlink below.
2517 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002518 if not quiet:
2519 _warn("%s: Not replacing locally modified %s hook",
2520 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002521 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002522 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002523 platform_utils.symlink(
2524 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002525 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002526 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002527 try:
2528 os.link(stock_hook, dst)
2529 except OSError:
2530 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002531 else:
2532 raise
2533
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002534 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002535 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002536 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002537 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002538 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002539 remote.review = self.remote.review
2540 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002541
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002542 if self.worktree:
2543 remote.ResetFetch(mirror=False)
2544 else:
2545 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002546 remote.Save()
2547
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002548 def _InitMRef(self):
2549 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002550 if self.use_git_worktrees:
2551 # We can't update this ref with git worktrees until it exists.
2552 # We'll wait until the initial checkout to set it.
2553 if not os.path.exists(self.worktree):
2554 return
2555
2556 base = R_WORKTREE_M
2557 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002558
2559 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002560 else:
2561 base = R_M
2562 active_git = self.bare_git
2563
2564 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002565
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002566 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002567 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002568
Remy Böhmer1469c282020-12-15 18:49:02 +01002569 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002570 cur = self.bare_ref.symref(ref)
2571
2572 if self.revisionId:
2573 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2574 msg = 'manifest set to %s' % self.revisionId
2575 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002576 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002577 else:
2578 remote = self.GetRemote(self.remote.name)
2579 dst = remote.ToLocal(self.revisionExpr)
2580 if cur != dst:
2581 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002582 if detach:
2583 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2584 else:
2585 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002586
Kevin Degi384b3c52014-10-16 16:02:58 -06002587 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002588 # Git worktrees don't use symlinks to share at all.
2589 if self.use_git_worktrees:
2590 return
2591
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002592 symlink_files = self.shareable_files[:]
2593 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002594 if share_refs:
2595 symlink_files += self.working_tree_files
2596 symlink_dirs += self.working_tree_dirs
2597 to_symlink = symlink_files + symlink_dirs
2598 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002599 # Try to self-heal a bit in simple cases.
2600 dst_path = os.path.join(destdir, name)
2601 src_path = os.path.join(srcdir, name)
2602
2603 if name in self.working_tree_dirs:
2604 # If the dir is missing under .repo/projects/, create it.
2605 if not os.path.exists(src_path):
2606 os.makedirs(src_path)
2607
2608 elif name in self.working_tree_files:
2609 # If it's a file under the checkout .git/ and the .repo/projects/ has
2610 # nothing, move the file under the .repo/projects/ tree.
2611 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2612 platform_utils.rename(dst_path, src_path)
2613
2614 # If the path exists under the .repo/projects/ and there's no symlink
2615 # under the checkout .git/, recreate the symlink.
2616 if name in self.working_tree_dirs or name in self.working_tree_files:
2617 if os.path.exists(src_path) and not os.path.exists(dst_path):
2618 platform_utils.symlink(
2619 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2620
2621 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002622 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002623 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002624 # Fail if the links are pointing to the wrong place
2625 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002626 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002627 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002628 'work tree. If you\'re comfortable with the '
2629 'possibility of losing the work tree\'s git metadata,'
2630 ' use `repo sync --force-sync {0}` to '
2631 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002632
David James8d201162013-10-11 17:03:19 -07002633 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2634 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2635
2636 Args:
2637 gitdir: The bare git repository. Must already be initialized.
2638 dotgit: The repository you would like to initialize.
2639 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2640 Only one work tree can store refs under a given |gitdir|.
2641 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2642 This saves you the effort of initializing |dotgit| yourself.
2643 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002644 symlink_files = self.shareable_files[:]
2645 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002646 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002647 symlink_files += self.working_tree_files
2648 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002649 to_symlink = symlink_files + symlink_dirs
2650
2651 to_copy = []
2652 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002653 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002654
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002655 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002656 for name in set(to_copy).union(to_symlink):
2657 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002658 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002659 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002660
Kevin Degi384b3c52014-10-16 16:02:58 -06002661 if os.path.lexists(dst):
2662 continue
David James8d201162013-10-11 17:03:19 -07002663
2664 # If the source dir doesn't exist, create an empty dir.
2665 if name in symlink_dirs and not os.path.lexists(src):
2666 os.makedirs(src)
2667
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002668 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002669 platform_utils.symlink(
2670 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002671 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002672 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002673 shutil.copytree(src, dst)
2674 elif os.path.isfile(src):
2675 shutil.copy(src, dst)
2676
Conley Owens80b87fe2014-05-09 17:13:44 -07002677 # If the source file doesn't exist, ensure the destination
2678 # file doesn't either.
2679 if name in symlink_files and not os.path.lexists(src):
2680 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002681 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002682 except OSError:
2683 pass
2684
David James8d201162013-10-11 17:03:19 -07002685 except OSError as e:
2686 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002687 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002688 else:
2689 raise
2690
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002691 def _InitGitWorktree(self):
2692 """Init the project using git worktrees."""
2693 self.bare_git.worktree('prune')
2694 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2695 self.worktree, self.GetRevisionId())
2696
2697 # Rewrite the internal state files to use relative paths between the
2698 # checkouts & worktrees.
2699 dotgit = os.path.join(self.worktree, '.git')
2700 with open(dotgit, 'r') as fp:
2701 # Figure out the checkout->worktree path.
2702 setting = fp.read()
2703 assert setting.startswith('gitdir:')
2704 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002705 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2706 # of file permissions. Delete it and recreate it from scratch to avoid.
2707 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002708 # Use relative path from checkout->worktree & maintain Unix line endings
2709 # on all OS's to match git behavior.
2710 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002711 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2712 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002713 # Use relative path from worktree->checkout & maintain Unix line endings
2714 # on all OS's to match git behavior.
2715 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002716 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2717
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002718 self._InitMRef()
2719
Martin Kellye4e94d22017-03-21 16:05:12 -07002720 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002721 realdotgit = os.path.join(self.worktree, '.git')
2722 tmpdotgit = realdotgit + '.tmp'
2723 init_dotgit = not os.path.exists(realdotgit)
2724 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002725 if self.use_git_worktrees:
2726 self._InitGitWorktree()
2727 self._CopyAndLinkFiles()
2728 return
2729
Mike Frysingerf4545122019-11-11 04:34:16 -05002730 dotgit = tmpdotgit
2731 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2732 os.makedirs(tmpdotgit)
2733 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2734 copy_all=False)
2735 else:
2736 dotgit = realdotgit
2737
Kevin Degib1a07b82015-07-27 13:33:43 -06002738 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002739 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2740 except GitError as e:
2741 if force_sync and not init_dotgit:
2742 try:
2743 platform_utils.rmtree(dotgit)
2744 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002745 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002746 raise e
2747 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002748
Mike Frysingerf4545122019-11-11 04:34:16 -05002749 if init_dotgit:
2750 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002751
Mike Frysingerf4545122019-11-11 04:34:16 -05002752 # Now that the .git dir is fully set up, move it to its final home.
2753 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002754
Mike Frysingerf4545122019-11-11 04:34:16 -05002755 # Finish checking out the worktree.
2756 cmd = ['read-tree', '--reset', '-u']
2757 cmd.append('-v')
2758 cmd.append(HEAD)
2759 if GitCommand(self, cmd).Wait() != 0:
2760 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002761
Mike Frysingerf4545122019-11-11 04:34:16 -05002762 if submodules:
2763 self._SyncSubmodules(quiet=True)
2764 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765
Renaud Paquay788e9622017-01-27 11:41:12 -08002766 def _get_symlink_error_message(self):
2767 if platform_utils.isWindows():
2768 return ('Unable to create symbolic link. Please re-run the command as '
2769 'Administrator, or see '
2770 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2771 'for other options.')
2772 return 'filesystem must support symlinks'
2773
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002774 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002775 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002776
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002777 def _revlist(self, *args, **kw):
2778 a = []
2779 a.extend(args)
2780 a.append('--')
2781 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002782
2783 @property
2784 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002785 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002786
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002787 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002788 """Get logs between two revisions of this project."""
2789 comp = '..'
2790 if rev1:
2791 revs = [rev1]
2792 if rev2:
2793 revs.extend([comp, rev2])
2794 cmd = ['log', ''.join(revs)]
2795 out = DiffColoring(self.config)
2796 if out.is_on and color:
2797 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002798 if pretty_format is not None:
2799 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002800 if oneline:
2801 cmd.append('--oneline')
2802
2803 try:
2804 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2805 if log.Wait() == 0:
2806 return log.stdout
2807 except GitError:
2808 # worktree may not exist if groups changed for example. In that case,
2809 # try in gitdir instead.
2810 if not os.path.exists(self.worktree):
2811 return self.bare_git.log(*cmd[1:])
2812 else:
2813 raise
2814 return None
2815
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002816 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2817 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002818 """Get the list of logs from this revision to given revisionId"""
2819 logs = {}
2820 selfId = self.GetRevisionId(self._allrefs)
2821 toId = toProject.GetRevisionId(toProject._allrefs)
2822
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002823 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2824 pretty_format=pretty_format)
2825 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2826 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002827 return logs
2828
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002829 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002830
David James8d201162013-10-11 17:03:19 -07002831 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002832 self._project = project
2833 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002834 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002835
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002836 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2837 def __getstate__(self):
2838 return (self._project, self._bare, self._gitdir)
2839
2840 def __setstate__(self, state):
2841 self._project, self._bare, self._gitdir = state
2842
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002843 def LsOthers(self):
2844 p = GitCommand(self._project,
2845 ['ls-files',
2846 '-z',
2847 '--others',
2848 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002849 bare=False,
David James8d201162013-10-11 17:03:19 -07002850 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002851 capture_stdout=True,
2852 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002853 if p.Wait() == 0:
2854 out = p.stdout
2855 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002856 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002857 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002858 return []
2859
2860 def DiffZ(self, name, *args):
2861 cmd = [name]
2862 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002863 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002864 cmd.extend(args)
2865 p = GitCommand(self._project,
2866 cmd,
David James8d201162013-10-11 17:03:19 -07002867 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002868 bare=False,
2869 capture_stdout=True,
2870 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002871 try:
2872 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002873 if not hasattr(out, 'encode'):
2874 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002875 r = {}
2876 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002877 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002879 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002880 info = next(out)
2881 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002882 except StopIteration:
2883 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002884
2885 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002886
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002887 def __init__(self, path, omode, nmode, oid, nid, state):
2888 self.path = path
2889 self.src_path = None
2890 self.old_mode = omode
2891 self.new_mode = nmode
2892 self.old_id = oid
2893 self.new_id = nid
2894
2895 if len(state) == 1:
2896 self.status = state
2897 self.level = None
2898 else:
2899 self.status = state[:1]
2900 self.level = state[1:]
2901 while self.level.startswith('0'):
2902 self.level = self.level[1:]
2903
2904 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002905 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002906 if info.status in ('R', 'C'):
2907 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002908 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002909 r[info.path] = info
2910 return r
2911 finally:
2912 p.Wait()
2913
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002914 def GetDotgitPath(self, subpath=None):
2915 """Return the full path to the .git dir.
2916
2917 As a convenience, append |subpath| if provided.
2918 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002919 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002920 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002921 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002922 dotgit = os.path.join(self._project.worktree, '.git')
2923 if os.path.isfile(dotgit):
2924 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2925 with open(dotgit) as fp:
2926 setting = fp.read()
2927 assert setting.startswith('gitdir:')
2928 gitdir = setting.split(':', 1)[1].strip()
2929 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
2930
2931 return dotgit if subpath is None else os.path.join(dotgit, subpath)
2932
2933 def GetHead(self):
2934 """Return the ref that HEAD points to."""
2935 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002936 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002937 with open(path) as fd:
2938 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002939 except IOError as e:
2940 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002941 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302942 line = line.decode()
2943 except AttributeError:
2944 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002945 if line.startswith('ref: '):
2946 return line[5:-1]
2947 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002948
2949 def SetHead(self, ref, message=None):
2950 cmdv = []
2951 if message is not None:
2952 cmdv.extend(['-m', message])
2953 cmdv.append(HEAD)
2954 cmdv.append(ref)
2955 self.symbolic_ref(*cmdv)
2956
2957 def DetachHead(self, new, message=None):
2958 cmdv = ['--no-deref']
2959 if message is not None:
2960 cmdv.extend(['-m', message])
2961 cmdv.append(HEAD)
2962 cmdv.append(new)
2963 self.update_ref(*cmdv)
2964
2965 def UpdateRef(self, name, new, old=None,
2966 message=None,
2967 detach=False):
2968 cmdv = []
2969 if message is not None:
2970 cmdv.extend(['-m', message])
2971 if detach:
2972 cmdv.append('--no-deref')
2973 cmdv.append(name)
2974 cmdv.append(new)
2975 if old is not None:
2976 cmdv.append(old)
2977 self.update_ref(*cmdv)
2978
2979 def DeleteRef(self, name, old=None):
2980 if not old:
2981 old = self.rev_parse(name)
2982 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002983 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002984
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002985 def rev_list(self, *args, **kw):
2986 if 'format' in kw:
2987 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2988 else:
2989 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002990 cmdv.extend(args)
2991 p = GitCommand(self._project,
2992 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002993 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002994 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002995 capture_stdout=True,
2996 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002997 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002998 raise GitError('%s rev-list %s: %s' %
2999 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003000 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003001
3002 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003003 """Allow arbitrary git commands using pythonic syntax.
3004
3005 This allows you to do things like:
3006 git_obj.rev_parse('HEAD')
3007
3008 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3009 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003010 Any other positional arguments will be passed to the git command, and the
3011 following keyword arguments are supported:
3012 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003013
3014 Args:
3015 name: The name of the git command to call. Any '_' characters will
3016 be replaced with '-'.
3017
3018 Returns:
3019 A callable object that will try to call git with the named command.
3020 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003021 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003022
Dave Borowitz091f8932012-10-23 17:01:04 -07003023 def runner(*args, **kwargs):
3024 cmdv = []
3025 config = kwargs.pop('config', None)
3026 for k in kwargs:
3027 raise TypeError('%s() got an unexpected keyword argument %r'
3028 % (name, k))
3029 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303030 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003031 cmdv.append('-c')
3032 cmdv.append('%s=%s' % (k, v))
3033 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003034 cmdv.extend(args)
3035 p = GitCommand(self._project,
3036 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003037 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003038 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003039 capture_stdout=True,
3040 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003041 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003042 raise GitError('%s %s: %s' %
3043 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003044 r = p.stdout
3045 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3046 return r[:-1]
3047 return r
3048 return runner
3049
3050
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003051class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003052
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003053 def __str__(self):
3054 return 'prior sync failed; rebase still in progress'
3055
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003056
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003057class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003058
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003059 def __str__(self):
3060 return 'contains uncommitted changes'
3061
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003062
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003063class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003064
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003065 def __init__(self, project, text):
3066 self.project = project
3067 self.text = text
3068
3069 def Print(self, syncbuf):
3070 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3071 syncbuf.out.nl()
3072
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003073
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003074class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003075
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003076 def __init__(self, project, why):
3077 self.project = project
3078 self.why = why
3079
3080 def Print(self, syncbuf):
3081 syncbuf.out.fail('error: %s/: %s',
3082 self.project.relpath,
3083 str(self.why))
3084 syncbuf.out.nl()
3085
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003086
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003087class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003088
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003089 def __init__(self, project, action):
3090 self.project = project
3091 self.action = action
3092
3093 def Run(self, syncbuf):
3094 out = syncbuf.out
3095 out.project('project %s/', self.project.relpath)
3096 out.nl()
3097 try:
3098 self.action()
3099 out.nl()
3100 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003101 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003102 out.nl()
3103 return False
3104
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003105
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003106class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003107
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003108 def __init__(self, config):
3109 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003110 self.project = self.printer('header', attr='bold')
3111 self.info = self.printer('info')
3112 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003113
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003114
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003115class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003116
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003117 def __init__(self, config, detach_head=False):
3118 self._messages = []
3119 self._failures = []
3120 self._later_queue1 = []
3121 self._later_queue2 = []
3122
3123 self.out = _SyncColoring(config)
3124 self.out.redirect(sys.stderr)
3125
3126 self.detach_head = detach_head
3127 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003128 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003129
3130 def info(self, project, fmt, *args):
3131 self._messages.append(_InfoMessage(project, fmt % args))
3132
3133 def fail(self, project, err=None):
3134 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003135 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003136
3137 def later1(self, project, what):
3138 self._later_queue1.append(_Later(project, what))
3139
3140 def later2(self, project, what):
3141 self._later_queue2.append(_Later(project, what))
3142
3143 def Finish(self):
3144 self._PrintMessages()
3145 self._RunLater()
3146 self._PrintMessages()
3147 return self.clean
3148
David Rileye0684ad2017-04-05 00:02:59 -07003149 def Recently(self):
3150 recent_clean = self.recent_clean
3151 self.recent_clean = True
3152 return recent_clean
3153
3154 def _MarkUnclean(self):
3155 self.clean = False
3156 self.recent_clean = False
3157
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158 def _RunLater(self):
3159 for q in ['_later_queue1', '_later_queue2']:
3160 if not self._RunQueue(q):
3161 return
3162
3163 def _RunQueue(self, queue):
3164 for m in getattr(self, queue):
3165 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003166 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003167 return False
3168 setattr(self, queue, [])
3169 return True
3170
3171 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003172 if self._messages or self._failures:
3173 if os.isatty(2):
3174 self.out.write(progress.CSI_ERASE_LINE)
3175 self.out.write('\r')
3176
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003177 for m in self._messages:
3178 m.Print(self)
3179 for m in self._failures:
3180 m.Print(self)
3181
3182 self._messages = []
3183 self._failures = []
3184
3185
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003186class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003188 """A special project housed under .repo.
3189 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003190
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003191 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003192 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003193 manifest=manifest,
3194 name=name,
3195 gitdir=gitdir,
3196 objdir=gitdir,
3197 worktree=worktree,
3198 remote=RemoteSpec('origin'),
3199 relpath='.repo/%s' % name,
3200 revisionExpr='refs/heads/master',
3201 revisionId=None,
3202 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003203
3204 def PreSync(self):
3205 if self.Exists:
3206 cb = self.CurrentBranch
3207 if cb:
3208 base = self.GetBranch(cb).merge
3209 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003210 self.revisionExpr = base
3211 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003212
Martin Kelly224a31a2017-07-10 14:46:25 -07003213 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003214 """ Prepare MetaProject for manifest branch switch
3215 """
3216
3217 # detach and delete manifest branch, allowing a new
3218 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003219 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003220 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003221 syncbuf.Finish()
3222
3223 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003224 ['update-ref', '-d', 'refs/heads/default'],
3225 capture_stdout=True,
3226 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003227
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003228 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003229 def LastFetch(self):
3230 try:
3231 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3232 return os.path.getmtime(fh)
3233 except OSError:
3234 return 0
3235
3236 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003237 def HasChanges(self):
3238 """Has the remote received new commits not yet checked out?
3239 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003240 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003241 return False
3242
David Pursehouse8a68ff92012-09-24 12:15:13 +09003243 all_refs = self.bare_ref.all
3244 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003245 head = self.work_git.GetHead()
3246 if head.startswith(R_HEADS):
3247 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003248 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003249 except KeyError:
3250 head = None
3251
3252 if revid == head:
3253 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003254 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003255 return True
3256 return False