blob: b81c10a1a226fb0f9f9bd995548359225d01964e [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080015import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070016import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070017import glob
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070019import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
21import shutil
22import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020025import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080026import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070027import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040028import urllib.parse
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070031from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070032from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
33 ID_RE
Remy Bohmer16c13282020-09-10 10:38:04 +020034from error import GitError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040035from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080036from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070037import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040038import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040039from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050041from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070043
George Engelbrecht9bc283e2020-04-02 12:36:09 -060044# Maximum sleep time allowed during retries.
45MAXIMUM_RETRY_SLEEP_SEC = 3600.0
46# +-10% random jitter is added to each Fetches retry sleep duration.
47RETRY_JITTER_PERCENT = 0.1
48
49
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070050def _lwrite(path, content):
51 lock = '%s.lock' % path
52
Remy Bohmer169b0212020-11-21 10:57:52 +010053 # Maintain Unix line endings on all OS's to match git behavior.
54 with open(lock, 'w', newline='\n') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070055 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070056
57 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070058 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080060 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070061 raise
62
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070063
Shawn O. Pearce48244782009-04-16 08:25:57 -070064def _error(fmt, *args):
65 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070066 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070067
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070068
David Pursehousef33929d2015-08-24 14:39:14 +090069def _warn(fmt, *args):
70 msg = fmt % args
71 print('warn: %s' % msg, file=sys.stderr)
72
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070073
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074def not_rev(r):
75 return '^' + r
76
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070077
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080078def sq(r):
79 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080080
David Pursehouse819827a2020-02-12 15:20:19 +090081
Jonathan Nieder93719792015-03-17 11:29:58 -070082_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
84
Jonathan Nieder93719792015-03-17 11:29:58 -070085def _ProjectHooks():
86 """List the hooks present in the 'hooks' directory.
87
88 These hooks are project hooks and are copied to the '.git/hooks' directory
89 of all subprojects.
90
91 This function caches the list of hooks (based on the contents of the
92 'repo/hooks' directory) on the first call.
93
94 Returns:
95 A list of absolute paths to all of the files in the hooks directory.
96 """
97 global _project_hook_list
98 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -070099 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700100 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700101 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700102 return _project_hook_list
103
104
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700105class DownloadedChange(object):
106 _commit_cache = None
107
108 def __init__(self, project, base, change_id, ps_id, commit):
109 self.project = project
110 self.base = base
111 self.change_id = change_id
112 self.ps_id = ps_id
113 self.commit = commit
114
115 @property
116 def commits(self):
117 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700118 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
119 '--abbrev-commit',
120 '--pretty=oneline',
121 '--reverse',
122 '--date-order',
123 not_rev(self.base),
124 self.commit,
125 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700126 return self._commit_cache
127
128
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129class ReviewableBranch(object):
130 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400131 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132
133 def __init__(self, project, branch, base):
134 self.project = project
135 self.branch = branch
136 self.base = base
137
138 @property
139 def name(self):
140 return self.branch.name
141
142 @property
143 def commits(self):
144 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400145 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
146 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
147 try:
148 self._commit_cache = self.project.bare_git.rev_list(*args)
149 except GitError:
150 # We weren't able to probe the commits for this branch. Was it tracking
151 # a branch that no longer exists? If so, return no commits. Otherwise,
152 # rethrow the error as we don't know what's going on.
153 if self.base_exists:
154 raise
155
156 self._commit_cache = []
157
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 return self._commit_cache
159
160 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800161 def unabbrev_commits(self):
162 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700163 for commit in self.project.bare_git.rev_list(not_rev(self.base),
164 R_HEADS + self.name,
165 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800166 r[commit[0:8]] = commit
167 return r
168
169 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 return self.project.bare_git.log('--pretty=format:%cd',
172 '-n', '1',
173 R_HEADS + self.name,
174 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175
Mike Frysinger6da17752019-09-11 18:43:17 -0400176 @property
177 def base_exists(self):
178 """Whether the branch we're tracking exists.
179
180 Normally it should, but sometimes branches we track can get deleted.
181 """
182 if self._base_exists is None:
183 try:
184 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
185 # If we're still here, the base branch exists.
186 self._base_exists = True
187 except GitError:
188 # If we failed to verify, the base branch doesn't exist.
189 self._base_exists = False
190
191 return self._base_exists
192
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700193 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500194 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700195 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500196 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500197 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200198 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700199 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200200 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200201 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800202 validate_certs=True,
203 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500204 self.project.UploadForReview(branch=self.name,
205 people=people,
206 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700207 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500208 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500209 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200210 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700211 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200212 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200213 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800214 validate_certs=validate_certs,
215 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700217 def GetPublishedRefs(self):
218 refs = {}
219 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700220 self.branch.remote.SshReviewUrl(self.project.UserEmail),
221 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700222 for line in output.split('\n'):
223 try:
224 (sha, ref) = line.split()
225 refs[sha] = ref
226 except ValueError:
227 pass
228
229 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700231
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500235 super().__init__(config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100236 self.project = self.printer('header', attr='bold')
237 self.branch = self.printer('header', attr='bold')
238 self.nobranch = self.printer('nobranch', fg='red')
239 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240
Anthony King7bdac712014-07-16 12:56:40 +0100241 self.added = self.printer('added', fg='green')
242 self.changed = self.printer('changed', fg='red')
243 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
245
246class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -0500249 super().__init__(config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100250 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400251 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700253
Anthony King7bdac712014-07-16 12:56:40 +0100254class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
James W. Mills24c13082012-04-12 15:04:13 -0500256 def __init__(self, name, value, keep):
257 self.name = name
258 self.value = value
259 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Mike Frysingere6a202f2019-08-02 15:57:57 -0400262def _SafeExpandPath(base, subpath, skipfinal=False):
263 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
Mike Frysingere6a202f2019-08-02 15:57:57 -0400265 We make sure no intermediate symlinks are traversed, and that the final path
266 is not a special file (e.g. not a socket or fifo).
267
268 NB: We rely on a number of paths already being filtered out while parsing the
269 manifest. See the validation logic in manifest_xml.py for more details.
270 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500271 # Split up the path by its components. We can't use os.path.sep exclusively
272 # as some platforms (like Windows) will convert / to \ and that bypasses all
273 # our constructed logic here. Especially since manifest authors only use
274 # / in their paths.
275 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
276 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400277 if skipfinal:
278 # Whether the caller handles the final component itself.
279 finalpart = components.pop()
280
281 path = base
282 for part in components:
283 if part in {'.', '..'}:
284 raise ManifestInvalidPathError(
285 '%s: "%s" not allowed in paths' % (subpath, part))
286
287 path = os.path.join(path, part)
288 if platform_utils.islink(path):
289 raise ManifestInvalidPathError(
290 '%s: traversing symlinks not allow' % (path,))
291
292 if os.path.exists(path):
293 if not os.path.isfile(path) and not platform_utils.isdir(path):
294 raise ManifestInvalidPathError(
295 '%s: only regular files & directories allowed' % (path,))
296
297 if skipfinal:
298 path = os.path.join(path, finalpart)
299
300 return path
301
302
303class _CopyFile(object):
304 """Container for <copyfile> manifest element."""
305
306 def __init__(self, git_worktree, src, topdir, dest):
307 """Register a <copyfile> request.
308
309 Args:
310 git_worktree: Absolute path to the git project checkout.
311 src: Relative path under |git_worktree| of file to read.
312 topdir: Absolute path to the top of the repo client checkout.
313 dest: Relative path under |topdir| of file to write.
314 """
315 self.git_worktree = git_worktree
316 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317 self.src = src
318 self.dest = dest
319
320 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400321 src = _SafeExpandPath(self.git_worktree, self.src)
322 dest = _SafeExpandPath(self.topdir, self.dest)
323
324 if platform_utils.isdir(src):
325 raise ManifestInvalidPathError(
326 '%s: copying from directory not supported' % (self.src,))
327 if platform_utils.isdir(dest):
328 raise ManifestInvalidPathError(
329 '%s: copying to directory not allowed' % (self.dest,))
330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 # copy file if it does not exist or is out of date
332 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
333 try:
334 # remove existing file first, since it might be read-only
335 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800336 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400337 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200338 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700339 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200340 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700341 shutil.copy(src, dest)
342 # make the file read-only
343 mode = os.stat(dest)[stat.ST_MODE]
344 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
345 os.chmod(dest, mode)
346 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700347 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Anthony King7bdac712014-07-16 12:56:40 +0100350class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400351 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700352
Mike Frysingere6a202f2019-08-02 15:57:57 -0400353 def __init__(self, git_worktree, src, topdir, dest):
354 """Register a <linkfile> request.
355
356 Args:
357 git_worktree: Absolute path to the git project checkout.
358 src: Target of symlink relative to path under |git_worktree|.
359 topdir: Absolute path to the top of the repo client checkout.
360 dest: Relative path under |topdir| of symlink to create.
361 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700362 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400363 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500364 self.src = src
365 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500366
Wink Saville4c426ef2015-06-03 08:05:17 -0700367 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500368 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700369 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500370 try:
371 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800372 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800373 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500374 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700375 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700376 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700378 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700380 _error('Cannot link file %s to %s', relSrc, absDest)
381
382 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400383 """Link the self.src & self.dest paths.
384
385 Handles wild cards on the src linking all of the files in the source in to
386 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500388 # Some people use src="." to create stable links to projects. Lets allow
389 # that but reject all other uses of "." to keep things simple.
390 if self.src == '.':
391 src = self.git_worktree
392 else:
393 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400394
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300395 if not glob.has_magic(src):
396 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400397 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
398 # dest & src are absolute paths at this point. Make sure the target of
399 # the symlink is relative in the context of the repo client checkout.
400 relpath = os.path.relpath(src, os.path.dirname(dest))
401 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700402 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300404 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 if os.path.exists(dest) and not platform_utils.isdir(dest):
406 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700407 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 # Create a releative path from source dir to destination dir
410 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400411 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700412
413 # Get the source file name
414 srcFile = os.path.basename(absSrcFile)
415
416 # Now form the final full paths to srcFile. They will be
417 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419 relSrc = os.path.join(relSrcDir, srcFile)
420 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500421
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700422
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700423class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700424
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425 def __init__(self,
426 name,
Anthony King7bdac712014-07-16 12:56:40 +0100427 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700428 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100429 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700430 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700431 orig_name=None,
432 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700433 self.name = name
434 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700435 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700436 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100437 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700438 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700439 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700440
Ian Kasprzak0286e312021-02-05 10:06:18 -0800441
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700442class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600443 # These objects can be shared between several working trees.
444 shareable_files = ['description', 'info']
445 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
446 # These objects can only be used by a single working tree.
447 working_tree_files = ['config', 'packed-refs', 'shallow']
448 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700449
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700450 def __init__(self,
451 manifest,
452 name,
453 remote,
454 gitdir,
David James8d201162013-10-11 17:03:19 -0700455 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700456 worktree,
457 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700458 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800459 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100460 rebase=True,
461 groups=None,
462 sync_c=False,
463 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900464 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100465 clone_depth=None,
466 upstream=None,
467 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500468 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100469 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900470 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700471 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600472 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700473 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800474 """Init a Project object.
475
476 Args:
477 manifest: The XmlManifest object.
478 name: The `name` attribute of manifest.xml's project element.
479 remote: RemoteSpec object specifying its remote's properties.
480 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700481 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800482 worktree: Absolute path of git working tree.
483 relpath: Relative path of git working tree to repo's top directory.
484 revisionExpr: The `revision` attribute of manifest.xml's project element.
485 revisionId: git commit id for checking out.
486 rebase: The `rebase` attribute of manifest.xml's project element.
487 groups: The `groups` attribute of manifest.xml's project element.
488 sync_c: The `sync-c` attribute of manifest.xml's project element.
489 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900490 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800491 upstream: The `upstream` attribute of manifest.xml's project element.
492 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500493 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800494 is_derived: False if the project was explicitly defined in the manifest;
495 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400496 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900497 optimized_fetch: If True, when a project is set to a sha1 revision, only
498 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600499 retry_fetches: Retry remote fetches n times upon receiving transient error
500 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700501 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800502 """
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400503 self.client = self.manifest = manifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700504 self.name = name
505 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800506 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700507 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800508 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700509 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800510 else:
511 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700513 self.revisionExpr = revisionExpr
514
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700515 if revisionId is None \
516 and revisionExpr \
517 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700518 self.revisionId = revisionExpr
519 else:
520 self.revisionId = revisionId
521
Mike Pontillod3153822012-02-28 11:53:24 -0800522 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700523 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700524 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800525 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900526 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900527 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700528 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800529 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500530 # NB: Do not use this setting in __init__ to change behavior so that the
531 # manifest.git checkout can inspect & change it after instantiating. See
532 # the XmlManifest init code for more info.
533 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800534 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900535 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600536 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800537 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800538
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700539 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700540 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500541 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500542 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700543 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -0400544 defaults=self.client.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700545
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800546 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700547 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800548 else:
549 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700550 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700551 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700552 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400553 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700554 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700555
Doug Anderson37282b42011-03-04 11:54:18 -0800556 # This will be filled in if a project is later identified to be the
557 # project containing repo hooks.
558 self.enabled_repo_hooks = []
559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700560 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800561 def Derived(self):
562 return self.is_derived
563
564 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700565 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700566 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700567
568 @property
569 def CurrentBranch(self):
570 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400571
572 The branch name omits the 'refs/heads/' prefix.
573 None is returned if the project is on a detached HEAD, or if the work_git is
574 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400576 try:
577 b = self.work_git.GetHead()
578 except NoManifestException:
579 # If the local checkout is in a bad state, don't barf. Let the callers
580 # process this like the head is unreadable.
581 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582 if b.startswith(R_HEADS):
583 return b[len(R_HEADS):]
584 return None
585
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700586 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500587 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
588 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
589 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 def IsDirty(self, consider_untracked=True):
592 """Is the working directory modified in some way?
593 """
594 self.work_git.update_index('-q',
595 '--unmerged',
596 '--ignore-missing',
597 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900598 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599 return True
600 if self.work_git.DiffZ('diff-files'):
601 return True
602 if consider_untracked and self.work_git.LsOthers():
603 return True
604 return False
605
606 _userident_name = None
607 _userident_email = None
608
609 @property
610 def UserName(self):
611 """Obtain the user's personal name.
612 """
613 if self._userident_name is None:
614 self._LoadUserIdentity()
615 return self._userident_name
616
617 @property
618 def UserEmail(self):
619 """Obtain the user's email address. This is very likely
620 to be their Gerrit login.
621 """
622 if self._userident_email is None:
623 self._LoadUserIdentity()
624 return self._userident_email
625
626 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900627 u = self.bare_git.var('GIT_COMMITTER_IDENT')
628 m = re.compile("^(.*) <([^>]*)> ").match(u)
629 if m:
630 self._userident_name = m.group(1)
631 self._userident_email = m.group(2)
632 else:
633 self._userident_name = ''
634 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635
636 def GetRemote(self, name):
637 """Get the configuration for a single remote.
638 """
639 return self.config.GetRemote(name)
640
641 def GetBranch(self, name):
642 """Get the configuration for a single branch.
643 """
644 return self.config.GetBranch(name)
645
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700646 def GetBranches(self):
647 """Get all existing local branches.
648 """
649 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900650 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700651 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700652
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530653 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700654 if name.startswith(R_HEADS):
655 name = name[len(R_HEADS):]
656 b = self.GetBranch(name)
657 b.current = name == current
658 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900659 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700660 heads[name] = b
661
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530662 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700663 if name.startswith(R_PUB):
664 name = name[len(R_PUB):]
665 b = heads.get(name)
666 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700668
669 return heads
670
Colin Cross5acde752012-03-28 20:15:45 -0700671 def MatchesGroups(self, manifest_groups):
672 """Returns true if the manifest groups specified at init should cause
673 this project to be synced.
674 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700675 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -0700676
677 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700678 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700679 manifest_groups: "-group1,group2"
680 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500681
682 The special manifest group "default" will match any project that
683 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -0700684 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500685 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700686 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700687 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500688 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700689
Conley Owens971de8e2012-04-16 10:36:08 -0700690 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700691 for group in expanded_manifest_groups:
692 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700693 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700694 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700695 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700696
Conley Owens971de8e2012-04-16 10:36:08 -0700697 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700699# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700700 def UncommitedFiles(self, get_all=True):
701 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700703 Args:
704 get_all: a boolean, if True - get information about all different
705 uncommitted files. If False - return as soon as any kind of
706 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500707 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700708 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500709 self.work_git.update_index('-q',
710 '--unmerged',
711 '--ignore-missing',
712 '--refresh')
713 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700714 details.append("rebase in progress")
715 if not get_all:
716 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500717
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700718 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
719 if changes:
720 details.extend(changes)
721 if not get_all:
722 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500723
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700724 changes = self.work_git.DiffZ('diff-files').keys()
725 if changes:
726 details.extend(changes)
727 if not get_all:
728 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500729
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700730 changes = self.work_git.LsOthers()
731 if changes:
732 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500733
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700734 return details
735
736 def HasChanges(self):
737 """Returns true if there are uncommitted changes.
738 """
739 if self.UncommitedFiles(get_all=False):
740 return True
741 else:
742 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500743
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600744 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700745 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +0200746
747 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +0200748 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600749 quiet: If True then only print the project name. Do not print
750 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 """
Renaud Paquaybed8b622018-09-27 10:46:58 -0700752 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700753 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +0200754 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -0700755 print(file=output_redir)
756 print('project %s/' % self.relpath, file=output_redir)
757 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 return
759
760 self.work_git.update_index('-q',
761 '--unmerged',
762 '--ignore-missing',
763 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700764 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700765 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
766 df = self.work_git.DiffZ('diff-files')
767 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +0100768 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700769 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770
771 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700772 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +0200773 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -0700774 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700775
Andrew Wheeler4d5bb682012-02-27 13:52:22 -0600776 if quiet:
777 out.nl()
778 return 'DIRTY'
779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 branch = self.CurrentBranch
781 if branch is None:
782 out.nobranch('(*** NO BRANCH ***)')
783 else:
784 out.branch('branch %s', branch)
785 out.nl()
786
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700787 if rb:
788 out.important('prior sync failed; rebase still in progress')
789 out.nl()
790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700791 paths = list()
792 paths.extend(di.keys())
793 paths.extend(df.keys())
794 paths.extend(do)
795
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530796 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900797 try:
798 i = di[p]
799 except KeyError:
800 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700801
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900802 try:
803 f = df[p]
804 except KeyError:
805 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200806
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900807 if i:
808 i_status = i.status.upper()
809 else:
810 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700811
David Pursehouse5c6eeac2012-10-11 16:44:48 +0900812 if f:
813 f_status = f.status.lower()
814 else:
815 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816
817 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -0800818 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700819 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700820 else:
821 line = ' %s%s\t%s' % (i_status, f_status, p)
822
823 if i and not f:
824 out.added('%s', line)
825 elif (i and f) or (not i and f):
826 out.changed('%s', line)
827 elif not i and not f:
828 out.untracked('%s', line)
829 else:
830 out.write('%s', line)
831 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +0200832
Shawn O. Pearce161f4452009-04-10 17:41:44 -0700833 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500835 def PrintWorkTreeDiff(self, absolute_paths=False, output_redir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 """Prints the status of the repository to stdout.
837 """
838 out = DiffColoring(self.config)
Mike Frysinger69b4a9c2021-02-16 18:18:01 -0500839 if output_redir:
840 out.redirect(output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 cmd = ['diff']
842 if out.is_on:
843 cmd.append('--color')
844 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +0300845 if absolute_paths:
846 cmd.append('--src-prefix=a/%s/' % self.relpath)
847 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400849 try:
850 p = GitCommand(self,
851 cmd,
852 capture_stdout=True,
853 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -0500854 p.Wait()
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400855 except GitError as e:
856 out.nl()
857 out.project('project %s/' % self.relpath)
858 out.nl()
859 out.fail('%s', str(e))
860 out.nl()
861 return False
Mike Frysinger84230002021-02-16 17:08:35 -0500862 if p.stdout:
863 out.nl()
864 out.project('project %s/' % self.relpath)
865 out.nl()
866 out.write(p.stdout)
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400867 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700869# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +0900870 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 """Was the branch published (uploaded) for code review?
872 If so, returns the SHA-1 hash of the last published
873 state for the branch.
874 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700875 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900876 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700877 try:
878 return self.bare_git.rev_parse(key)
879 except GitError:
880 return None
881 else:
882 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900883 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -0700884 except KeyError:
885 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700886
David Pursehouse8a68ff92012-09-24 12:15:13 +0900887 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700888 """Prunes any stale published refs.
889 """
David Pursehouse8a68ff92012-09-24 12:15:13 +0900890 if all_refs is None:
891 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700892 heads = set()
893 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530894 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700895 if name.startswith(R_HEADS):
896 heads.add(name)
897 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900898 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530900 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901 n = name[len(R_PUB):]
902 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900903 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700904
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700905 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 """List any branches which can be uploaded for review.
907 """
908 heads = {}
909 pubed = {}
910
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530911 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700912 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900913 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700914 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900915 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916
917 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530918 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +0900919 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -0700921 if selected_branch and branch != selected_branch:
922 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700923
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800924 rb = self.GetUploadableBranch(branch)
925 if rb:
926 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 return ready
928
Shawn O. Pearce35f25962008-11-11 17:03:13 -0800929 def GetUploadableBranch(self, branch_name):
930 """Get a single uploadable branch, or None.
931 """
932 branch = self.GetBranch(branch_name)
933 base = branch.LocalMerge
934 if branch.LocalMerge:
935 rb = ReviewableBranch(self, branch, base)
936 if rb.commits:
937 return rb
938 return None
939
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700940 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +0100941 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -0500942 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -0700943 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500944 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500945 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200946 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700947 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200948 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200949 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800950 validate_certs=True,
951 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 """Uploads the named branch for code review.
953 """
954 if branch is None:
955 branch = self.CurrentBranch
956 if branch is None:
957 raise GitError('not currently on a branch')
958
959 branch = self.GetBranch(branch)
960 if not branch.LocalMerge:
961 raise GitError('branch %s does not track a remote' % branch.name)
962 if not branch.remote.review:
963 raise GitError('remote %s has no review url' % branch.remote.name)
964
Bryan Jacobsf609f912013-05-06 13:36:24 -0400965 if dest_branch is None:
966 dest_branch = self.dest_branch
967 if dest_branch is None:
968 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 if not dest_branch.startswith(R_HEADS):
970 dest_branch = R_HEADS + dest_branch
971
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800972 if not branch.remote.projectname:
973 branch.remote.projectname = self.name
974 branch.remote.Save()
975
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200976 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800977 if url is None:
978 raise UploadError('review not configured')
979 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -0500980 if dryrun:
981 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800982
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800983 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -0800984 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700985
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800986 for push_option in (push_options or []):
987 cmd.append('-o')
988 cmd.append(push_option)
989
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800990 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800991
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800992 if dest_branch.startswith(R_HEADS):
993 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -0700994
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -0500995 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -0800996 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800997 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -0800998 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -0500999 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001000 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001001
David Pursehousef25a3702018-11-14 19:01:22 -08001002 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001003 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001004 if notify:
1005 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001006 if private:
1007 opts += ['private']
1008 if wip:
1009 opts += ['wip']
1010 if opts:
1011 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001012 cmd.append(ref_spec)
1013
Anthony King7bdac712014-07-16 12:56:40 +01001014 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001015 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001016
Mike Frysingerd7f86832020-11-19 19:18:46 -05001017 if not dryrun:
1018 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1019 self.bare_git.UpdateRef(R_PUB + branch.name,
1020 R_HEADS + branch.name,
1021 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001022
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001023# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001024 def _ExtractArchive(self, tarpath, path=None):
1025 """Extract the given tar on its current location
1026
1027 Args:
1028 - tarpath: The path to the actual tar file
1029
1030 """
1031 try:
1032 with tarfile.open(tarpath, 'r') as tar:
1033 tar.extractall(path=path)
1034 return True
1035 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001036 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001037 return False
1038
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001039 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001040 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001041 verbose=False,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001042 output_redir=None,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001043 is_new=None,
1044 current_branch_only=False,
1045 force_sync=False,
1046 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001047 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001048 archive=False,
1049 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001050 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001051 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001052 submodules=False,
1053 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001054 """Perform only the network IO portion of the sync process.
1055 Local working directory/branch state is not affected.
1056 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001057 if archive and not isinstance(self, MetaProject):
1058 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001059 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001060 return False
1061
1062 name = self.relpath.replace('\\', '/')
1063 name = name.replace('/', '_')
1064 tarpath = '%s.tar' % name
1065 topdir = self.manifest.topdir
1066
1067 try:
1068 self._FetchArchive(tarpath, cwd=topdir)
1069 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001070 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001071 return False
1072
1073 # From now on, we only need absolute tarpath
1074 tarpath = os.path.join(topdir, tarpath)
1075
1076 if not self._ExtractArchive(tarpath, path=topdir):
1077 return False
1078 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001079 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001080 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001081 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001082 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001083 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001084 if is_new is None:
1085 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001086 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001087 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001088 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001089 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001091
1092 if is_new:
1093 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1094 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001095 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001096 # This works for both absolute and relative alternate directories.
1097 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001098 except IOError:
1099 alt_dir = None
1100 else:
1101 alt_dir = None
1102
Mike Frysingere50b6a72020-02-19 01:45:48 -05001103 if (clone_bundle
1104 and alt_dir is None
1105 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001106 is_new = False
1107
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001108 if not current_branch_only:
1109 if self.sync_c:
1110 current_branch_only = True
1111 elif not self.manifest._loaded:
1112 # Manifest cannot check defaults until it syncs.
1113 current_branch_only = False
1114 elif self.manifest.default.sync_c:
1115 current_branch_only = True
1116
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001117 if not self.sync_tags:
1118 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001119
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001120 if self.clone_depth:
1121 depth = self.clone_depth
1122 else:
1123 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1124
Mike Frysinger521d01b2020-02-17 01:51:49 -05001125 # See if we can skip the network fetch entirely.
1126 if not (optimized_fetch and
1127 (ID_RE.match(self.revisionExpr) and
1128 self._CheckForImmutableRevision())):
1129 if not self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05001130 initial=is_new,
1131 quiet=quiet, verbose=verbose, output_redir=output_redir,
1132 alt_dir=alt_dir, current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001133 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001134 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001135 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001136 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001137
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001138 mp = self.manifest.manifestProject
1139 dissociate = mp.config.GetBoolean('repo.dissociate')
1140 if dissociate:
1141 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1142 if os.path.exists(alternates_file):
1143 cmd = ['repack', '-a', '-d']
Mike Frysinger7b586f22021-02-23 18:38:39 -05001144 p = GitCommand(self, cmd, bare=True, capture_stdout=bool(output_redir),
1145 merge_output=bool(output_redir))
1146 if p.stdout and output_redir:
1147 buf.write(p.stdout)
1148 if p.Wait() != 0:
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001149 return False
1150 platform_utils.remove(alternates_file)
1151
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001152 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001153 self._InitMRef()
1154 else:
1155 self._InitMirrorHead()
1156 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001157 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001158 except OSError:
1159 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001161
1162 def PostRepoUpgrade(self):
1163 self._InitHooks()
1164
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001165 def _CopyAndLinkFiles(self):
Mike Frysinger8c1e9cb2020-09-06 14:53:18 -04001166 if self.client.isGitcClient:
Simran Basib9a1b732015-08-20 12:19:28 -07001167 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001168 for copyfile in self.copyfiles:
1169 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001170 for linkfile in self.linkfiles:
1171 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172
Julien Camperguedd654222014-01-09 16:21:37 +01001173 def GetCommitRevisionId(self):
1174 """Get revisionId of a commit.
1175
1176 Use this method instead of GetRevisionId to get the id of the commit rather
1177 than the id of the current git object (for example, a tag)
1178
1179 """
1180 if not self.revisionExpr.startswith(R_TAGS):
1181 return self.GetRevisionId(self._allrefs)
1182
1183 try:
1184 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1185 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001186 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1187 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001188
David Pursehouse8a68ff92012-09-24 12:15:13 +09001189 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001190 if self.revisionId:
1191 return self.revisionId
1192
1193 rem = self.GetRemote(self.remote.name)
1194 rev = rem.ToLocal(self.revisionExpr)
1195
David Pursehouse8a68ff92012-09-24 12:15:13 +09001196 if all_refs is not None and rev in all_refs:
1197 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001198
1199 try:
1200 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1201 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001202 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1203 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001204
Raman Tenneti6a872c92021-01-14 19:17:50 -08001205 def SetRevisionId(self, revisionId):
1206 self.revisionId = revisionId
1207
Martin Kellye4e94d22017-03-21 16:05:12 -07001208 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001209 """Perform only the local IO portion of the sync process.
1210 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001211 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001212 if not os.path.exists(self.gitdir):
1213 syncbuf.fail(self,
1214 'Cannot checkout %s due to missing network sync; Run '
1215 '`repo sync -n %s` first.' %
1216 (self.name, self.name))
1217 return
1218
Martin Kellye4e94d22017-03-21 16:05:12 -07001219 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001220 all_refs = self.bare_ref.all
1221 self.CleanPublishedCache(all_refs)
1222 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001223
David Pursehouse1d947b32012-10-25 12:23:11 +09001224 def _doff():
1225 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001226 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001227
Martin Kellye4e94d22017-03-21 16:05:12 -07001228 def _dosubmodules():
1229 self._SyncSubmodules(quiet=True)
1230
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001231 head = self.work_git.GetHead()
1232 if head.startswith(R_HEADS):
1233 branch = head[len(R_HEADS):]
1234 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001235 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001236 except KeyError:
1237 head = None
1238 else:
1239 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001241 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 # Currently on a detached HEAD. The user is assumed to
1243 # not have any local modifications worth worrying about.
1244 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001245 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001246 syncbuf.fail(self, _PriorSyncFailedError())
1247 return
1248
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001249 if head == revid:
1250 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001251 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001252 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001253 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001254 # The copy/linkfile config may have changed.
1255 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001256 return
1257 else:
1258 lost = self._revlist(not_rev(revid), HEAD)
1259 if lost:
1260 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001261
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001263 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001264 if submodules:
1265 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001266 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001267 syncbuf.fail(self, e)
1268 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001269 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001270 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001272 if head == revid:
1273 # No changes; don't do anything further.
1274 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001275 # The copy/linkfile config may have changed.
1276 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001277 return
1278
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001281 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001283 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001285 syncbuf.info(self,
1286 "leaving %s; does not track upstream",
1287 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001288 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001289 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001290 if submodules:
1291 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001292 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001293 syncbuf.fail(self, e)
1294 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001295 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001296 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001298 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001299
1300 # See if we can perform a fast forward merge. This can happen if our
1301 # branch isn't in the exact same state as we last published.
1302 try:
1303 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1304 # Skip the published logic.
1305 pub = False
1306 except GitError:
1307 pub = self.WasPublished(branch.name, all_refs)
1308
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001310 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 if not_merged:
1312 if upstream_gain:
1313 # The user has published this branch and some of those
1314 # commits are not yet merged upstream. We do not want
1315 # to rewrite the published commits so we punt.
1316 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001317 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001318 "branch %s is published (but not merged) and is now "
1319 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001320 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001321 elif pub == head:
1322 # All published commits are merged, and thus we are a
1323 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001324 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001325 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001326 if submodules:
1327 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001328 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001330 # Examine the local commits not in the remote. Find the
1331 # last one attributed to this user, if any.
1332 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001333 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001334 last_mine = None
1335 cnt_mine = 0
1336 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001337 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001338 if committer_email == self.UserEmail:
1339 last_mine = commit_id
1340 cnt_mine += 1
1341
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001342 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001343 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
1345 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001346 syncbuf.fail(self, _DirtyError())
1347 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001348
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001349 # If the upstream switched on us, warn the user.
1350 #
1351 if branch.merge != self.revisionExpr:
1352 if branch.merge and self.revisionExpr:
1353 syncbuf.info(self,
1354 'manifest switched %s...%s',
1355 branch.merge,
1356 self.revisionExpr)
1357 elif branch.merge:
1358 syncbuf.info(self,
1359 'manifest no longer tracks %s',
1360 branch.merge)
1361
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001362 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001363 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001364 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001365 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001366 syncbuf.info(self,
1367 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001368 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001369
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001370 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001371 if not ID_RE.match(self.revisionExpr):
1372 # in case of manifest sync the revisionExpr might be a SHA1
1373 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001374 if not branch.merge.startswith('refs/'):
1375 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 branch.Save()
1377
Mike Pontillod3153822012-02-28 11:53:24 -08001378 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001379 def _docopyandlink():
1380 self._CopyAndLinkFiles()
1381
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001382 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001383 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001384 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001385 if submodules:
1386 syncbuf.later2(self, _dosubmodules)
1387 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001388 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001389 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001390 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001391 if submodules:
1392 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001393 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001394 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001395 syncbuf.fail(self, e)
1396 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001397 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001398 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001399 if submodules:
1400 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001401
Mike Frysingere6a202f2019-08-02 15:57:57 -04001402 def AddCopyFile(self, src, dest, topdir):
1403 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001404
Mike Frysingere6a202f2019-08-02 15:57:57 -04001405 No filesystem changes occur here. Actual copying happens later on.
1406
1407 Paths should have basic validation run on them before being queued.
1408 Further checking will be handled when the actual copy happens.
1409 """
1410 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1411
1412 def AddLinkFile(self, src, dest, topdir):
1413 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1414
1415 No filesystem changes occur here. Actual linking happens later on.
1416
1417 Paths should have basic validation run on them before being queued.
1418 Further checking will be handled when the actual link happens.
1419 """
1420 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001421
James W. Mills24c13082012-04-12 15:04:13 -05001422 def AddAnnotation(self, name, value, keep):
1423 self.annotations.append(_Annotation(name, value, keep))
1424
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001425 def DownloadPatchSet(self, change_id, patch_id):
1426 """Download a single patch set of a single change to FETCH_HEAD.
1427 """
1428 remote = self.GetRemote(self.remote.name)
1429
1430 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001431 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001432 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001433 if GitCommand(self, cmd, bare=True).Wait() != 0:
1434 return None
1435 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001436 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001437 change_id,
1438 patch_id,
1439 self.bare_git.rev_parse('FETCH_HEAD'))
1440
Mike Frysingerc0d18662020-02-19 19:19:18 -05001441 def DeleteWorktree(self, quiet=False, force=False):
1442 """Delete the source checkout and any other housekeeping tasks.
1443
1444 This currently leaves behind the internal .repo/ cache state. This helps
1445 when switching branches or manifest changes get reverted as we don't have
1446 to redownload all the git objects. But we should do some GC at some point.
1447
1448 Args:
1449 quiet: Whether to hide normal messages.
1450 force: Always delete tree even if dirty.
1451
1452 Returns:
1453 True if the worktree was completely cleaned out.
1454 """
1455 if self.IsDirty():
1456 if force:
1457 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1458 (self.relpath,), file=sys.stderr)
1459 else:
1460 print('error: %s: Cannot remove project: uncommitted changes are '
1461 'present.\n' % (self.relpath,), file=sys.stderr)
1462 return False
1463
1464 if not quiet:
1465 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1466
1467 # Unlock and delink from the main worktree. We don't use git's worktree
1468 # remove because it will recursively delete projects -- we handle that
1469 # ourselves below. https://crbug.com/git/48
1470 if self.use_git_worktrees:
1471 needle = platform_utils.realpath(self.gitdir)
1472 # Find the git worktree commondir under .repo/worktrees/.
1473 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1474 assert output.startswith('worktree '), output
1475 commondir = output[9:]
1476 # Walk each of the git worktrees to see where they point.
1477 configs = os.path.join(commondir, 'worktrees')
1478 for name in os.listdir(configs):
1479 gitdir = os.path.join(configs, name, 'gitdir')
1480 with open(gitdir) as fp:
1481 relpath = fp.read().strip()
1482 # Resolve the checkout path and see if it matches this project.
1483 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1484 if fullpath == needle:
1485 platform_utils.rmtree(os.path.join(configs, name))
1486
1487 # Delete the .git directory first, so we're less likely to have a partially
1488 # working git repository around. There shouldn't be any git projects here,
1489 # so rmtree works.
1490
1491 # Try to remove plain files first in case of git worktrees. If this fails
1492 # for any reason, we'll fall back to rmtree, and that'll display errors if
1493 # it can't remove things either.
1494 try:
1495 platform_utils.remove(self.gitdir)
1496 except OSError:
1497 pass
1498 try:
1499 platform_utils.rmtree(self.gitdir)
1500 except OSError as e:
1501 if e.errno != errno.ENOENT:
1502 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1503 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1504 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1505 return False
1506
1507 # Delete everything under the worktree, except for directories that contain
1508 # another git project.
1509 dirs_to_remove = []
1510 failed = False
1511 for root, dirs, files in platform_utils.walk(self.worktree):
1512 for f in files:
1513 path = os.path.join(root, f)
1514 try:
1515 platform_utils.remove(path)
1516 except OSError as e:
1517 if e.errno != errno.ENOENT:
1518 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1519 failed = True
1520 dirs[:] = [d for d in dirs
1521 if not os.path.lexists(os.path.join(root, d, '.git'))]
1522 dirs_to_remove += [os.path.join(root, d) for d in dirs
1523 if os.path.join(root, d) not in dirs_to_remove]
1524 for d in reversed(dirs_to_remove):
1525 if platform_utils.islink(d):
1526 try:
1527 platform_utils.remove(d)
1528 except OSError as e:
1529 if e.errno != errno.ENOENT:
1530 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1531 failed = True
1532 elif not platform_utils.listdir(d):
1533 try:
1534 platform_utils.rmdir(d)
1535 except OSError as e:
1536 if e.errno != errno.ENOENT:
1537 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1538 failed = True
1539 if failed:
1540 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1541 file=sys.stderr)
1542 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1543 return False
1544
1545 # Try deleting parent dirs if they are empty.
1546 path = self.worktree
1547 while path != self.manifest.topdir:
1548 try:
1549 platform_utils.rmdir(path)
1550 except OSError as e:
1551 if e.errno != errno.ENOENT:
1552 break
1553 path = os.path.dirname(path)
1554
1555 return True
1556
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001557# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001558 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001559 """Create a new branch off the manifest's revision.
1560 """
Simran Basib9a1b732015-08-20 12:19:28 -07001561 if not branch_merge:
1562 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001563 head = self.work_git.GetHead()
1564 if head == (R_HEADS + name):
1565 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001566
David Pursehouse8a68ff92012-09-24 12:15:13 +09001567 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001568 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001569 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001570 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001571 capture_stdout=True,
1572 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001573
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001574 branch = self.GetBranch(name)
1575 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001576 branch.merge = branch_merge
1577 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1578 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001579
1580 if revision is None:
1581 revid = self.GetRevisionId(all_refs)
1582 else:
1583 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001584
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001585 if head.startswith(R_HEADS):
1586 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001587 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001588 except KeyError:
1589 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001590 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001591 ref = R_HEADS + name
1592 self.work_git.update_ref(ref, revid)
1593 self.work_git.symbolic_ref(HEAD, ref)
1594 branch.Save()
1595 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001596
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001597 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001598 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001599 capture_stdout=True,
1600 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001601 branch.Save()
1602 return True
1603 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604
Wink Saville02d79452009-04-10 13:01:24 -07001605 def CheckoutBranch(self, name):
1606 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001607
1608 Args:
1609 name: The name of the branch to checkout.
1610
1611 Returns:
1612 True if the checkout succeeded; False if it didn't; None if the branch
1613 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001614 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001615 rev = R_HEADS + name
1616 head = self.work_git.GetHead()
1617 if head == rev:
1618 # Already on the branch
1619 #
1620 return True
Wink Saville02d79452009-04-10 13:01:24 -07001621
David Pursehouse8a68ff92012-09-24 12:15:13 +09001622 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001623 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001624 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001625 except KeyError:
1626 # Branch does not exist in this project
1627 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001628 return None
Wink Saville02d79452009-04-10 13:01:24 -07001629
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001630 if head.startswith(R_HEADS):
1631 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001632 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001633 except KeyError:
1634 head = None
1635
1636 if head == revid:
1637 # Same revision; just update HEAD to point to the new
1638 # target branch, but otherwise take no other action.
1639 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05001640 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
1641 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001642 return True
1643
1644 return GitCommand(self,
1645 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001646 capture_stdout=True,
1647 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001648
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001649 def AbandonBranch(self, name):
1650 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001651
1652 Args:
1653 name: The name of the branch to abandon.
1654
1655 Returns:
1656 True if the abandon succeeded; False if it didn't; None if the branch
1657 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001658 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001659 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001660 all_refs = self.bare_ref.all
1661 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001662 # Doesn't exist
1663 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001664
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001665 head = self.work_git.GetHead()
1666 if head == rev:
1667 # We can't destroy the branch while we are sitting
1668 # on it. Switch to a detached HEAD.
1669 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001670 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001671
David Pursehouse8a68ff92012-09-24 12:15:13 +09001672 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001673 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05001674 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001675 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001676 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001677
1678 return GitCommand(self,
1679 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001680 capture_stdout=True,
1681 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 def PruneHeads(self):
1684 """Prune any topic branches already merged into upstream.
1685 """
1686 cb = self.CurrentBranch
1687 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001688 left = self._allrefs
1689 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001690 if name.startswith(R_HEADS):
1691 name = name[len(R_HEADS):]
1692 if cb is None or name != cb:
1693 kill.append(name)
1694
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001695 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001696 if cb is not None \
1697 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001698 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699 self.work_git.DetachHead(HEAD)
1700 kill.append(cb)
1701
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001702 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001703 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705 try:
1706 self.bare_git.DetachHead(rev)
1707
1708 b = ['branch', '-d']
1709 b.extend(kill)
1710 b = GitCommand(self, b, bare=True,
1711 capture_stdout=True,
1712 capture_stderr=True)
1713 b.Wait()
1714 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001715 if ID_RE.match(old):
1716 self.bare_git.DetachHead(old)
1717 else:
1718 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001719 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001720
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001721 for branch in kill:
1722 if (R_HEADS + branch) not in left:
1723 self.CleanPublishedCache()
1724 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725
1726 if cb and cb not in kill:
1727 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001728 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729
1730 kept = []
1731 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001732 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001733 branch = self.GetBranch(branch)
1734 base = branch.LocalMerge
1735 if not base:
1736 base = rev
1737 kept.append(ReviewableBranch(self, branch, base))
1738 return kept
1739
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001740# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001741 def GetRegisteredSubprojects(self):
1742 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001743
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001744 def rec(subprojects):
1745 if not subprojects:
1746 return
1747 result.extend(subprojects)
1748 for p in subprojects:
1749 rec(p.subprojects)
1750 rec(self.subprojects)
1751 return result
1752
1753 def _GetSubmodules(self):
1754 # Unfortunately we cannot call `git submodule status --recursive` here
1755 # because the working tree might not exist yet, and it cannot be used
1756 # without a working tree in its current implementation.
1757
1758 def get_submodules(gitdir, rev):
1759 # Parse .gitmodules for submodule sub_paths and sub_urls
1760 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1761 if not sub_paths:
1762 return []
1763 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1764 # revision of submodule repository
1765 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1766 submodules = []
1767 for sub_path, sub_url in zip(sub_paths, sub_urls):
1768 try:
1769 sub_rev = sub_revs[sub_path]
1770 except KeyError:
1771 # Ignore non-exist submodules
1772 continue
1773 submodules.append((sub_rev, sub_path, sub_url))
1774 return submodules
1775
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001776 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1777 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001778
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001779 def parse_gitmodules(gitdir, rev):
1780 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1781 try:
Anthony King7bdac712014-07-16 12:56:40 +01001782 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1783 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001784 except GitError:
1785 return [], []
1786 if p.Wait() != 0:
1787 return [], []
1788
1789 gitmodules_lines = []
1790 fd, temp_gitmodules_path = tempfile.mkstemp()
1791 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05001792 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001793 os.close(fd)
1794 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001795 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1796 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001797 if p.Wait() != 0:
1798 return [], []
1799 gitmodules_lines = p.stdout.split('\n')
1800 except GitError:
1801 return [], []
1802 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001803 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001804
1805 names = set()
1806 paths = {}
1807 urls = {}
1808 for line in gitmodules_lines:
1809 if not line:
1810 continue
1811 m = re_path.match(line)
1812 if m:
1813 names.add(m.group(1))
1814 paths[m.group(1)] = m.group(2)
1815 continue
1816 m = re_url.match(line)
1817 if m:
1818 names.add(m.group(1))
1819 urls[m.group(1)] = m.group(2)
1820 continue
1821 names = sorted(names)
1822 return ([paths.get(name, '') for name in names],
1823 [urls.get(name, '') for name in names])
1824
1825 def git_ls_tree(gitdir, rev, paths):
1826 cmd = ['ls-tree', rev, '--']
1827 cmd.extend(paths)
1828 try:
Anthony King7bdac712014-07-16 12:56:40 +01001829 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1830 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001831 except GitError:
1832 return []
1833 if p.Wait() != 0:
1834 return []
1835 objects = {}
1836 for line in p.stdout.split('\n'):
1837 if not line.strip():
1838 continue
1839 object_rev, object_path = line.split()[2:4]
1840 objects[object_path] = object_rev
1841 return objects
1842
1843 try:
1844 rev = self.GetRevisionId()
1845 except GitError:
1846 return []
1847 return get_submodules(self.gitdir, rev)
1848
1849 def GetDerivedSubprojects(self):
1850 result = []
1851 if not self.Exists:
1852 # If git repo does not exist yet, querying its submodules will
1853 # mess up its states; so return here.
1854 return result
1855 for rev, path, url in self._GetSubmodules():
1856 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07001857 relpath, worktree, gitdir, objdir = \
1858 self.manifest.GetSubprojectPaths(self, name, path)
1859 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001860 if project:
1861 result.extend(project.GetDerivedSubprojects())
1862 continue
David James8d201162013-10-11 17:03:19 -07001863
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08001864 if url.startswith('..'):
1865 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001866 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01001867 url=url,
Steve Raed6480452016-08-10 15:00:00 -07001868 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01001869 review=self.remote.review,
1870 revision=self.remote.revision)
1871 subproject = Project(manifest=self.manifest,
1872 name=name,
1873 remote=remote,
1874 gitdir=gitdir,
1875 objdir=objdir,
1876 worktree=worktree,
1877 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02001878 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01001879 revisionId=rev,
1880 rebase=self.rebase,
1881 groups=self.groups,
1882 sync_c=self.sync_c,
1883 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001884 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01001885 parent=self,
1886 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001887 result.append(subproject)
1888 result.extend(subproject.GetDerivedSubprojects())
1889 return result
1890
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001891# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05001892 def EnableRepositoryExtension(self, key, value='true', version=1):
1893 """Enable git repository extension |key| with |value|.
1894
1895 Args:
1896 key: The extension to enabled. Omit the "extensions." prefix.
1897 value: The value to use for the extension.
1898 version: The minimum git repository version needed.
1899 """
1900 # Make sure the git repo version is new enough already.
1901 found_version = self.config.GetInt('core.repositoryFormatVersion')
1902 if found_version is None:
1903 found_version = 0
1904 if found_version < version:
1905 self.config.SetString('core.repositoryFormatVersion', str(version))
1906
1907 # Enable the extension!
1908 self.config.SetString('extensions.%s' % (key,), value)
1909
Mike Frysinger50a81de2020-09-06 15:51:21 -04001910 def ResolveRemoteHead(self, name=None):
1911 """Find out what the default branch (HEAD) points to.
1912
1913 Normally this points to refs/heads/master, but projects are moving to main.
1914 Support whatever the server uses rather than hardcoding "master" ourselves.
1915 """
1916 if name is None:
1917 name = self.remote.name
1918
1919 # The output will look like (NB: tabs are separators):
1920 # ref: refs/heads/master HEAD
1921 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
1922 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
1923
1924 for line in output.splitlines():
1925 lhs, rhs = line.split('\t', 1)
1926 if rhs == 'HEAD' and lhs.startswith('ref:'):
1927 return lhs[4:].strip()
1928
1929 return None
1930
Zac Livingstone4332262017-06-16 08:56:09 -06001931 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05001932 try:
1933 # if revision (sha or tag) is not present then following function
1934 # throws an error.
Ian Kasprzak0286e312021-02-05 10:06:18 -08001935 self.bare_git.rev_list('-1', '--missing=allow-any',
1936 '%s^0' % self.revisionExpr, '--')
Chris AtLee2fb64662014-01-16 21:32:33 -05001937 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,
Mike Frysinger7b586f22021-02-23 18:38:39 -05001960 output_redir=None,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07001961 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001962 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001963 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001964 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04001965 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07001966 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001967 clone_filter=None,
1968 retry_fetches=2,
1969 retry_sleep_initial_sec=4.0,
1970 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001971 is_sha1 = False
1972 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001973 # The depth should not be used when fetching to a mirror because
1974 # it will result in a shallow repository that cannot be cloned or
1975 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001976 # The repo project should also never be synced with partial depth.
1977 if self.manifest.IsMirror or self.relpath == '.repo/repo':
1978 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09001979
Shawn Pearce69e04d82014-01-29 12:48:54 -08001980 if depth:
1981 current_branch_only = True
1982
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001983 if ID_RE.match(self.revisionExpr) is not None:
1984 is_sha1 = True
1985
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001986 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06001987 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001988 # this is a tag and its sha1 value should never change
1989 tag_name = self.revisionExpr[len(R_TAGS):]
1990
1991 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06001992 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05001993 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02001994 print('Skipped fetching project %s (already have persistent ref)'
1995 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07001996 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08001997 if is_sha1 and not depth:
1998 # When syncing a specific commit and --depth is not set:
1999 # * if upstream is explicitly specified and is not a sha1, fetch only
2000 # upstream as users expect only upstream to be fetch.
2001 # Note: The commit might not be in upstream in which case the sync
2002 # will fail.
2003 # * otherwise, fetch all branches to make sure we end up with the
2004 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002005 if self.upstream:
2006 current_branch_only = not ID_RE.match(self.upstream)
2007 else:
2008 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002009
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002010 if not name:
2011 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002012
2013 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002014 remote = self.GetRemote(name)
2015 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002016 ssh_proxy = True
2017
Shawn O. Pearce88443382010-10-08 10:02:09 +02002018 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002019 if alt_dir and 'objects' == os.path.basename(alt_dir):
2020 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002021 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2022 remote = self.GetRemote(name)
2023
David Pursehouse8a68ff92012-09-24 12:15:13 +09002024 all_refs = self.bare_ref.all
2025 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002026 tmp = set()
2027
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302028 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002029 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002030 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002031 all_refs[r] = ref_id
2032 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002033 continue
2034
David Pursehouse8a68ff92012-09-24 12:15:13 +09002035 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002036 continue
2037
David Pursehouse8a68ff92012-09-24 12:15:13 +09002038 r = 'refs/_alt/%s' % ref_id
2039 all_refs[r] = ref_id
2040 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002041 tmp.add(r)
2042
heping3d7bbc92017-04-12 19:51:47 +08002043 tmp_packed_lines = []
2044 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002045
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302046 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002047 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002048 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002049 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002050 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002051
heping3d7bbc92017-04-12 19:51:47 +08002052 tmp_packed = ''.join(tmp_packed_lines)
2053 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002054 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002055 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002056 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002057
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002058 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002059
Xin Li745be2e2019-06-03 11:24:30 -07002060 if clone_filter:
2061 git_require((2, 19, 0), fail=True, msg='partial clones')
2062 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002063 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002064
Conley Owensf97e8382015-01-21 11:12:46 -08002065 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002066 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002067 else:
2068 # If this repo has shallow objects, then we don't know which refs have
2069 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2070 # do this with projects that don't have shallow objects, since it is less
2071 # efficient.
2072 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2073 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002074
Mike Frysinger4847e052020-02-22 00:07:35 -05002075 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002076 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002077 if not quiet and sys.stdout.isatty():
2078 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002079 if not self.worktree:
2080 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002081 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002082
Mike Frysingere57f1142019-03-18 21:27:54 -04002083 if force_sync:
2084 cmd.append('--force')
2085
David Pursehouse74cfd272015-10-14 10:50:15 +09002086 if prune:
2087 cmd.append('--prune')
2088
Martin Kellye4e94d22017-03-21 16:05:12 -07002089 if submodules:
2090 cmd.append('--recurse-submodules=on-demand')
2091
Kuang-che Wu6856f982019-11-25 12:37:55 +08002092 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002093 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002094 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002095 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002096 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002097 spec.append('tag')
2098 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002099
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302100 if self.manifest.IsMirror and not current_branch_only:
2101 branch = None
2102 else:
2103 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002104 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002105 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002106 # Shallow checkout of a specific commit, fetch from that commit and not
2107 # the heads only as the commit might be deeper in the history.
2108 spec.append(branch)
2109 else:
2110 if is_sha1:
2111 branch = self.upstream
2112 if branch is not None and branch.strip():
2113 if not branch.startswith('refs/'):
2114 branch = R_HEADS + branch
2115 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2116
2117 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2118 # whole repo.
2119 if self.manifest.IsMirror and not spec:
2120 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2121
2122 # If using depth then we should not get all the tags since they may
2123 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002124 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002125 cmd.append('--no-tags')
2126 else:
2127 cmd.append('--tags')
2128 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2129
Conley Owens80b87fe2014-05-09 17:13:44 -07002130 cmd.extend(spec)
2131
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002132 # At least one retry minimum due to git remote prune.
2133 retry_fetches = max(retry_fetches, 2)
2134 retry_cur_sleep = retry_sleep_initial_sec
2135 ok = prune_tried = False
2136 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002137 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger7b586f22021-02-23 18:38:39 -05002138 merge_output=True, capture_stdout=quiet or bool(output_redir))
2139 if gitcmd.stdout and not quiet and output_redir:
2140 output_redir.write(gitcmd.stdout)
John L. Villalovos126e2982015-01-29 21:58:12 -08002141 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002142 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002143 ok = True
2144 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002145
2146 # Retry later due to HTTP 429 Too Many Requests.
Mike Frysinger92304bf2021-02-23 03:58:43 -05002147 elif (gitcmd.stdout and
2148 'error:' in gitcmd.stdout and
2149 'HTTP 429' in gitcmd.stdout):
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002150 if not quiet:
2151 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2152 file=sys.stderr)
2153 time.sleep(retry_cur_sleep)
2154 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2155 MAXIMUM_RETRY_SLEEP_SEC)
2156 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2157 RETRY_JITTER_PERCENT))
2158 continue
2159
2160 # If this is not last attempt, try 'git remote prune'.
2161 elif (try_n < retry_fetches - 1 and
Mike Frysinger92304bf2021-02-23 03:58:43 -05002162 gitcmd.stdout and
2163 'error:' in gitcmd.stdout and
2164 'git remote prune' in gitcmd.stdout and
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002165 not prune_tried):
2166 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002167 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002168 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002169 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002170 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002171 break
2172 continue
Brian Harring14a66742012-09-28 20:21:57 -07002173 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002174 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2175 # in sha1 mode, we just tried sync'ing from the upstream field; it
2176 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002177 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002178 elif ret < 0:
2179 # Git died with a signal, exit immediately
2180 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002181 if not verbose:
Mike Frysinger7b586f22021-02-23 18:38:39 -05002182 print('\n%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002183 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002184
2185 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002186 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002187 if old_packed != '':
2188 _lwrite(packed_refs, old_packed)
2189 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002190 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002191 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002192
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002193 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002194 # We just synced the upstream given branch; verify we
2195 # got what we wanted, else trigger a second run of all
2196 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002197 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002198 # Sync the current branch only with depth set to None.
2199 # We always pass depth=None down to avoid infinite recursion.
2200 return self._RemoteFetch(
Mike Frysinger7b586f22021-02-23 18:38:39 -05002201 name=name, quiet=quiet, verbose=verbose, output_redir=output_redir,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002202 current_branch_only=current_branch_only and depth,
2203 initial=False, alt_dir=alt_dir,
2204 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002205
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002206 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002207
Mike Frysingere50b6a72020-02-19 01:45:48 -05002208 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002209 if initial and \
2210 (self.manifest.manifestProject.config.GetString('repo.depth') or
2211 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002212 return False
2213
2214 remote = self.GetRemote(self.remote.name)
2215 bundle_url = remote.url + '/clone.bundle'
2216 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002217 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2218 'persistent-http',
2219 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002220 return False
2221
2222 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2223 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2224
2225 exist_dst = os.path.exists(bundle_dst)
2226 exist_tmp = os.path.exists(bundle_tmp)
2227
2228 if not initial and not exist_dst and not exist_tmp:
2229 return False
2230
2231 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002232 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2233 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002234 if not exist_dst:
2235 return False
2236
2237 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002238 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002239 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002240 if not quiet and sys.stdout.isatty():
2241 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002242 if not self.worktree:
2243 cmd.append('--update-head-ok')
2244 cmd.append(bundle_dst)
2245 for f in remote.fetch:
2246 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002247 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002248
2249 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002250 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002251 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002252 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002253 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002254 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002255
Mike Frysingere50b6a72020-02-19 01:45:48 -05002256 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002257 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002258 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002259
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002260 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002261 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002262 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002263 if os.path.exists(tmpPath):
2264 size = os.stat(tmpPath).st_size
2265 if size >= 1024:
2266 cmd += ['--continue-at', '%d' % (size,)]
2267 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002268 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002269 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002270 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002271 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002272 if proxy:
2273 cmd += ['--proxy', proxy]
2274 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2275 cmd += ['--proxy', os.environ['http_proxy']]
2276 if srcUrl.startswith('persistent-https'):
2277 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2278 elif srcUrl.startswith('persistent-http'):
2279 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002280 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002281
Dave Borowitz137d0132015-01-02 11:12:54 -08002282 if IsTrace():
2283 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002284 if verbose:
2285 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2286 stdout = None if verbose else subprocess.PIPE
2287 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002288 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002289 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002290 except OSError:
2291 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002292
Mike Frysingere50b6a72020-02-19 01:45:48 -05002293 (output, _) = proc.communicate()
2294 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002295
Dave Borowitz137d0132015-01-02 11:12:54 -08002296 if curlret == 22:
2297 # From curl man page:
2298 # 22: HTTP page not retrieved. The requested url was not found or
2299 # returned another error with the HTTP error code being 400 or above.
2300 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002301 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002302 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2303 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002304 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002305 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002306 elif curlret and not verbose and output:
2307 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002308
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002309 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002310 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002311 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002312 return True
2313 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002314 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002315 return False
2316 else:
2317 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002318
Kris Giesingc8d882a2014-12-23 13:02:32 -08002319 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002320 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002321 with open(path, 'rb') as f:
2322 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002323 return True
2324 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002325 if not quiet:
2326 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002327 return False
2328 except OSError:
2329 return False
2330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002331 def _Checkout(self, rev, quiet=False):
2332 cmd = ['checkout']
2333 if quiet:
2334 cmd.append('-q')
2335 cmd.append(rev)
2336 cmd.append('--')
2337 if GitCommand(self, cmd).Wait() != 0:
2338 if self._allrefs:
2339 raise GitError('%s checkout %s ' % (self.name, rev))
2340
Mike Frysinger915fda12020-03-22 12:15:20 -04002341 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002342 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002343 if ffonly:
2344 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002345 if record_origin:
2346 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002347 cmd.append(rev)
2348 cmd.append('--')
2349 if GitCommand(self, cmd).Wait() != 0:
2350 if self._allrefs:
2351 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2352
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302353 def _LsRemote(self, refs):
2354 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302355 p = GitCommand(self, cmd, capture_stdout=True)
2356 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002357 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302358 return None
2359
Anthony King7bdac712014-07-16 12:56:40 +01002360 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002361 cmd = ['revert']
2362 cmd.append('--no-edit')
2363 cmd.append(rev)
2364 cmd.append('--')
2365 if GitCommand(self, cmd).Wait() != 0:
2366 if self._allrefs:
2367 raise GitError('%s revert %s ' % (self.name, rev))
2368
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002369 def _ResetHard(self, rev, quiet=True):
2370 cmd = ['reset', '--hard']
2371 if quiet:
2372 cmd.append('-q')
2373 cmd.append(rev)
2374 if GitCommand(self, cmd).Wait() != 0:
2375 raise GitError('%s reset --hard %s ' % (self.name, rev))
2376
Martin Kellye4e94d22017-03-21 16:05:12 -07002377 def _SyncSubmodules(self, quiet=True):
2378 cmd = ['submodule', 'update', '--init', '--recursive']
2379 if quiet:
2380 cmd.append('-q')
2381 if GitCommand(self, cmd).Wait() != 0:
2382 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2383
Anthony King7bdac712014-07-16 12:56:40 +01002384 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002385 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002386 if onto is not None:
2387 cmd.extend(['--onto', onto])
2388 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002389 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002390 raise GitError('%s rebase %s ' % (self.name, upstream))
2391
Pierre Tardy3d125942012-05-04 12:18:12 +02002392 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002393 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002394 if ffonly:
2395 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002396 if GitCommand(self, cmd).Wait() != 0:
2397 raise GitError('%s merge %s ' % (self.name, head))
2398
David Pursehousee8ace262020-02-13 12:41:15 +09002399 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002400 init_git_dir = not os.path.exists(self.gitdir)
2401 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002402 try:
2403 # Initialize the bare repository, which contains all of the objects.
2404 if init_obj_dir:
2405 os.makedirs(self.objdir)
2406 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002407
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002408 if self.use_git_worktrees:
2409 # Set up the m/ space to point to the worktree-specific ref space.
2410 # We'll update the worktree-specific ref space on each checkout.
2411 if self.manifest.branch:
2412 self.bare_git.symbolic_ref(
2413 '-m', 'redirecting to worktree scope',
2414 R_M + self.manifest.branch,
2415 R_WORKTREE_M + self.manifest.branch)
2416
2417 # Enable per-worktree config file support if possible. This is more a
2418 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002419 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002420 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002421
Kevin Degib1a07b82015-07-27 13:33:43 -06002422 # If we have a separate directory to hold refs, initialize it as well.
2423 if self.objdir != self.gitdir:
2424 if init_git_dir:
2425 os.makedirs(self.gitdir)
2426
2427 if init_obj_dir or init_git_dir:
2428 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2429 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002430 try:
2431 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2432 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002433 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002434 print("Retrying clone after deleting %s" %
2435 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002436 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002437 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2438 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002439 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002440 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002441 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2442 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002443 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002444 raise e
2445 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002446
Kevin Degi384b3c52014-10-16 16:02:58 -06002447 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002448 mp = self.manifest.manifestProject
2449 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002450
Kevin Degib1a07b82015-07-27 13:33:43 -06002451 if ref_dir or mirror_git:
2452 if not mirror_git:
2453 mirror_git = os.path.join(ref_dir, self.name + '.git')
2454 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2455 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002456 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2457 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002458
Kevin Degib1a07b82015-07-27 13:33:43 -06002459 if os.path.exists(mirror_git):
2460 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002461 elif os.path.exists(repo_git):
2462 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002463 elif os.path.exists(worktrees_git):
2464 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002465 else:
2466 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002467
Kevin Degib1a07b82015-07-27 13:33:43 -06002468 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002469 if not os.path.isabs(ref_dir):
2470 # The alternate directory is relative to the object database.
2471 ref_dir = os.path.relpath(ref_dir,
2472 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002473 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2474 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002475
David Pursehousee8ace262020-02-13 12:41:15 +09002476 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002477
2478 m = self.manifest.manifestProject.config
2479 for key in ['user.name', 'user.email']:
2480 if m.Has(key, include_defaults=False):
2481 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002482 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002483 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Mike Frysinger38867fb2021-02-09 23:14:41 -05002484 self.config.SetBoolean('core.bare', True if self.manifest.IsMirror else None)
Kevin Degib1a07b82015-07-27 13:33:43 -06002485 except Exception:
2486 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002487 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002488 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002489 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002490 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002491
David Pursehousee8ace262020-02-13 12:41:15 +09002492 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002493 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002494 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002495
David Pursehousee8ace262020-02-13 12:41:15 +09002496 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002497 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002498 if not os.path.exists(hooks):
2499 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002500 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002501 name = os.path.basename(stock_hook)
2502
Victor Boivie65e0f352011-04-18 11:23:29 +02002503 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002504 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002505 # Don't install a Gerrit Code Review hook if this
2506 # project does not appear to use it for reviews.
2507 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002508 # Since the manifest project is one of those, but also
2509 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002510 continue
2511
2512 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002513 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002514 continue
2515 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002516 # If the files are the same, we'll leave it alone. We create symlinks
2517 # below by default but fallback to hardlinks if the OS blocks them.
2518 # So if we're here, it's probably because we made a hardlink below.
2519 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002520 if not quiet:
2521 _warn("%s: Not replacing locally modified %s hook",
2522 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002523 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002524 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002525 platform_utils.symlink(
2526 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002527 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002528 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002529 try:
2530 os.link(stock_hook, dst)
2531 except OSError:
2532 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002533 else:
2534 raise
2535
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002536 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002537 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002538 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002539 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002540 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002541 remote.review = self.remote.review
2542 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002543
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002544 if self.worktree:
2545 remote.ResetFetch(mirror=False)
2546 else:
2547 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002548 remote.Save()
2549
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002550 def _InitMRef(self):
2551 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002552 if self.use_git_worktrees:
2553 # We can't update this ref with git worktrees until it exists.
2554 # We'll wait until the initial checkout to set it.
2555 if not os.path.exists(self.worktree):
2556 return
2557
2558 base = R_WORKTREE_M
2559 active_git = self.work_git
Remy Böhmer1469c282020-12-15 18:49:02 +01002560
2561 self._InitAnyMRef(HEAD, self.bare_git, detach=True)
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002562 else:
2563 base = R_M
2564 active_git = self.bare_git
2565
2566 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002567
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002568 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002569 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002570
Remy Böhmer1469c282020-12-15 18:49:02 +01002571 def _InitAnyMRef(self, ref, active_git, detach=False):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002572 cur = self.bare_ref.symref(ref)
2573
2574 if self.revisionId:
2575 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2576 msg = 'manifest set to %s' % self.revisionId
2577 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002578 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002579 else:
2580 remote = self.GetRemote(self.remote.name)
2581 dst = remote.ToLocal(self.revisionExpr)
2582 if cur != dst:
2583 msg = 'manifest set to %s' % self.revisionExpr
Remy Böhmer1469c282020-12-15 18:49:02 +01002584 if detach:
2585 active_git.UpdateRef(ref, dst, message=msg, detach=True)
2586 else:
2587 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002588
Kevin Degi384b3c52014-10-16 16:02:58 -06002589 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002590 # Git worktrees don't use symlinks to share at all.
2591 if self.use_git_worktrees:
2592 return
2593
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002594 symlink_files = self.shareable_files[:]
2595 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002596 if share_refs:
2597 symlink_files += self.working_tree_files
2598 symlink_dirs += self.working_tree_dirs
2599 to_symlink = symlink_files + symlink_dirs
2600 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002601 # Try to self-heal a bit in simple cases.
2602 dst_path = os.path.join(destdir, name)
2603 src_path = os.path.join(srcdir, name)
2604
2605 if name in self.working_tree_dirs:
2606 # If the dir is missing under .repo/projects/, create it.
2607 if not os.path.exists(src_path):
2608 os.makedirs(src_path)
2609
2610 elif name in self.working_tree_files:
2611 # If it's a file under the checkout .git/ and the .repo/projects/ has
2612 # nothing, move the file under the .repo/projects/ tree.
2613 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2614 platform_utils.rename(dst_path, src_path)
2615
2616 # If the path exists under the .repo/projects/ and there's no symlink
2617 # under the checkout .git/, recreate the symlink.
2618 if name in self.working_tree_dirs or name in self.working_tree_files:
2619 if os.path.exists(src_path) and not os.path.exists(dst_path):
2620 platform_utils.symlink(
2621 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2622
2623 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002624 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002625 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002626 # Fail if the links are pointing to the wrong place
2627 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002628 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002629 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002630 'work tree. If you\'re comfortable with the '
2631 'possibility of losing the work tree\'s git metadata,'
2632 ' use `repo sync --force-sync {0}` to '
2633 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002634
David James8d201162013-10-11 17:03:19 -07002635 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2636 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2637
2638 Args:
2639 gitdir: The bare git repository. Must already be initialized.
2640 dotgit: The repository you would like to initialize.
2641 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2642 Only one work tree can store refs under a given |gitdir|.
2643 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2644 This saves you the effort of initializing |dotgit| yourself.
2645 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002646 symlink_files = self.shareable_files[:]
2647 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002648 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002649 symlink_files += self.working_tree_files
2650 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002651 to_symlink = symlink_files + symlink_dirs
2652
2653 to_copy = []
2654 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002655 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002656
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002657 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002658 for name in set(to_copy).union(to_symlink):
2659 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002660 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002661 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002662
Kevin Degi384b3c52014-10-16 16:02:58 -06002663 if os.path.lexists(dst):
2664 continue
David James8d201162013-10-11 17:03:19 -07002665
2666 # If the source dir doesn't exist, create an empty dir.
2667 if name in symlink_dirs and not os.path.lexists(src):
2668 os.makedirs(src)
2669
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002670 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002671 platform_utils.symlink(
2672 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002673 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002674 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002675 shutil.copytree(src, dst)
2676 elif os.path.isfile(src):
2677 shutil.copy(src, dst)
2678
Conley Owens80b87fe2014-05-09 17:13:44 -07002679 # If the source file doesn't exist, ensure the destination
2680 # file doesn't either.
2681 if name in symlink_files and not os.path.lexists(src):
2682 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002683 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002684 except OSError:
2685 pass
2686
David James8d201162013-10-11 17:03:19 -07002687 except OSError as e:
2688 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002689 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002690 else:
2691 raise
2692
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002693 def _InitGitWorktree(self):
2694 """Init the project using git worktrees."""
2695 self.bare_git.worktree('prune')
2696 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
2697 self.worktree, self.GetRevisionId())
2698
2699 # Rewrite the internal state files to use relative paths between the
2700 # checkouts & worktrees.
2701 dotgit = os.path.join(self.worktree, '.git')
2702 with open(dotgit, 'r') as fp:
2703 # Figure out the checkout->worktree path.
2704 setting = fp.read()
2705 assert setting.startswith('gitdir:')
2706 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05002707 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
2708 # of file permissions. Delete it and recreate it from scratch to avoid.
2709 platform_utils.remove(dotgit)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002710 # Use relative path from checkout->worktree & maintain Unix line endings
2711 # on all OS's to match git behavior.
2712 with open(dotgit, 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002713 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
2714 file=fp)
Remy Bohmer44bc9642020-11-20 21:19:10 +01002715 # Use relative path from worktree->checkout & maintain Unix line endings
2716 # on all OS's to match git behavior.
2717 with open(os.path.join(git_worktree_path, 'gitdir'), 'w', newline='\n') as fp:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002718 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
2719
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002720 self._InitMRef()
2721
Martin Kellye4e94d22017-03-21 16:05:12 -07002722 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002723 realdotgit = os.path.join(self.worktree, '.git')
2724 tmpdotgit = realdotgit + '.tmp'
2725 init_dotgit = not os.path.exists(realdotgit)
2726 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002727 if self.use_git_worktrees:
2728 self._InitGitWorktree()
2729 self._CopyAndLinkFiles()
2730 return
2731
Mike Frysingerf4545122019-11-11 04:34:16 -05002732 dotgit = tmpdotgit
2733 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2734 os.makedirs(tmpdotgit)
2735 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2736 copy_all=False)
2737 else:
2738 dotgit = realdotgit
2739
Kevin Degib1a07b82015-07-27 13:33:43 -06002740 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002741 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2742 except GitError as e:
2743 if force_sync and not init_dotgit:
2744 try:
2745 platform_utils.rmtree(dotgit)
2746 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002747 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002748 raise e
2749 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750
Mike Frysingerf4545122019-11-11 04:34:16 -05002751 if init_dotgit:
2752 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002753
Mike Frysingerf4545122019-11-11 04:34:16 -05002754 # Now that the .git dir is fully set up, move it to its final home.
2755 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002756
Mike Frysingerf4545122019-11-11 04:34:16 -05002757 # Finish checking out the worktree.
2758 cmd = ['read-tree', '--reset', '-u']
2759 cmd.append('-v')
2760 cmd.append(HEAD)
2761 if GitCommand(self, cmd).Wait() != 0:
2762 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002763
Mike Frysingerf4545122019-11-11 04:34:16 -05002764 if submodules:
2765 self._SyncSubmodules(quiet=True)
2766 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767
Renaud Paquay788e9622017-01-27 11:41:12 -08002768 def _get_symlink_error_message(self):
2769 if platform_utils.isWindows():
2770 return ('Unable to create symbolic link. Please re-run the command as '
2771 'Administrator, or see '
2772 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2773 'for other options.')
2774 return 'filesystem must support symlinks'
2775
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002776 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002777 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002778
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002779 def _revlist(self, *args, **kw):
2780 a = []
2781 a.extend(args)
2782 a.append('--')
2783 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002784
2785 @property
2786 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002787 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002788
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002789 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002790 """Get logs between two revisions of this project."""
2791 comp = '..'
2792 if rev1:
2793 revs = [rev1]
2794 if rev2:
2795 revs.extend([comp, rev2])
2796 cmd = ['log', ''.join(revs)]
2797 out = DiffColoring(self.config)
2798 if out.is_on and color:
2799 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002800 if pretty_format is not None:
2801 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002802 if oneline:
2803 cmd.append('--oneline')
2804
2805 try:
2806 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2807 if log.Wait() == 0:
2808 return log.stdout
2809 except GitError:
2810 # worktree may not exist if groups changed for example. In that case,
2811 # try in gitdir instead.
2812 if not os.path.exists(self.worktree):
2813 return self.bare_git.log(*cmd[1:])
2814 else:
2815 raise
2816 return None
2817
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002818 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2819 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002820 """Get the list of logs from this revision to given revisionId"""
2821 logs = {}
2822 selfId = self.GetRevisionId(self._allrefs)
2823 toId = toProject.GetRevisionId(toProject._allrefs)
2824
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002825 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2826 pretty_format=pretty_format)
2827 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2828 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002829 return logs
2830
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002831 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002832
David James8d201162013-10-11 17:03:19 -07002833 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002834 self._project = project
2835 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002836 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002837
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09002838 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
2839 def __getstate__(self):
2840 return (self._project, self._bare, self._gitdir)
2841
2842 def __setstate__(self, state):
2843 self._project, self._bare, self._gitdir = state
2844
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002845 def LsOthers(self):
2846 p = GitCommand(self._project,
2847 ['ls-files',
2848 '-z',
2849 '--others',
2850 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002851 bare=False,
David James8d201162013-10-11 17:03:19 -07002852 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002853 capture_stdout=True,
2854 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002855 if p.Wait() == 0:
2856 out = p.stdout
2857 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002858 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002859 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002860 return []
2861
2862 def DiffZ(self, name, *args):
2863 cmd = [name]
2864 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002865 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002866 cmd.extend(args)
2867 p = GitCommand(self._project,
2868 cmd,
David James8d201162013-10-11 17:03:19 -07002869 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002870 bare=False,
2871 capture_stdout=True,
2872 capture_stderr=True)
Mike Frysinger84230002021-02-16 17:08:35 -05002873 p.Wait()
2874 r = {}
2875 out = p.stdout
2876 if out:
2877 out = iter(out[:-1].split('\0'))
2878 while out:
2879 try:
2880 info = next(out)
2881 path = next(out)
2882 except StopIteration:
2883 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002884
Mike Frysinger84230002021-02-16 17:08:35 -05002885 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002886
Mike Frysinger84230002021-02-16 17:08:35 -05002887 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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002894
Mike Frysinger84230002021-02-16 17:08:35 -05002895 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:]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002903
Mike Frysinger84230002021-02-16 17:08:35 -05002904 info = info[1:].split(' ')
2905 info = _Info(path, *info)
2906 if info.status in ('R', 'C'):
2907 info.src_path = info.path
2908 info.path = next(out)
2909 r[info.path] = info
2910 return r
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002911
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002912 def GetDotgitPath(self, subpath=None):
2913 """Return the full path to the .git dir.
2914
2915 As a convenience, append |subpath| if provided.
2916 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002917 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002918 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002919 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05002920 dotgit = os.path.join(self._project.worktree, '.git')
2921 if os.path.isfile(dotgit):
2922 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
2923 with open(dotgit) as fp:
2924 setting = fp.read()
2925 assert setting.startswith('gitdir:')
2926 gitdir = setting.split(':', 1)[1].strip()
2927 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
2928
2929 return dotgit if subpath is None else os.path.join(dotgit, subpath)
2930
2931 def GetHead(self):
2932 """Return the ref that HEAD points to."""
2933 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002934 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05002935 with open(path) as fd:
2936 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04002937 except IOError as e:
2938 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002939 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302940 line = line.decode()
2941 except AttributeError:
2942 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002943 if line.startswith('ref: '):
2944 return line[5:-1]
2945 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002946
2947 def SetHead(self, ref, message=None):
2948 cmdv = []
2949 if message is not None:
2950 cmdv.extend(['-m', message])
2951 cmdv.append(HEAD)
2952 cmdv.append(ref)
2953 self.symbolic_ref(*cmdv)
2954
2955 def DetachHead(self, new, message=None):
2956 cmdv = ['--no-deref']
2957 if message is not None:
2958 cmdv.extend(['-m', message])
2959 cmdv.append(HEAD)
2960 cmdv.append(new)
2961 self.update_ref(*cmdv)
2962
2963 def UpdateRef(self, name, new, old=None,
2964 message=None,
2965 detach=False):
2966 cmdv = []
2967 if message is not None:
2968 cmdv.extend(['-m', message])
2969 if detach:
2970 cmdv.append('--no-deref')
2971 cmdv.append(name)
2972 cmdv.append(new)
2973 if old is not None:
2974 cmdv.append(old)
2975 self.update_ref(*cmdv)
2976
2977 def DeleteRef(self, name, old=None):
2978 if not old:
2979 old = self.rev_parse(name)
2980 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002981 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002982
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002983 def rev_list(self, *args, **kw):
2984 if 'format' in kw:
2985 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2986 else:
2987 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 cmdv.extend(args)
2989 p = GitCommand(self._project,
2990 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002991 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002992 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002993 capture_stdout=True,
2994 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002996 raise GitError('%s rev-list %s: %s' %
2997 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002998 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002999
3000 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003001 """Allow arbitrary git commands using pythonic syntax.
3002
3003 This allows you to do things like:
3004 git_obj.rev_parse('HEAD')
3005
3006 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3007 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003008 Any other positional arguments will be passed to the git command, and the
3009 following keyword arguments are supported:
3010 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003011
3012 Args:
3013 name: The name of the git command to call. Any '_' characters will
3014 be replaced with '-'.
3015
3016 Returns:
3017 A callable object that will try to call git with the named command.
3018 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003019 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003020
Dave Borowitz091f8932012-10-23 17:01:04 -07003021 def runner(*args, **kwargs):
3022 cmdv = []
3023 config = kwargs.pop('config', None)
3024 for k in kwargs:
3025 raise TypeError('%s() got an unexpected keyword argument %r'
3026 % (name, k))
3027 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303028 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003029 cmdv.append('-c')
3030 cmdv.append('%s=%s' % (k, v))
3031 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003032 cmdv.extend(args)
3033 p = GitCommand(self._project,
3034 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003035 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003036 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003037 capture_stdout=True,
3038 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003039 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003040 raise GitError('%s %s: %s' %
3041 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003042 r = p.stdout
3043 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3044 return r[:-1]
3045 return r
3046 return runner
3047
3048
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003049class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003050
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003051 def __str__(self):
3052 return 'prior sync failed; rebase still in progress'
3053
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003054
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003055class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003056
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003057 def __str__(self):
3058 return 'contains uncommitted changes'
3059
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003060
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003061class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003062
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003063 def __init__(self, project, text):
3064 self.project = project
3065 self.text = text
3066
3067 def Print(self, syncbuf):
3068 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3069 syncbuf.out.nl()
3070
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003071
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003072class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003073
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003074 def __init__(self, project, why):
3075 self.project = project
3076 self.why = why
3077
3078 def Print(self, syncbuf):
3079 syncbuf.out.fail('error: %s/: %s',
3080 self.project.relpath,
3081 str(self.why))
3082 syncbuf.out.nl()
3083
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003084
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003085class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003086
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003087 def __init__(self, project, action):
3088 self.project = project
3089 self.action = action
3090
3091 def Run(self, syncbuf):
3092 out = syncbuf.out
3093 out.project('project %s/', self.project.relpath)
3094 out.nl()
3095 try:
3096 self.action()
3097 out.nl()
3098 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003099 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003100 out.nl()
3101 return False
3102
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003103
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003104class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003105
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003106 def __init__(self, config):
Mike Frysinger5d9c4972021-02-19 13:34:09 -05003107 super().__init__(config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003108 self.project = self.printer('header', attr='bold')
3109 self.info = self.printer('info')
3110 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003111
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003112
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003113class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003114
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003115 def __init__(self, config, detach_head=False):
3116 self._messages = []
3117 self._failures = []
3118 self._later_queue1 = []
3119 self._later_queue2 = []
3120
3121 self.out = _SyncColoring(config)
3122 self.out.redirect(sys.stderr)
3123
3124 self.detach_head = detach_head
3125 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003126 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003127
3128 def info(self, project, fmt, *args):
3129 self._messages.append(_InfoMessage(project, fmt % args))
3130
3131 def fail(self, project, err=None):
3132 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003133 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003134
3135 def later1(self, project, what):
3136 self._later_queue1.append(_Later(project, what))
3137
3138 def later2(self, project, what):
3139 self._later_queue2.append(_Later(project, what))
3140
3141 def Finish(self):
3142 self._PrintMessages()
3143 self._RunLater()
3144 self._PrintMessages()
3145 return self.clean
3146
David Rileye0684ad2017-04-05 00:02:59 -07003147 def Recently(self):
3148 recent_clean = self.recent_clean
3149 self.recent_clean = True
3150 return recent_clean
3151
3152 def _MarkUnclean(self):
3153 self.clean = False
3154 self.recent_clean = False
3155
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003156 def _RunLater(self):
3157 for q in ['_later_queue1', '_later_queue2']:
3158 if not self._RunQueue(q):
3159 return
3160
3161 def _RunQueue(self, queue):
3162 for m in getattr(self, queue):
3163 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003164 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003165 return False
3166 setattr(self, queue, [])
3167 return True
3168
3169 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003170 if self._messages or self._failures:
3171 if os.isatty(2):
3172 self.out.write(progress.CSI_ERASE_LINE)
3173 self.out.write('\r')
3174
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003175 for m in self._messages:
3176 m.Print(self)
3177 for m in self._failures:
3178 m.Print(self)
3179
3180 self._messages = []
3181 self._failures = []
3182
3183
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003184class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003185
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003186 """A special project housed under .repo.
3187 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003189 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003190 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003191 manifest=manifest,
3192 name=name,
3193 gitdir=gitdir,
3194 objdir=gitdir,
3195 worktree=worktree,
3196 remote=RemoteSpec('origin'),
3197 relpath='.repo/%s' % name,
3198 revisionExpr='refs/heads/master',
3199 revisionId=None,
3200 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003201
3202 def PreSync(self):
3203 if self.Exists:
3204 cb = self.CurrentBranch
3205 if cb:
3206 base = self.GetBranch(cb).merge
3207 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003208 self.revisionExpr = base
3209 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003210
Martin Kelly224a31a2017-07-10 14:46:25 -07003211 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003212 """ Prepare MetaProject for manifest branch switch
3213 """
3214
3215 # detach and delete manifest branch, allowing a new
3216 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003217 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003218 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003219 syncbuf.Finish()
3220
3221 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003222 ['update-ref', '-d', 'refs/heads/default'],
3223 capture_stdout=True,
3224 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003225
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003226 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003227 def LastFetch(self):
3228 try:
3229 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3230 return os.path.getmtime(fh)
3231 except OSError:
3232 return 0
3233
3234 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003235 def HasChanges(self):
3236 """Has the remote received new commits not yet checked out?
3237 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003238 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003239 return False
3240
David Pursehouse8a68ff92012-09-24 12:15:13 +09003241 all_refs = self.bare_ref.all
3242 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003243 head = self.work_git.GetHead()
3244 if head.startswith(R_HEADS):
3245 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003246 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003247 except KeyError:
3248 head = None
3249
3250 if revid == head:
3251 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003252 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003253 return True
3254 return False