blob: 80b0cf8d5017a5e33bfe3bc3a64311651e9bd768 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050045from 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 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
George Engelbrecht9bc283e2020-04-02 12:36:09 -060058# Maximum sleep time allowed during retries.
59MAXIMUM_RETRY_SLEEP_SEC = 3600.0
60# +-10% random jitter is added to each Fetches retry sleep duration.
61RETRY_JITTER_PERCENT = 0.1
62
63
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070064def _lwrite(path, content):
65 lock = '%s.lock' % path
66
Mike Frysinger3164d402019-11-11 05:40:22 -050067 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070069
70 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070071 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070072 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080073 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070074 raise
75
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
Shawn O. Pearce48244782009-04-16 08:25:57 -070077def _error(fmt, *args):
78 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070079 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070080
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
David Pursehousef33929d2015-08-24 14:39:14 +090082def _warn(fmt, *args):
83 msg = fmt % args
84 print('warn: %s' % msg, file=sys.stderr)
85
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070086
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070087def not_rev(r):
88 return '^' + r
89
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080091def sq(r):
92 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080093
David Pursehouse819827a2020-02-12 15:20:19 +090094
Jonathan Nieder93719792015-03-17 11:29:58 -070095_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070096
97
Jonathan Nieder93719792015-03-17 11:29:58 -070098def _ProjectHooks():
99 """List the hooks present in the 'hooks' directory.
100
101 These hooks are project hooks and are copied to the '.git/hooks' directory
102 of all subprojects.
103
104 This function caches the list of hooks (based on the contents of the
105 'repo/hooks' directory) on the first call.
106
107 Returns:
108 A list of absolute paths to all of the files in the hooks directory.
109 """
110 global _project_hook_list
111 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700112 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700113 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700114 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700115 return _project_hook_list
116
117
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700118class DownloadedChange(object):
119 _commit_cache = None
120
121 def __init__(self, project, base, change_id, ps_id, commit):
122 self.project = project
123 self.base = base
124 self.change_id = change_id
125 self.ps_id = ps_id
126 self.commit = commit
127
128 @property
129 def commits(self):
130 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700131 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
132 '--abbrev-commit',
133 '--pretty=oneline',
134 '--reverse',
135 '--date-order',
136 not_rev(self.base),
137 self.commit,
138 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700139 return self._commit_cache
140
141
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142class ReviewableBranch(object):
143 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400144 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700145
146 def __init__(self, project, branch, base):
147 self.project = project
148 self.branch = branch
149 self.base = base
150
151 @property
152 def name(self):
153 return self.branch.name
154
155 @property
156 def commits(self):
157 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400158 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
159 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
160 try:
161 self._commit_cache = self.project.bare_git.rev_list(*args)
162 except GitError:
163 # We weren't able to probe the commits for this branch. Was it tracking
164 # a branch that no longer exists? If so, return no commits. Otherwise,
165 # rethrow the error as we don't know what's going on.
166 if self.base_exists:
167 raise
168
169 self._commit_cache = []
170
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 return self._commit_cache
172
173 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800174 def unabbrev_commits(self):
175 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700176 for commit in self.project.bare_git.rev_list(not_rev(self.base),
177 R_HEADS + self.name,
178 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800179 r[commit[0:8]] = commit
180 return r
181
182 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700184 return self.project.bare_git.log('--pretty=format:%cd',
185 '-n', '1',
186 R_HEADS + self.name,
187 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188
Mike Frysinger6da17752019-09-11 18:43:17 -0400189 @property
190 def base_exists(self):
191 """Whether the branch we're tracking exists.
192
193 Normally it should, but sometimes branches we track can get deleted.
194 """
195 if self._base_exists is None:
196 try:
197 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
198 # If we're still here, the base branch exists.
199 self._base_exists = True
200 except GitError:
201 # If we failed to verify, the base branch doesn't exist.
202 self._base_exists = False
203
204 return self._base_exists
205
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700206 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500207 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700208 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500209 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500210 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +0200211 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700212 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200213 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200214 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800215 validate_certs=True,
216 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500217 self.project.UploadForReview(branch=self.name,
218 people=people,
219 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700220 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500221 hashtags=hashtags,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500222 labels=labels,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200223 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700224 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200225 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200226 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800227 validate_certs=validate_certs,
228 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700230 def GetPublishedRefs(self):
231 refs = {}
232 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233 self.branch.remote.SshReviewUrl(self.project.UserEmail),
234 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700235 for line in output.split('\n'):
236 try:
237 (sha, ref) = line.split()
238 refs[sha] = ref
239 except ValueError:
240 pass
241
242 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700244
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700246
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247 def __init__(self, config):
248 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100249 self.project = self.printer('header', attr='bold')
250 self.branch = self.printer('header', attr='bold')
251 self.nobranch = self.printer('nobranch', fg='red')
252 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
Anthony King7bdac712014-07-16 12:56:40 +0100254 self.added = self.printer('added', fg='green')
255 self.changed = self.printer('changed', fg='red')
256 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257
258
259class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261 def __init__(self, config):
262 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100263 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400264 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Anthony King7bdac712014-07-16 12:56:40 +0100267class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
James W. Mills24c13082012-04-12 15:04:13 -0500269 def __init__(self, name, value, keep):
270 self.name = name
271 self.value = value
272 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700273
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700274
Mike Frysingere6a202f2019-08-02 15:57:57 -0400275def _SafeExpandPath(base, subpath, skipfinal=False):
276 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700277
Mike Frysingere6a202f2019-08-02 15:57:57 -0400278 We make sure no intermediate symlinks are traversed, and that the final path
279 is not a special file (e.g. not a socket or fifo).
280
281 NB: We rely on a number of paths already being filtered out while parsing the
282 manifest. See the validation logic in manifest_xml.py for more details.
283 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500284 # Split up the path by its components. We can't use os.path.sep exclusively
285 # as some platforms (like Windows) will convert / to \ and that bypasses all
286 # our constructed logic here. Especially since manifest authors only use
287 # / in their paths.
288 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
289 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400290 if skipfinal:
291 # Whether the caller handles the final component itself.
292 finalpart = components.pop()
293
294 path = base
295 for part in components:
296 if part in {'.', '..'}:
297 raise ManifestInvalidPathError(
298 '%s: "%s" not allowed in paths' % (subpath, part))
299
300 path = os.path.join(path, part)
301 if platform_utils.islink(path):
302 raise ManifestInvalidPathError(
303 '%s: traversing symlinks not allow' % (path,))
304
305 if os.path.exists(path):
306 if not os.path.isfile(path) and not platform_utils.isdir(path):
307 raise ManifestInvalidPathError(
308 '%s: only regular files & directories allowed' % (path,))
309
310 if skipfinal:
311 path = os.path.join(path, finalpart)
312
313 return path
314
315
316class _CopyFile(object):
317 """Container for <copyfile> manifest element."""
318
319 def __init__(self, git_worktree, src, topdir, dest):
320 """Register a <copyfile> request.
321
322 Args:
323 git_worktree: Absolute path to the git project checkout.
324 src: Relative path under |git_worktree| of file to read.
325 topdir: Absolute path to the top of the repo client checkout.
326 dest: Relative path under |topdir| of file to write.
327 """
328 self.git_worktree = git_worktree
329 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700330 self.src = src
331 self.dest = dest
332
333 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400334 src = _SafeExpandPath(self.git_worktree, self.src)
335 dest = _SafeExpandPath(self.topdir, self.dest)
336
337 if platform_utils.isdir(src):
338 raise ManifestInvalidPathError(
339 '%s: copying from directory not supported' % (self.src,))
340 if platform_utils.isdir(dest):
341 raise ManifestInvalidPathError(
342 '%s: copying to directory not allowed' % (self.dest,))
343
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700344 # copy file if it does not exist or is out of date
345 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
346 try:
347 # remove existing file first, since it might be read-only
348 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800349 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400350 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200351 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700352 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200353 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354 shutil.copy(src, dest)
355 # make the file read-only
356 mode = os.stat(dest)[stat.ST_MODE]
357 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
358 os.chmod(dest, mode)
359 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700360 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700361
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700362
Anthony King7bdac712014-07-16 12:56:40 +0100363class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400364 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700365
Mike Frysingere6a202f2019-08-02 15:57:57 -0400366 def __init__(self, git_worktree, src, topdir, dest):
367 """Register a <linkfile> request.
368
369 Args:
370 git_worktree: Absolute path to the git project checkout.
371 src: Target of symlink relative to path under |git_worktree|.
372 topdir: Absolute path to the top of the repo client checkout.
373 dest: Relative path under |topdir| of symlink to create.
374 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700375 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400376 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 self.src = src
378 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379
Wink Saville4c426ef2015-06-03 08:05:17 -0700380 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700382 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500383 try:
384 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800385 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800386 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500387 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700388 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700389 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500390 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700391 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500392 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700393 _error('Cannot link file %s to %s', relSrc, absDest)
394
395 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400396 """Link the self.src & self.dest paths.
397
398 Handles wild cards on the src linking all of the files in the source in to
399 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700400 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500401 # Some people use src="." to create stable links to projects. Lets allow
402 # that but reject all other uses of "." to keep things simple.
403 if self.src == '.':
404 src = self.git_worktree
405 else:
406 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400407
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300408 if not glob.has_magic(src):
409 # Entity does not contain a wild card so just a simple one to one link operation.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
411 # dest & src are absolute paths at this point. Make sure the target of
412 # the symlink is relative in the context of the repo client checkout.
413 relpath = os.path.relpath(src, os.path.dirname(dest))
414 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700415 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400416 dest = _SafeExpandPath(self.topdir, self.dest)
Angel Petkovdbfbcb12020-05-02 23:16:20 +0300417 # Entity contains a wild card.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 if os.path.exists(dest) and not platform_utils.isdir(dest):
419 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700420 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400421 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700422 # Create a releative path from source dir to destination dir
423 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400424 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700425
426 # Get the source file name
427 srcFile = os.path.basename(absSrcFile)
428
429 # Now form the final full paths to srcFile. They will be
430 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400431 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700432 relSrc = os.path.join(relSrcDir, srcFile)
433 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500434
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700435
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700436class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700437
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700438 def __init__(self,
439 name,
Anthony King7bdac712014-07-16 12:56:40 +0100440 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700441 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100442 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700443 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700444 orig_name=None,
445 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700446 self.name = name
447 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700448 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700449 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100450 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700451 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700452 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700453
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700454
Doug Anderson37282b42011-03-04 11:54:18 -0800455class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700456
Doug Anderson37282b42011-03-04 11:54:18 -0800457 """A RepoHook contains information about a script to run as a hook.
458
459 Hooks are used to run a python script before running an upload (for instance,
460 to run presubmit checks). Eventually, we may have hooks for other actions.
461
462 This shouldn't be confused with files in the 'repo/hooks' directory. Those
463 files are copied into each '.git/hooks' folder for each project. Repo-level
464 hooks are associated instead with repo actions.
465
466 Hooks are always python. When a hook is run, we will load the hook into the
467 interpreter and execute its main() function.
468 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700469
Doug Anderson37282b42011-03-04 11:54:18 -0800470 def __init__(self,
471 hook_type,
472 hooks_project,
473 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800475 abort_if_user_denies=False):
476 """RepoHook constructor.
477
478 Params:
479 hook_type: A string representing the type of hook. This is also used
480 to figure out the name of the file containing the hook. For
481 example: 'pre-upload'.
482 hooks_project: The project containing the repo hooks. If you have a
483 manifest, this is manifest.repo_hooks_project. OK if this is None,
484 which will make the hook a no-op.
485 topdir: Repo's top directory (the one containing the .repo directory).
486 Scripts will run with CWD as this directory. If you have a manifest,
487 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400488 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800489 abort_if_user_denies: If True, we'll throw a HookError() if the user
490 doesn't allow us to run the hook.
491 """
492 self._hook_type = hook_type
493 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400494 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800495 self._topdir = topdir
496 self._abort_if_user_denies = abort_if_user_denies
497
498 # Store the full path to the script for convenience.
499 if self._hooks_project:
500 self._script_fullpath = os.path.join(self._hooks_project.worktree,
501 self._hook_type + '.py')
502 else:
503 self._script_fullpath = None
504
505 def _GetHash(self):
506 """Return a hash of the contents of the hooks directory.
507
508 We'll just use git to do this. This hash has the property that if anything
509 changes in the directory we will return a different has.
510
511 SECURITY CONSIDERATION:
512 This hash only represents the contents of files in the hook directory, not
513 any other files imported or called by hooks. Changes to imported files
514 can change the script behavior without affecting the hash.
515
516 Returns:
517 A string representing the hash. This will always be ASCII so that it can
518 be printed to the user easily.
519 """
520 assert self._hooks_project, "Must have hooks to calculate their hash."
521
522 # We will use the work_git object rather than just calling GetRevisionId().
523 # That gives us a hash of the latest checked in version of the files that
524 # the user will actually be executing. Specifically, GetRevisionId()
525 # doesn't appear to change even if a user checks out a different version
526 # of the hooks repo (via git checkout) nor if a user commits their own revs.
527 #
528 # NOTE: Local (non-committed) changes will not be factored into this hash.
529 # I think this is OK, since we're really only worried about warning the user
530 # about upstream changes.
531 return self._hooks_project.work_git.rev_parse('HEAD')
532
533 def _GetMustVerb(self):
534 """Return 'must' if the hook is required; 'should' if not."""
535 if self._abort_if_user_denies:
536 return 'must'
537 else:
538 return 'should'
539
540 def _CheckForHookApproval(self):
541 """Check to see whether this hook has been approved.
542
Mike Frysinger40252c22016-08-15 21:23:44 -0400543 We'll accept approval of manifest URLs if they're using secure transports.
544 This way the user can say they trust the manifest hoster. For insecure
545 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800546
547 Note that we ask permission for each individual hook even though we use
548 the hash of all hooks when detecting changes. We'd like the user to be
549 able to approve / deny each hook individually. We only use the hash of all
550 hooks because there is no other easy way to detect changes to local imports.
551
552 Returns:
553 True if this hook is approved to run; False otherwise.
554
555 Raises:
556 HookError: Raised if the user doesn't approve and abort_if_user_denies
557 was passed to the consturctor.
558 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400559 if self._ManifestUrlHasSecureScheme():
560 return self._CheckForHookApprovalManifest()
561 else:
562 return self._CheckForHookApprovalHash()
563
564 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
565 changed_prompt):
566 """Check for approval for a particular attribute and hook.
567
568 Args:
569 subkey: The git config key under [repo.hooks.<hook_type>] to store the
570 last approved string.
571 new_val: The new value to compare against the last approved one.
572 main_prompt: Message to display to the user to ask for approval.
573 changed_prompt: Message explaining why we're re-asking for approval.
574
575 Returns:
576 True if this hook is approved to run; False otherwise.
577
578 Raises:
579 HookError: Raised if the user doesn't approve and abort_if_user_denies
580 was passed to the consturctor.
581 """
Doug Anderson37282b42011-03-04 11:54:18 -0800582 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400583 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800584
Mike Frysinger40252c22016-08-15 21:23:44 -0400585 # Get the last value that the user approved for this hook; may be None.
586 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800587
Mike Frysinger40252c22016-08-15 21:23:44 -0400588 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800589 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400590 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800591 # Approval matched. We're done.
592 return True
593 else:
594 # Give the user a reason why we're prompting, since they last told
595 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400596 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800597 else:
598 prompt = ''
599
600 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
601 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400602 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530603 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900604 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800605
606 # User is doing a one-time approval.
607 if response in ('y', 'yes'):
608 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400609 elif response == 'always':
610 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800611 return True
612
613 # For anything else, we'll assume no approval.
614 if self._abort_if_user_denies:
615 raise HookError('You must allow the %s hook or use --no-verify.' %
616 self._hook_type)
617
618 return False
619
Mike Frysinger40252c22016-08-15 21:23:44 -0400620 def _ManifestUrlHasSecureScheme(self):
621 """Check if the URI for the manifest is a secure transport."""
622 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
623 parse_results = urllib.parse.urlparse(self._manifest_url)
624 return parse_results.scheme in secure_schemes
625
626 def _CheckForHookApprovalManifest(self):
627 """Check whether the user has approved this manifest host.
628
629 Returns:
630 True if this hook is approved to run; False otherwise.
631 """
632 return self._CheckForHookApprovalHelper(
633 'approvedmanifest',
634 self._manifest_url,
635 'Run hook scripts from %s' % (self._manifest_url,),
636 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
637
638 def _CheckForHookApprovalHash(self):
639 """Check whether the user has approved the hooks repo.
640
641 Returns:
642 True if this hook is approved to run; False otherwise.
643 """
644 prompt = ('Repo %s run the script:\n'
645 ' %s\n'
646 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700647 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400648 return self._CheckForHookApprovalHelper(
649 'approvedhash',
650 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700651 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400652 'Scripts have changed since %s was allowed.' % (self._hook_type,))
653
Mike Frysingerf7c51602019-06-18 17:23:39 -0400654 @staticmethod
655 def _ExtractInterpFromShebang(data):
656 """Extract the interpreter used in the shebang.
657
658 Try to locate the interpreter the script is using (ignoring `env`).
659
660 Args:
661 data: The file content of the script.
662
663 Returns:
664 The basename of the main script interpreter, or None if a shebang is not
665 used or could not be parsed out.
666 """
667 firstline = data.splitlines()[:1]
668 if not firstline:
669 return None
670
671 # The format here can be tricky.
672 shebang = firstline[0].strip()
673 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
674 if not m:
675 return None
676
677 # If the using `env`, find the target program.
678 interp = m.group(1)
679 if os.path.basename(interp) == 'env':
680 interp = m.group(2)
681
682 return interp
683
684 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
685 """Execute the hook script through |interp|.
686
687 Note: Support for this feature should be dropped ~Jun 2021.
688
689 Args:
690 interp: The Python program to run.
691 context: Basic Python context to execute the hook inside.
692 kwargs: Arbitrary arguments to pass to the hook script.
693
694 Raises:
695 HookError: When the hooks failed for any reason.
696 """
697 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
698 script = """
699import json, os, sys
700path = '''%(path)s'''
701kwargs = json.loads('''%(kwargs)s''')
702context = json.loads('''%(context)s''')
703sys.path.insert(0, os.path.dirname(path))
704data = open(path).read()
705exec(compile(data, path, 'exec'), context)
706context['main'](**kwargs)
707""" % {
708 'path': self._script_fullpath,
709 'kwargs': json.dumps(kwargs),
710 'context': json.dumps(context),
711 }
712
713 # We pass the script via stdin to avoid OS argv limits. It also makes
714 # unhandled exception tracebacks less verbose/confusing for users.
715 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
716 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
717 proc.communicate(input=script.encode('utf-8'))
718 if proc.returncode:
719 raise HookError('Failed to run %s hook.' % (self._hook_type,))
720
721 def _ExecuteHookViaImport(self, data, context, **kwargs):
722 """Execute the hook code in |data| directly.
723
724 Args:
725 data: The code of the hook to execute.
726 context: Basic Python context to execute the hook inside.
727 kwargs: Arbitrary arguments to pass to the hook script.
728
729 Raises:
730 HookError: When the hooks failed for any reason.
731 """
732 # Exec, storing global context in the context dict. We catch exceptions
733 # and convert to a HookError w/ just the failing traceback.
734 try:
735 exec(compile(data, self._script_fullpath, 'exec'), context)
736 except Exception:
737 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
738 (traceback.format_exc(), self._hook_type))
739
740 # Running the script should have defined a main() function.
741 if 'main' not in context:
742 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
743
744 # Call the main function in the hook. If the hook should cause the
745 # build to fail, it will raise an Exception. We'll catch that convert
746 # to a HookError w/ just the failing traceback.
747 try:
748 context['main'](**kwargs)
749 except Exception:
750 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
751 'above.' % (traceback.format_exc(), self._hook_type))
752
Doug Anderson37282b42011-03-04 11:54:18 -0800753 def _ExecuteHook(self, **kwargs):
754 """Actually execute the given hook.
755
756 This will run the hook's 'main' function in our python interpreter.
757
758 Args:
759 kwargs: Keyword arguments to pass to the hook. These are often specific
760 to the hook type. For instance, pre-upload hooks will contain
761 a project_list.
762 """
763 # Keep sys.path and CWD stashed away so that we can always restore them
764 # upon function exit.
765 orig_path = os.getcwd()
766 orig_syspath = sys.path
767
768 try:
769 # Always run hooks with CWD as topdir.
770 os.chdir(self._topdir)
771
772 # Put the hook dir as the first item of sys.path so hooks can do
773 # relative imports. We want to replace the repo dir as [0] so
774 # hooks can't import repo files.
775 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
776
Mike Frysingerf7c51602019-06-18 17:23:39 -0400777 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500778 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800779
Doug Anderson37282b42011-03-04 11:54:18 -0800780 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
781 # We don't actually want hooks to define their main with this argument--
782 # it's there to remind them that their hook should always take **kwargs.
783 # For instance, a pre-upload hook should be defined like:
784 # def main(project_list, **kwargs):
785 #
786 # This allows us to later expand the API without breaking old hooks.
787 kwargs = kwargs.copy()
788 kwargs['hook_should_take_kwargs'] = True
789
Mike Frysingerf7c51602019-06-18 17:23:39 -0400790 # See what version of python the hook has been written against.
791 data = open(self._script_fullpath).read()
792 interp = self._ExtractInterpFromShebang(data)
793 reexec = False
794 if interp:
795 prog = os.path.basename(interp)
796 if prog.startswith('python2') and sys.version_info.major != 2:
797 reexec = True
798 elif prog.startswith('python3') and sys.version_info.major == 2:
799 reexec = True
800
801 # Attempt to execute the hooks through the requested version of Python.
802 if reexec:
803 try:
804 self._ExecuteHookViaReexec(interp, context, **kwargs)
805 except OSError as e:
806 if e.errno == errno.ENOENT:
807 # We couldn't find the interpreter, so fallback to importing.
808 reexec = False
809 else:
810 raise
811
812 # Run the hook by importing directly.
813 if not reexec:
814 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800815 finally:
816 # Restore sys.path and CWD.
817 sys.path = orig_syspath
818 os.chdir(orig_path)
819
820 def Run(self, user_allows_all_hooks, **kwargs):
821 """Run the hook.
822
823 If the hook doesn't exist (because there is no hooks project or because
824 this particular hook is not enabled), this is a no-op.
825
826 Args:
827 user_allows_all_hooks: If True, we will never prompt about running the
828 hook--we'll just assume it's OK to run it.
829 kwargs: Keyword arguments to pass to the hook. These are often specific
830 to the hook type. For instance, pre-upload hooks will contain
831 a project_list.
832
833 Raises:
834 HookError: If there was a problem finding the hook or the user declined
835 to run a required hook (from _CheckForHookApproval).
836 """
837 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700838 if ((not self._hooks_project) or (self._hook_type not in
839 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800840 return
841
842 # Bail with a nice error if we can't find the hook.
843 if not os.path.isfile(self._script_fullpath):
844 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
845
846 # Make sure the user is OK with running the hook.
847 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
848 return
849
850 # Run the hook with the same version of python we're using.
851 self._ExecuteHook(**kwargs)
852
853
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600855 # These objects can be shared between several working trees.
856 shareable_files = ['description', 'info']
857 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
858 # These objects can only be used by a single working tree.
859 working_tree_files = ['config', 'packed-refs', 'shallow']
860 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700861
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 def __init__(self,
863 manifest,
864 name,
865 remote,
866 gitdir,
David James8d201162013-10-11 17:03:19 -0700867 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700868 worktree,
869 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700870 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800871 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100872 rebase=True,
873 groups=None,
874 sync_c=False,
875 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900876 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100877 clone_depth=None,
878 upstream=None,
879 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500880 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100881 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900882 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700883 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600884 retry_fetches=0,
Simran Basib9a1b732015-08-20 12:19:28 -0700885 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800886 """Init a Project object.
887
888 Args:
889 manifest: The XmlManifest object.
890 name: The `name` attribute of manifest.xml's project element.
891 remote: RemoteSpec object specifying its remote's properties.
892 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700893 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800894 worktree: Absolute path of git working tree.
895 relpath: Relative path of git working tree to repo's top directory.
896 revisionExpr: The `revision` attribute of manifest.xml's project element.
897 revisionId: git commit id for checking out.
898 rebase: The `rebase` attribute of manifest.xml's project element.
899 groups: The `groups` attribute of manifest.xml's project element.
900 sync_c: The `sync-c` attribute of manifest.xml's project element.
901 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900902 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800903 upstream: The `upstream` attribute of manifest.xml's project element.
904 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500905 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800906 is_derived: False if the project was explicitly defined in the manifest;
907 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400908 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900909 optimized_fetch: If True, when a project is set to a sha1 revision, only
910 fetch from the remote if the sha1 is not present locally.
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600911 retry_fetches: Retry remote fetches n times upon receiving transient error
912 with exponential backoff and jitter.
Simran Basib9a1b732015-08-20 12:19:28 -0700913 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800914 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 self.manifest = manifest
916 self.name = name
917 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800918 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700919 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800920 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700921 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800922 else:
923 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700924 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700925 self.revisionExpr = revisionExpr
926
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700927 if revisionId is None \
928 and revisionExpr \
929 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700930 self.revisionId = revisionExpr
931 else:
932 self.revisionId = revisionId
933
Mike Pontillod3153822012-02-28 11:53:24 -0800934 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700935 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700936 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800937 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900938 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900939 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700940 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800941 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500942 # NB: Do not use this setting in __init__ to change behavior so that the
943 # manifest.git checkout can inspect & change it after instantiating. See
944 # the XmlManifest init code for more info.
945 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800946 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900947 self.optimized_fetch = optimized_fetch
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600948 self.retry_fetches = max(0, retry_fetches)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800949 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800950
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700952 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500953 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500954 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700955 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
956 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800958 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700959 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800960 else:
961 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700962 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700963 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700964 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400965 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700966 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967
Doug Anderson37282b42011-03-04 11:54:18 -0800968 # This will be filled in if a project is later identified to be the
969 # project containing repo hooks.
970 self.enabled_repo_hooks = []
971
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700972 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800973 def Derived(self):
974 return self.is_derived
975
976 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700978 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
980 @property
981 def CurrentBranch(self):
982 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400983
984 The branch name omits the 'refs/heads/' prefix.
985 None is returned if the project is on a detached HEAD, or if the work_git is
986 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400988 try:
989 b = self.work_git.GetHead()
990 except NoManifestException:
991 # If the local checkout is in a bad state, don't barf. Let the callers
992 # process this like the head is unreadable.
993 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 if b.startswith(R_HEADS):
995 return b[len(R_HEADS):]
996 return None
997
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700998 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500999 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
1000 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
1001 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001002
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 def IsDirty(self, consider_untracked=True):
1004 """Is the working directory modified in some way?
1005 """
1006 self.work_git.update_index('-q',
1007 '--unmerged',
1008 '--ignore-missing',
1009 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +09001010 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011 return True
1012 if self.work_git.DiffZ('diff-files'):
1013 return True
1014 if consider_untracked and self.work_git.LsOthers():
1015 return True
1016 return False
1017
1018 _userident_name = None
1019 _userident_email = None
1020
1021 @property
1022 def UserName(self):
1023 """Obtain the user's personal name.
1024 """
1025 if self._userident_name is None:
1026 self._LoadUserIdentity()
1027 return self._userident_name
1028
1029 @property
1030 def UserEmail(self):
1031 """Obtain the user's email address. This is very likely
1032 to be their Gerrit login.
1033 """
1034 if self._userident_email is None:
1035 self._LoadUserIdentity()
1036 return self._userident_email
1037
1038 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001039 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1040 m = re.compile("^(.*) <([^>]*)> ").match(u)
1041 if m:
1042 self._userident_name = m.group(1)
1043 self._userident_email = m.group(2)
1044 else:
1045 self._userident_name = ''
1046 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001047
1048 def GetRemote(self, name):
1049 """Get the configuration for a single remote.
1050 """
1051 return self.config.GetRemote(name)
1052
1053 def GetBranch(self, name):
1054 """Get the configuration for a single branch.
1055 """
1056 return self.config.GetBranch(name)
1057
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001058 def GetBranches(self):
1059 """Get all existing local branches.
1060 """
1061 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001062 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001063 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001064
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301065 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001066 if name.startswith(R_HEADS):
1067 name = name[len(R_HEADS):]
1068 b = self.GetBranch(name)
1069 b.current = name == current
1070 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001071 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001072 heads[name] = b
1073
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301074 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001075 if name.startswith(R_PUB):
1076 name = name[len(R_PUB):]
1077 b = heads.get(name)
1078 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001079 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001080
1081 return heads
1082
Colin Cross5acde752012-03-28 20:15:45 -07001083 def MatchesGroups(self, manifest_groups):
1084 """Returns true if the manifest groups specified at init should cause
1085 this project to be synced.
1086 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001087 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001088
1089 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001090 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001091 manifest_groups: "-group1,group2"
1092 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001093
1094 The special manifest group "default" will match any project that
1095 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001096 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001097 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001098 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001099 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001100 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001101
Conley Owens971de8e2012-04-16 10:36:08 -07001102 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001103 for group in expanded_manifest_groups:
1104 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001105 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001106 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001107 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001108
Conley Owens971de8e2012-04-16 10:36:08 -07001109 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001110
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001111# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001112 def UncommitedFiles(self, get_all=True):
1113 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001114
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001115 Args:
1116 get_all: a boolean, if True - get information about all different
1117 uncommitted files. If False - return as soon as any kind of
1118 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001119 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001120 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001121 self.work_git.update_index('-q',
1122 '--unmerged',
1123 '--ignore-missing',
1124 '--refresh')
1125 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001126 details.append("rebase in progress")
1127 if not get_all:
1128 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001129
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001130 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1131 if changes:
1132 details.extend(changes)
1133 if not get_all:
1134 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001135
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001136 changes = self.work_git.DiffZ('diff-files').keys()
1137 if changes:
1138 details.extend(changes)
1139 if not get_all:
1140 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001141
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001142 changes = self.work_git.LsOthers()
1143 if changes:
1144 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001145
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001146 return details
1147
1148 def HasChanges(self):
1149 """Returns true if there are uncommitted changes.
1150 """
1151 if self.UncommitedFiles(get_all=False):
1152 return True
1153 else:
1154 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001155
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001156 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001157 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001158
1159 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001160 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001161 quiet: If True then only print the project name. Do not print
1162 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001164 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001165 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001166 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001167 print(file=output_redir)
1168 print('project %s/' % self.relpath, file=output_redir)
1169 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170 return
1171
1172 self.work_git.update_index('-q',
1173 '--unmerged',
1174 '--ignore-missing',
1175 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001176 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001177 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1178 df = self.work_git.DiffZ('diff-files')
1179 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001180 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001181 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001182
1183 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001184 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001185 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001186 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001187
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001188 if quiet:
1189 out.nl()
1190 return 'DIRTY'
1191
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 branch = self.CurrentBranch
1193 if branch is None:
1194 out.nobranch('(*** NO BRANCH ***)')
1195 else:
1196 out.branch('branch %s', branch)
1197 out.nl()
1198
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001199 if rb:
1200 out.important('prior sync failed; rebase still in progress')
1201 out.nl()
1202
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203 paths = list()
1204 paths.extend(di.keys())
1205 paths.extend(df.keys())
1206 paths.extend(do)
1207
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301208 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001209 try:
1210 i = di[p]
1211 except KeyError:
1212 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001214 try:
1215 f = df[p]
1216 except KeyError:
1217 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001218
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001219 if i:
1220 i_status = i.status.upper()
1221 else:
1222 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001224 if f:
1225 f_status = f.status.lower()
1226 else:
1227 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001228
1229 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001230 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001231 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001232 else:
1233 line = ' %s%s\t%s' % (i_status, f_status, p)
1234
1235 if i and not f:
1236 out.added('%s', line)
1237 elif (i and f) or (not i and f):
1238 out.changed('%s', line)
1239 elif not i and not f:
1240 out.untracked('%s', line)
1241 else:
1242 out.write('%s', line)
1243 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001244
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001245 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246
pelyad67872d2012-03-28 14:49:58 +03001247 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248 """Prints the status of the repository to stdout.
1249 """
1250 out = DiffColoring(self.config)
1251 cmd = ['diff']
1252 if out.is_on:
1253 cmd.append('--color')
1254 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001255 if absolute_paths:
1256 cmd.append('--src-prefix=a/%s/' % self.relpath)
1257 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001259 try:
1260 p = GitCommand(self,
1261 cmd,
1262 capture_stdout=True,
1263 capture_stderr=True)
1264 except GitError as e:
1265 out.nl()
1266 out.project('project %s/' % self.relpath)
1267 out.nl()
1268 out.fail('%s', str(e))
1269 out.nl()
1270 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 has_diff = False
1272 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001273 if not hasattr(line, 'encode'):
1274 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275 if not has_diff:
1276 out.nl()
1277 out.project('project %s/' % self.relpath)
1278 out.nl()
1279 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001280 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001281 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001283# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001284 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001285 """Was the branch published (uploaded) for code review?
1286 If so, returns the SHA-1 hash of the last published
1287 state for the branch.
1288 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001289 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001291 try:
1292 return self.bare_git.rev_parse(key)
1293 except GitError:
1294 return None
1295 else:
1296 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001297 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001298 except KeyError:
1299 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300
David Pursehouse8a68ff92012-09-24 12:15:13 +09001301 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302 """Prunes any stale published refs.
1303 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 if all_refs is None:
1305 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 heads = set()
1307 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301308 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309 if name.startswith(R_HEADS):
1310 heads.add(name)
1311 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001312 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001313
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301314 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315 n = name[len(R_PUB):]
1316 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001317 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001319 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 """List any branches which can be uploaded for review.
1321 """
1322 heads = {}
1323 pubed = {}
1324
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301325 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001327 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001329 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
1331 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301332 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001333 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001334 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001335 if selected_branch and branch != selected_branch:
1336 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001338 rb = self.GetUploadableBranch(branch)
1339 if rb:
1340 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001341 return ready
1342
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001343 def GetUploadableBranch(self, branch_name):
1344 """Get a single uploadable branch, or None.
1345 """
1346 branch = self.GetBranch(branch_name)
1347 base = branch.LocalMerge
1348 if branch.LocalMerge:
1349 rb = ReviewableBranch(self, branch, base)
1350 if rb.commits:
1351 return rb
1352 return None
1353
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001354 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001355 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001356 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001357 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001358 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001359 labels=(),
Changcheng Xiao87984c62017-08-02 16:55:03 +02001360 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001361 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001362 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001363 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001364 validate_certs=True,
1365 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001366 """Uploads the named branch for code review.
1367 """
1368 if branch is None:
1369 branch = self.CurrentBranch
1370 if branch is None:
1371 raise GitError('not currently on a branch')
1372
1373 branch = self.GetBranch(branch)
1374 if not branch.LocalMerge:
1375 raise GitError('branch %s does not track a remote' % branch.name)
1376 if not branch.remote.review:
1377 raise GitError('remote %s has no review url' % branch.remote.name)
1378
Bryan Jacobsf609f912013-05-06 13:36:24 -04001379 if dest_branch is None:
1380 dest_branch = self.dest_branch
1381 if dest_branch is None:
1382 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001383 if not dest_branch.startswith(R_HEADS):
1384 dest_branch = R_HEADS + dest_branch
1385
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001386 if not branch.remote.projectname:
1387 branch.remote.projectname = self.name
1388 branch.remote.Save()
1389
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001390 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001391 if url is None:
1392 raise UploadError('review not configured')
1393 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001394 if dryrun:
1395 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001396
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001397 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001398 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001399
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001400 for push_option in (push_options or []):
1401 cmd.append('-o')
1402 cmd.append(push_option)
1403
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001404 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001405
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001406 if dest_branch.startswith(R_HEADS):
1407 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001408
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001409 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001410 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001411 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001412 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001413 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001414 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001415
David Pursehousef25a3702018-11-14 19:01:22 -08001416 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001417 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001418 if notify:
1419 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001420 if private:
1421 opts += ['private']
1422 if wip:
1423 opts += ['wip']
1424 if opts:
1425 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001426 cmd.append(ref_spec)
1427
Anthony King7bdac712014-07-16 12:56:40 +01001428 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001429 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430
1431 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1432 self.bare_git.UpdateRef(R_PUB + branch.name,
1433 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001434 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001435
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001436# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001437 def _ExtractArchive(self, tarpath, path=None):
1438 """Extract the given tar on its current location
1439
1440 Args:
1441 - tarpath: The path to the actual tar file
1442
1443 """
1444 try:
1445 with tarfile.open(tarpath, 'r') as tar:
1446 tar.extractall(path=path)
1447 return True
1448 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001449 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001450 return False
1451
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001452 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001453 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001454 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001455 is_new=None,
1456 current_branch_only=False,
1457 force_sync=False,
1458 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001459 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001460 archive=False,
1461 optimized_fetch=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001462 retry_fetches=0,
Martin Kellye4e94d22017-03-21 16:05:12 -07001463 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001464 submodules=False,
1465 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001466 """Perform only the network IO portion of the sync process.
1467 Local working directory/branch state is not affected.
1468 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001469 if archive and not isinstance(self, MetaProject):
1470 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001471 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001472 return False
1473
1474 name = self.relpath.replace('\\', '/')
1475 name = name.replace('/', '_')
1476 tarpath = '%s.tar' % name
1477 topdir = self.manifest.topdir
1478
1479 try:
1480 self._FetchArchive(tarpath, cwd=topdir)
1481 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001482 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001483 return False
1484
1485 # From now on, we only need absolute tarpath
1486 tarpath = os.path.join(topdir, tarpath)
1487
1488 if not self._ExtractArchive(tarpath, path=topdir):
1489 return False
1490 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001491 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001492 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001493 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001494 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001495 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001496 if is_new is None:
1497 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001498 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001499 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001500 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001501 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001502 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001503
1504 if is_new:
1505 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1506 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001507 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001508 # This works for both absolute and relative alternate directories.
1509 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001510 except IOError:
1511 alt_dir = None
1512 else:
1513 alt_dir = None
1514
Mike Frysingere50b6a72020-02-19 01:45:48 -05001515 if (clone_bundle
1516 and alt_dir is None
1517 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001518 is_new = False
1519
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001520 if not current_branch_only:
1521 if self.sync_c:
1522 current_branch_only = True
1523 elif not self.manifest._loaded:
1524 # Manifest cannot check defaults until it syncs.
1525 current_branch_only = False
1526 elif self.manifest.default.sync_c:
1527 current_branch_only = True
1528
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001529 if not self.sync_tags:
1530 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001531
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001532 if self.clone_depth:
1533 depth = self.clone_depth
1534 else:
1535 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1536
Mike Frysinger521d01b2020-02-17 01:51:49 -05001537 # See if we can skip the network fetch entirely.
1538 if not (optimized_fetch and
1539 (ID_RE.match(self.revisionExpr) and
1540 self._CheckForImmutableRevision())):
1541 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001542 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1543 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001544 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001545 submodules=submodules, force_sync=force_sync,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001546 clone_filter=clone_filter, retry_fetches=retry_fetches):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001547 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001548
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001549 mp = self.manifest.manifestProject
1550 dissociate = mp.config.GetBoolean('repo.dissociate')
1551 if dissociate:
1552 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1553 if os.path.exists(alternates_file):
1554 cmd = ['repack', '-a', '-d']
1555 if GitCommand(self, cmd, bare=True).Wait() != 0:
1556 return False
1557 platform_utils.remove(alternates_file)
1558
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001559 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001560 self._InitMRef()
1561 else:
1562 self._InitMirrorHead()
1563 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001564 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001565 except OSError:
1566 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001567 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001568
1569 def PostRepoUpgrade(self):
1570 self._InitHooks()
1571
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001572 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001573 if self.manifest.isGitcClient:
1574 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001575 for copyfile in self.copyfiles:
1576 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001577 for linkfile in self.linkfiles:
1578 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001579
Julien Camperguedd654222014-01-09 16:21:37 +01001580 def GetCommitRevisionId(self):
1581 """Get revisionId of a commit.
1582
1583 Use this method instead of GetRevisionId to get the id of the commit rather
1584 than the id of the current git object (for example, a tag)
1585
1586 """
1587 if not self.revisionExpr.startswith(R_TAGS):
1588 return self.GetRevisionId(self._allrefs)
1589
1590 try:
1591 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1592 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001593 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1594 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001595
David Pursehouse8a68ff92012-09-24 12:15:13 +09001596 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001597 if self.revisionId:
1598 return self.revisionId
1599
1600 rem = self.GetRemote(self.remote.name)
1601 rev = rem.ToLocal(self.revisionExpr)
1602
David Pursehouse8a68ff92012-09-24 12:15:13 +09001603 if all_refs is not None and rev in all_refs:
1604 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001605
1606 try:
1607 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1608 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001609 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1610 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001611
Martin Kellye4e94d22017-03-21 16:05:12 -07001612 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001613 """Perform only the local IO portion of the sync process.
1614 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001615 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001616 if not os.path.exists(self.gitdir):
1617 syncbuf.fail(self,
1618 'Cannot checkout %s due to missing network sync; Run '
1619 '`repo sync -n %s` first.' %
1620 (self.name, self.name))
1621 return
1622
Martin Kellye4e94d22017-03-21 16:05:12 -07001623 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001624 all_refs = self.bare_ref.all
1625 self.CleanPublishedCache(all_refs)
1626 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001627
David Pursehouse1d947b32012-10-25 12:23:11 +09001628 def _doff():
1629 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001630 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001631
Martin Kellye4e94d22017-03-21 16:05:12 -07001632 def _dosubmodules():
1633 self._SyncSubmodules(quiet=True)
1634
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001635 head = self.work_git.GetHead()
1636 if head.startswith(R_HEADS):
1637 branch = head[len(R_HEADS):]
1638 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001639 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001640 except KeyError:
1641 head = None
1642 else:
1643 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001644
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001645 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001646 # Currently on a detached HEAD. The user is assumed to
1647 # not have any local modifications worth worrying about.
1648 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001649 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001650 syncbuf.fail(self, _PriorSyncFailedError())
1651 return
1652
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001653 if head == revid:
1654 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001655 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001656 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001657 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001658 # The copy/linkfile config may have changed.
1659 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001660 return
1661 else:
1662 lost = self._revlist(not_rev(revid), HEAD)
1663 if lost:
1664 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001665
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001666 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001667 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001668 if submodules:
1669 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001670 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001671 syncbuf.fail(self, e)
1672 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001673 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001674 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001675
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001676 if head == revid:
1677 # No changes; don't do anything further.
1678 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001679 # The copy/linkfile config may have changed.
1680 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001681 return
1682
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001684
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001685 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001686 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001687 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001688 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001689 syncbuf.info(self,
1690 "leaving %s; does not track upstream",
1691 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001693 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001694 if submodules:
1695 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001696 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001697 syncbuf.fail(self, e)
1698 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001699 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001700 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001702 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001703
1704 # See if we can perform a fast forward merge. This can happen if our
1705 # branch isn't in the exact same state as we last published.
1706 try:
1707 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1708 # Skip the published logic.
1709 pub = False
1710 except GitError:
1711 pub = self.WasPublished(branch.name, all_refs)
1712
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001713 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001714 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001715 if not_merged:
1716 if upstream_gain:
1717 # The user has published this branch and some of those
1718 # commits are not yet merged upstream. We do not want
1719 # to rewrite the published commits so we punt.
1720 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001721 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001722 "branch %s is published (but not merged) and is now "
1723 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001724 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001725 elif pub == head:
1726 # All published commits are merged, and thus we are a
1727 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001728 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001729 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001730 if submodules:
1731 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001732 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001733
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001734 # Examine the local commits not in the remote. Find the
1735 # last one attributed to this user, if any.
1736 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001737 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001738 last_mine = None
1739 cnt_mine = 0
1740 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001741 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001742 if committer_email == self.UserEmail:
1743 last_mine = commit_id
1744 cnt_mine += 1
1745
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001746 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001747 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001748
1749 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001750 syncbuf.fail(self, _DirtyError())
1751 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001753 # If the upstream switched on us, warn the user.
1754 #
1755 if branch.merge != self.revisionExpr:
1756 if branch.merge and self.revisionExpr:
1757 syncbuf.info(self,
1758 'manifest switched %s...%s',
1759 branch.merge,
1760 self.revisionExpr)
1761 elif branch.merge:
1762 syncbuf.info(self,
1763 'manifest no longer tracks %s',
1764 branch.merge)
1765
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001766 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001767 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001768 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001770 syncbuf.info(self,
1771 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001772 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001774 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001775 if not ID_RE.match(self.revisionExpr):
1776 # in case of manifest sync the revisionExpr might be a SHA1
1777 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001778 if not branch.merge.startswith('refs/'):
1779 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780 branch.Save()
1781
Mike Pontillod3153822012-02-28 11:53:24 -08001782 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001783 def _docopyandlink():
1784 self._CopyAndLinkFiles()
1785
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001786 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001787 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001788 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001789 if submodules:
1790 syncbuf.later2(self, _dosubmodules)
1791 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001792 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001793 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001794 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001795 if submodules:
1796 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001797 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001798 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001799 syncbuf.fail(self, e)
1800 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001802 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001803 if submodules:
1804 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001805
Mike Frysingere6a202f2019-08-02 15:57:57 -04001806 def AddCopyFile(self, src, dest, topdir):
1807 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001808
Mike Frysingere6a202f2019-08-02 15:57:57 -04001809 No filesystem changes occur here. Actual copying happens later on.
1810
1811 Paths should have basic validation run on them before being queued.
1812 Further checking will be handled when the actual copy happens.
1813 """
1814 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1815
1816 def AddLinkFile(self, src, dest, topdir):
1817 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1818
1819 No filesystem changes occur here. Actual linking happens later on.
1820
1821 Paths should have basic validation run on them before being queued.
1822 Further checking will be handled when the actual link happens.
1823 """
1824 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001825
James W. Mills24c13082012-04-12 15:04:13 -05001826 def AddAnnotation(self, name, value, keep):
1827 self.annotations.append(_Annotation(name, value, keep))
1828
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001829 def DownloadPatchSet(self, change_id, patch_id):
1830 """Download a single patch set of a single change to FETCH_HEAD.
1831 """
1832 remote = self.GetRemote(self.remote.name)
1833
1834 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001835 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001836 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001837 if GitCommand(self, cmd, bare=True).Wait() != 0:
1838 return None
1839 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001840 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001841 change_id,
1842 patch_id,
1843 self.bare_git.rev_parse('FETCH_HEAD'))
1844
Mike Frysingerc0d18662020-02-19 19:19:18 -05001845 def DeleteWorktree(self, quiet=False, force=False):
1846 """Delete the source checkout and any other housekeeping tasks.
1847
1848 This currently leaves behind the internal .repo/ cache state. This helps
1849 when switching branches or manifest changes get reverted as we don't have
1850 to redownload all the git objects. But we should do some GC at some point.
1851
1852 Args:
1853 quiet: Whether to hide normal messages.
1854 force: Always delete tree even if dirty.
1855
1856 Returns:
1857 True if the worktree was completely cleaned out.
1858 """
1859 if self.IsDirty():
1860 if force:
1861 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1862 (self.relpath,), file=sys.stderr)
1863 else:
1864 print('error: %s: Cannot remove project: uncommitted changes are '
1865 'present.\n' % (self.relpath,), file=sys.stderr)
1866 return False
1867
1868 if not quiet:
1869 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1870
1871 # Unlock and delink from the main worktree. We don't use git's worktree
1872 # remove because it will recursively delete projects -- we handle that
1873 # ourselves below. https://crbug.com/git/48
1874 if self.use_git_worktrees:
1875 needle = platform_utils.realpath(self.gitdir)
1876 # Find the git worktree commondir under .repo/worktrees/.
1877 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1878 assert output.startswith('worktree '), output
1879 commondir = output[9:]
1880 # Walk each of the git worktrees to see where they point.
1881 configs = os.path.join(commondir, 'worktrees')
1882 for name in os.listdir(configs):
1883 gitdir = os.path.join(configs, name, 'gitdir')
1884 with open(gitdir) as fp:
1885 relpath = fp.read().strip()
1886 # Resolve the checkout path and see if it matches this project.
1887 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1888 if fullpath == needle:
1889 platform_utils.rmtree(os.path.join(configs, name))
1890
1891 # Delete the .git directory first, so we're less likely to have a partially
1892 # working git repository around. There shouldn't be any git projects here,
1893 # so rmtree works.
1894
1895 # Try to remove plain files first in case of git worktrees. If this fails
1896 # for any reason, we'll fall back to rmtree, and that'll display errors if
1897 # it can't remove things either.
1898 try:
1899 platform_utils.remove(self.gitdir)
1900 except OSError:
1901 pass
1902 try:
1903 platform_utils.rmtree(self.gitdir)
1904 except OSError as e:
1905 if e.errno != errno.ENOENT:
1906 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1907 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1908 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1909 return False
1910
1911 # Delete everything under the worktree, except for directories that contain
1912 # another git project.
1913 dirs_to_remove = []
1914 failed = False
1915 for root, dirs, files in platform_utils.walk(self.worktree):
1916 for f in files:
1917 path = os.path.join(root, f)
1918 try:
1919 platform_utils.remove(path)
1920 except OSError as e:
1921 if e.errno != errno.ENOENT:
1922 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1923 failed = True
1924 dirs[:] = [d for d in dirs
1925 if not os.path.lexists(os.path.join(root, d, '.git'))]
1926 dirs_to_remove += [os.path.join(root, d) for d in dirs
1927 if os.path.join(root, d) not in dirs_to_remove]
1928 for d in reversed(dirs_to_remove):
1929 if platform_utils.islink(d):
1930 try:
1931 platform_utils.remove(d)
1932 except OSError as e:
1933 if e.errno != errno.ENOENT:
1934 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1935 failed = True
1936 elif not platform_utils.listdir(d):
1937 try:
1938 platform_utils.rmdir(d)
1939 except OSError as e:
1940 if e.errno != errno.ENOENT:
1941 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1942 failed = True
1943 if failed:
1944 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1945 file=sys.stderr)
1946 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1947 return False
1948
1949 # Try deleting parent dirs if they are empty.
1950 path = self.worktree
1951 while path != self.manifest.topdir:
1952 try:
1953 platform_utils.rmdir(path)
1954 except OSError as e:
1955 if e.errno != errno.ENOENT:
1956 break
1957 path = os.path.dirname(path)
1958
1959 return True
1960
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001961# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001962 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001963 """Create a new branch off the manifest's revision.
1964 """
Simran Basib9a1b732015-08-20 12:19:28 -07001965 if not branch_merge:
1966 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001967 head = self.work_git.GetHead()
1968 if head == (R_HEADS + name):
1969 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970
David Pursehouse8a68ff92012-09-24 12:15:13 +09001971 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001972 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001973 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001974 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001975 capture_stdout=True,
1976 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001977
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001978 branch = self.GetBranch(name)
1979 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001980 branch.merge = branch_merge
1981 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1982 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001983
1984 if revision is None:
1985 revid = self.GetRevisionId(all_refs)
1986 else:
1987 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001988
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001989 if head.startswith(R_HEADS):
1990 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001991 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001992 except KeyError:
1993 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001994 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001995 ref = R_HEADS + name
1996 self.work_git.update_ref(ref, revid)
1997 self.work_git.symbolic_ref(HEAD, ref)
1998 branch.Save()
1999 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002000
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002001 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002002 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01002003 capture_stdout=True,
2004 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002005 branch.Save()
2006 return True
2007 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002008
Wink Saville02d79452009-04-10 13:01:24 -07002009 def CheckoutBranch(self, name):
2010 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002011
2012 Args:
2013 name: The name of the branch to checkout.
2014
2015 Returns:
2016 True if the checkout succeeded; False if it didn't; None if the branch
2017 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002018 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002019 rev = R_HEADS + name
2020 head = self.work_git.GetHead()
2021 if head == rev:
2022 # Already on the branch
2023 #
2024 return True
Wink Saville02d79452009-04-10 13:01:24 -07002025
David Pursehouse8a68ff92012-09-24 12:15:13 +09002026 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002027 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002028 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002029 except KeyError:
2030 # Branch does not exist in this project
2031 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002032 return None
Wink Saville02d79452009-04-10 13:01:24 -07002033
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002034 if head.startswith(R_HEADS):
2035 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002036 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002037 except KeyError:
2038 head = None
2039
2040 if head == revid:
2041 # Same revision; just update HEAD to point to the new
2042 # target branch, but otherwise take no other action.
2043 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05002044 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
2045 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002046 return True
2047
2048 return GitCommand(self,
2049 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002050 capture_stdout=True,
2051 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002052
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002053 def AbandonBranch(self, name):
2054 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002055
2056 Args:
2057 name: The name of the branch to abandon.
2058
2059 Returns:
2060 True if the abandon succeeded; False if it didn't; None if the branch
2061 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002062 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002063 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002064 all_refs = self.bare_ref.all
2065 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002066 # Doesn't exist
2067 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002068
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002069 head = self.work_git.GetHead()
2070 if head == rev:
2071 # We can't destroy the branch while we are sitting
2072 # on it. Switch to a detached HEAD.
2073 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002074 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002075
David Pursehouse8a68ff92012-09-24 12:15:13 +09002076 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002077 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05002078 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002079 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002080 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002081
2082 return GitCommand(self,
2083 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002084 capture_stdout=True,
2085 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002086
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002087 def PruneHeads(self):
2088 """Prune any topic branches already merged into upstream.
2089 """
2090 cb = self.CurrentBranch
2091 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002092 left = self._allrefs
2093 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002094 if name.startswith(R_HEADS):
2095 name = name[len(R_HEADS):]
2096 if cb is None or name != cb:
2097 kill.append(name)
2098
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002099 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002100 if cb is not None \
2101 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002102 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103 self.work_git.DetachHead(HEAD)
2104 kill.append(cb)
2105
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002106 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002107 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002108
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109 try:
2110 self.bare_git.DetachHead(rev)
2111
2112 b = ['branch', '-d']
2113 b.extend(kill)
2114 b = GitCommand(self, b, bare=True,
2115 capture_stdout=True,
2116 capture_stderr=True)
2117 b.Wait()
2118 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002119 if ID_RE.match(old):
2120 self.bare_git.DetachHead(old)
2121 else:
2122 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002123 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002124
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002125 for branch in kill:
2126 if (R_HEADS + branch) not in left:
2127 self.CleanPublishedCache()
2128 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002129
2130 if cb and cb not in kill:
2131 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002132 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133
2134 kept = []
2135 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002136 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137 branch = self.GetBranch(branch)
2138 base = branch.LocalMerge
2139 if not base:
2140 base = rev
2141 kept.append(ReviewableBranch(self, branch, base))
2142 return kept
2143
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002144# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002145 def GetRegisteredSubprojects(self):
2146 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002147
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002148 def rec(subprojects):
2149 if not subprojects:
2150 return
2151 result.extend(subprojects)
2152 for p in subprojects:
2153 rec(p.subprojects)
2154 rec(self.subprojects)
2155 return result
2156
2157 def _GetSubmodules(self):
2158 # Unfortunately we cannot call `git submodule status --recursive` here
2159 # because the working tree might not exist yet, and it cannot be used
2160 # without a working tree in its current implementation.
2161
2162 def get_submodules(gitdir, rev):
2163 # Parse .gitmodules for submodule sub_paths and sub_urls
2164 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2165 if not sub_paths:
2166 return []
2167 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2168 # revision of submodule repository
2169 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2170 submodules = []
2171 for sub_path, sub_url in zip(sub_paths, sub_urls):
2172 try:
2173 sub_rev = sub_revs[sub_path]
2174 except KeyError:
2175 # Ignore non-exist submodules
2176 continue
2177 submodules.append((sub_rev, sub_path, sub_url))
2178 return submodules
2179
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002180 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2181 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002182
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002183 def parse_gitmodules(gitdir, rev):
2184 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2185 try:
Anthony King7bdac712014-07-16 12:56:40 +01002186 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2187 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002188 except GitError:
2189 return [], []
2190 if p.Wait() != 0:
2191 return [], []
2192
2193 gitmodules_lines = []
2194 fd, temp_gitmodules_path = tempfile.mkstemp()
2195 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002196 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002197 os.close(fd)
2198 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002199 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2200 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002201 if p.Wait() != 0:
2202 return [], []
2203 gitmodules_lines = p.stdout.split('\n')
2204 except GitError:
2205 return [], []
2206 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002207 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002208
2209 names = set()
2210 paths = {}
2211 urls = {}
2212 for line in gitmodules_lines:
2213 if not line:
2214 continue
2215 m = re_path.match(line)
2216 if m:
2217 names.add(m.group(1))
2218 paths[m.group(1)] = m.group(2)
2219 continue
2220 m = re_url.match(line)
2221 if m:
2222 names.add(m.group(1))
2223 urls[m.group(1)] = m.group(2)
2224 continue
2225 names = sorted(names)
2226 return ([paths.get(name, '') for name in names],
2227 [urls.get(name, '') for name in names])
2228
2229 def git_ls_tree(gitdir, rev, paths):
2230 cmd = ['ls-tree', rev, '--']
2231 cmd.extend(paths)
2232 try:
Anthony King7bdac712014-07-16 12:56:40 +01002233 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2234 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002235 except GitError:
2236 return []
2237 if p.Wait() != 0:
2238 return []
2239 objects = {}
2240 for line in p.stdout.split('\n'):
2241 if not line.strip():
2242 continue
2243 object_rev, object_path = line.split()[2:4]
2244 objects[object_path] = object_rev
2245 return objects
2246
2247 try:
2248 rev = self.GetRevisionId()
2249 except GitError:
2250 return []
2251 return get_submodules(self.gitdir, rev)
2252
2253 def GetDerivedSubprojects(self):
2254 result = []
2255 if not self.Exists:
2256 # If git repo does not exist yet, querying its submodules will
2257 # mess up its states; so return here.
2258 return result
2259 for rev, path, url in self._GetSubmodules():
2260 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002261 relpath, worktree, gitdir, objdir = \
2262 self.manifest.GetSubprojectPaths(self, name, path)
2263 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002264 if project:
2265 result.extend(project.GetDerivedSubprojects())
2266 continue
David James8d201162013-10-11 17:03:19 -07002267
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002268 if url.startswith('..'):
2269 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002270 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002271 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002272 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002273 review=self.remote.review,
2274 revision=self.remote.revision)
2275 subproject = Project(manifest=self.manifest,
2276 name=name,
2277 remote=remote,
2278 gitdir=gitdir,
2279 objdir=objdir,
2280 worktree=worktree,
2281 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002282 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002283 revisionId=rev,
2284 rebase=self.rebase,
2285 groups=self.groups,
2286 sync_c=self.sync_c,
2287 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002288 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002289 parent=self,
2290 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002291 result.append(subproject)
2292 result.extend(subproject.GetDerivedSubprojects())
2293 return result
2294
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002295# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002296 def EnableRepositoryExtension(self, key, value='true', version=1):
2297 """Enable git repository extension |key| with |value|.
2298
2299 Args:
2300 key: The extension to enabled. Omit the "extensions." prefix.
2301 value: The value to use for the extension.
2302 version: The minimum git repository version needed.
2303 """
2304 # Make sure the git repo version is new enough already.
2305 found_version = self.config.GetInt('core.repositoryFormatVersion')
2306 if found_version is None:
2307 found_version = 0
2308 if found_version < version:
2309 self.config.SetString('core.repositoryFormatVersion', str(version))
2310
2311 # Enable the extension!
2312 self.config.SetString('extensions.%s' % (key,), value)
2313
Mike Frysinger50a81de2020-09-06 15:51:21 -04002314 def ResolveRemoteHead(self, name=None):
2315 """Find out what the default branch (HEAD) points to.
2316
2317 Normally this points to refs/heads/master, but projects are moving to main.
2318 Support whatever the server uses rather than hardcoding "master" ourselves.
2319 """
2320 if name is None:
2321 name = self.remote.name
2322
2323 # The output will look like (NB: tabs are separators):
2324 # ref: refs/heads/master HEAD
2325 # 5f6803b100bb3cd0f534e96e88c91373e8ed1c44 HEAD
2326 output = self.bare_git.ls_remote('-q', '--symref', '--exit-code', name, 'HEAD')
2327
2328 for line in output.splitlines():
2329 lhs, rhs = line.split('\t', 1)
2330 if rhs == 'HEAD' and lhs.startswith('ref:'):
2331 return lhs[4:].strip()
2332
2333 return None
2334
Zac Livingstone4332262017-06-16 08:56:09 -06002335 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002336 try:
2337 # if revision (sha or tag) is not present then following function
2338 # throws an error.
2339 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2340 return True
2341 except GitError:
2342 # There is no such persistent revision. We have to fetch it.
2343 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002344
Julien Campergue335f5ef2013-10-16 11:02:35 +02002345 def _FetchArchive(self, tarpath, cwd=None):
2346 cmd = ['archive', '-v', '-o', tarpath]
2347 cmd.append('--remote=%s' % self.remote.url)
2348 cmd.append('--prefix=%s/' % self.relpath)
2349 cmd.append(self.revisionExpr)
2350
2351 command = GitCommand(self, cmd, cwd=cwd,
2352 capture_stdout=True,
2353 capture_stderr=True)
2354
2355 if command.Wait() != 0:
2356 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2357
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002358 def _RemoteFetch(self, name=None,
2359 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002360 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002361 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002362 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002363 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002364 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002365 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002366 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002367 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002368 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002369 clone_filter=None,
2370 retry_fetches=2,
2371 retry_sleep_initial_sec=4.0,
2372 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002373 is_sha1 = False
2374 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002375 # The depth should not be used when fetching to a mirror because
2376 # it will result in a shallow repository that cannot be cloned or
2377 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002378 # The repo project should also never be synced with partial depth.
2379 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2380 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002381
Shawn Pearce69e04d82014-01-29 12:48:54 -08002382 if depth:
2383 current_branch_only = True
2384
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002385 if ID_RE.match(self.revisionExpr) is not None:
2386 is_sha1 = True
2387
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002388 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002389 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002390 # this is a tag and its sha1 value should never change
2391 tag_name = self.revisionExpr[len(R_TAGS):]
2392
2393 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002394 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002395 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002396 print('Skipped fetching project %s (already have persistent ref)'
2397 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002398 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002399 if is_sha1 and not depth:
2400 # When syncing a specific commit and --depth is not set:
2401 # * if upstream is explicitly specified and is not a sha1, fetch only
2402 # upstream as users expect only upstream to be fetch.
2403 # Note: The commit might not be in upstream in which case the sync
2404 # will fail.
2405 # * otherwise, fetch all branches to make sure we end up with the
2406 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002407 if self.upstream:
2408 current_branch_only = not ID_RE.match(self.upstream)
2409 else:
2410 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002411
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002412 if not name:
2413 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002414
2415 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002416 remote = self.GetRemote(name)
2417 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002418 ssh_proxy = True
2419
Shawn O. Pearce88443382010-10-08 10:02:09 +02002420 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002421 if alt_dir and 'objects' == os.path.basename(alt_dir):
2422 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002423 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2424 remote = self.GetRemote(name)
2425
David Pursehouse8a68ff92012-09-24 12:15:13 +09002426 all_refs = self.bare_ref.all
2427 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002428 tmp = set()
2429
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302430 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002431 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002432 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002433 all_refs[r] = ref_id
2434 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002435 continue
2436
David Pursehouse8a68ff92012-09-24 12:15:13 +09002437 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002438 continue
2439
David Pursehouse8a68ff92012-09-24 12:15:13 +09002440 r = 'refs/_alt/%s' % ref_id
2441 all_refs[r] = ref_id
2442 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002443 tmp.add(r)
2444
heping3d7bbc92017-04-12 19:51:47 +08002445 tmp_packed_lines = []
2446 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002447
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302448 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002449 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002450 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002451 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002452 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002453
heping3d7bbc92017-04-12 19:51:47 +08002454 tmp_packed = ''.join(tmp_packed_lines)
2455 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002456 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002457 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002458 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002459
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002460 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002461
Xin Li745be2e2019-06-03 11:24:30 -07002462 if clone_filter:
2463 git_require((2, 19, 0), fail=True, msg='partial clones')
2464 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002465 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002466
Conley Owensf97e8382015-01-21 11:12:46 -08002467 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002468 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002469 else:
2470 # If this repo has shallow objects, then we don't know which refs have
2471 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2472 # do this with projects that don't have shallow objects, since it is less
2473 # efficient.
2474 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2475 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002476
Mike Frysinger4847e052020-02-22 00:07:35 -05002477 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002478 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002479 if not quiet and sys.stdout.isatty():
2480 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002481 if not self.worktree:
2482 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002483 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002484
Mike Frysingere57f1142019-03-18 21:27:54 -04002485 if force_sync:
2486 cmd.append('--force')
2487
David Pursehouse74cfd272015-10-14 10:50:15 +09002488 if prune:
2489 cmd.append('--prune')
2490
Martin Kellye4e94d22017-03-21 16:05:12 -07002491 if submodules:
2492 cmd.append('--recurse-submodules=on-demand')
2493
Kuang-che Wu6856f982019-11-25 12:37:55 +08002494 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002495 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002496 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002497 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002498 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002499 spec.append('tag')
2500 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002501
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302502 if self.manifest.IsMirror and not current_branch_only:
2503 branch = None
2504 else:
2505 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002506 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002507 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002508 # Shallow checkout of a specific commit, fetch from that commit and not
2509 # the heads only as the commit might be deeper in the history.
2510 spec.append(branch)
2511 else:
2512 if is_sha1:
2513 branch = self.upstream
2514 if branch is not None and branch.strip():
2515 if not branch.startswith('refs/'):
2516 branch = R_HEADS + branch
2517 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2518
2519 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2520 # whole repo.
2521 if self.manifest.IsMirror and not spec:
2522 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2523
2524 # If using depth then we should not get all the tags since they may
2525 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002526 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002527 cmd.append('--no-tags')
2528 else:
2529 cmd.append('--tags')
2530 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2531
Conley Owens80b87fe2014-05-09 17:13:44 -07002532 cmd.extend(spec)
2533
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002534 # At least one retry minimum due to git remote prune.
2535 retry_fetches = max(retry_fetches, 2)
2536 retry_cur_sleep = retry_sleep_initial_sec
2537 ok = prune_tried = False
2538 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002539 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002540 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002541 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002542 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002543 ok = True
2544 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002545
2546 # Retry later due to HTTP 429 Too Many Requests.
2547 elif ('error:' in gitcmd.stderr and
2548 'HTTP 429' in gitcmd.stderr):
2549 if not quiet:
2550 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2551 file=sys.stderr)
2552 time.sleep(retry_cur_sleep)
2553 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2554 MAXIMUM_RETRY_SLEEP_SEC)
2555 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2556 RETRY_JITTER_PERCENT))
2557 continue
2558
2559 # If this is not last attempt, try 'git remote prune'.
2560 elif (try_n < retry_fetches - 1 and
2561 'error:' in gitcmd.stderr and
2562 'git remote prune' in gitcmd.stderr and
2563 not prune_tried):
2564 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002565 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002566 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002567 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002568 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002569 break
2570 continue
Brian Harring14a66742012-09-28 20:21:57 -07002571 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002572 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2573 # in sha1 mode, we just tried sync'ing from the upstream field; it
2574 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002575 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002576 elif ret < 0:
2577 # Git died with a signal, exit immediately
2578 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002579 if not verbose:
2580 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002581 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002582
2583 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002584 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002585 if old_packed != '':
2586 _lwrite(packed_refs, old_packed)
2587 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002588 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002589 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002590
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002591 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002592 # We just synced the upstream given branch; verify we
2593 # got what we wanted, else trigger a second run of all
2594 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002595 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002596 # Sync the current branch only with depth set to None.
2597 # We always pass depth=None down to avoid infinite recursion.
2598 return self._RemoteFetch(
2599 name=name, quiet=quiet, verbose=verbose,
2600 current_branch_only=current_branch_only and depth,
2601 initial=False, alt_dir=alt_dir,
2602 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002603
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002604 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002605
Mike Frysingere50b6a72020-02-19 01:45:48 -05002606 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002607 if initial and \
2608 (self.manifest.manifestProject.config.GetString('repo.depth') or
2609 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002610 return False
2611
2612 remote = self.GetRemote(self.remote.name)
2613 bundle_url = remote.url + '/clone.bundle'
2614 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002615 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2616 'persistent-http',
2617 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002618 return False
2619
2620 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2621 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2622
2623 exist_dst = os.path.exists(bundle_dst)
2624 exist_tmp = os.path.exists(bundle_tmp)
2625
2626 if not initial and not exist_dst and not exist_tmp:
2627 return False
2628
2629 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002630 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2631 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002632 if not exist_dst:
2633 return False
2634
2635 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002636 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002637 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002638 if not quiet and sys.stdout.isatty():
2639 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002640 if not self.worktree:
2641 cmd.append('--update-head-ok')
2642 cmd.append(bundle_dst)
2643 for f in remote.fetch:
2644 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002645 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002646
2647 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002648 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002649 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002650 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002651 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002652 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002653
Mike Frysingere50b6a72020-02-19 01:45:48 -05002654 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002655 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002656 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002657
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002658 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002659 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002660 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002661 if os.path.exists(tmpPath):
2662 size = os.stat(tmpPath).st_size
2663 if size >= 1024:
2664 cmd += ['--continue-at', '%d' % (size,)]
2665 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002666 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002667 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002668 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002669 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002670 if proxy:
2671 cmd += ['--proxy', proxy]
2672 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2673 cmd += ['--proxy', os.environ['http_proxy']]
2674 if srcUrl.startswith('persistent-https'):
2675 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2676 elif srcUrl.startswith('persistent-http'):
2677 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002678 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002679
Dave Borowitz137d0132015-01-02 11:12:54 -08002680 if IsTrace():
2681 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002682 if verbose:
2683 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2684 stdout = None if verbose else subprocess.PIPE
2685 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002686 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002687 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002688 except OSError:
2689 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002690
Mike Frysingere50b6a72020-02-19 01:45:48 -05002691 (output, _) = proc.communicate()
2692 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002693
Dave Borowitz137d0132015-01-02 11:12:54 -08002694 if curlret == 22:
2695 # From curl man page:
2696 # 22: HTTP page not retrieved. The requested url was not found or
2697 # returned another error with the HTTP error code being 400 or above.
2698 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002699 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002700 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2701 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002702 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002703 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002704 elif curlret and not verbose and output:
2705 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002706
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002707 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002708 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002709 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002710 return True
2711 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002712 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002713 return False
2714 else:
2715 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002716
Kris Giesingc8d882a2014-12-23 13:02:32 -08002717 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002718 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002719 with open(path, 'rb') as f:
2720 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002721 return True
2722 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002723 if not quiet:
2724 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002725 return False
2726 except OSError:
2727 return False
2728
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002729 def _Checkout(self, rev, quiet=False):
2730 cmd = ['checkout']
2731 if quiet:
2732 cmd.append('-q')
2733 cmd.append(rev)
2734 cmd.append('--')
2735 if GitCommand(self, cmd).Wait() != 0:
2736 if self._allrefs:
2737 raise GitError('%s checkout %s ' % (self.name, rev))
2738
Mike Frysinger915fda12020-03-22 12:15:20 -04002739 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002740 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002741 if ffonly:
2742 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002743 if record_origin:
2744 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002745 cmd.append(rev)
2746 cmd.append('--')
2747 if GitCommand(self, cmd).Wait() != 0:
2748 if self._allrefs:
2749 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2750
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302751 def _LsRemote(self, refs):
2752 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302753 p = GitCommand(self, cmd, capture_stdout=True)
2754 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002755 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302756 return None
2757
Anthony King7bdac712014-07-16 12:56:40 +01002758 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002759 cmd = ['revert']
2760 cmd.append('--no-edit')
2761 cmd.append(rev)
2762 cmd.append('--')
2763 if GitCommand(self, cmd).Wait() != 0:
2764 if self._allrefs:
2765 raise GitError('%s revert %s ' % (self.name, rev))
2766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767 def _ResetHard(self, rev, quiet=True):
2768 cmd = ['reset', '--hard']
2769 if quiet:
2770 cmd.append('-q')
2771 cmd.append(rev)
2772 if GitCommand(self, cmd).Wait() != 0:
2773 raise GitError('%s reset --hard %s ' % (self.name, rev))
2774
Martin Kellye4e94d22017-03-21 16:05:12 -07002775 def _SyncSubmodules(self, quiet=True):
2776 cmd = ['submodule', 'update', '--init', '--recursive']
2777 if quiet:
2778 cmd.append('-q')
2779 if GitCommand(self, cmd).Wait() != 0:
2780 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2781
Anthony King7bdac712014-07-16 12:56:40 +01002782 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002783 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002784 if onto is not None:
2785 cmd.extend(['--onto', onto])
2786 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002787 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002788 raise GitError('%s rebase %s ' % (self.name, upstream))
2789
Pierre Tardy3d125942012-05-04 12:18:12 +02002790 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002791 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002792 if ffonly:
2793 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002794 if GitCommand(self, cmd).Wait() != 0:
2795 raise GitError('%s merge %s ' % (self.name, head))
2796
David Pursehousee8ace262020-02-13 12:41:15 +09002797 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002798 init_git_dir = not os.path.exists(self.gitdir)
2799 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002800 try:
2801 # Initialize the bare repository, which contains all of the objects.
2802 if init_obj_dir:
2803 os.makedirs(self.objdir)
2804 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002805
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002806 if self.use_git_worktrees:
2807 # Set up the m/ space to point to the worktree-specific ref space.
2808 # We'll update the worktree-specific ref space on each checkout.
2809 if self.manifest.branch:
2810 self.bare_git.symbolic_ref(
2811 '-m', 'redirecting to worktree scope',
2812 R_M + self.manifest.branch,
2813 R_WORKTREE_M + self.manifest.branch)
2814
2815 # Enable per-worktree config file support if possible. This is more a
2816 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002817 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002818 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002819
Kevin Degib1a07b82015-07-27 13:33:43 -06002820 # If we have a separate directory to hold refs, initialize it as well.
2821 if self.objdir != self.gitdir:
2822 if init_git_dir:
2823 os.makedirs(self.gitdir)
2824
2825 if init_obj_dir or init_git_dir:
2826 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2827 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002828 try:
2829 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2830 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002831 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002832 print("Retrying clone after deleting %s" %
2833 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002834 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002835 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2836 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002837 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002838 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002839 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2840 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002841 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002842 raise e
2843 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002844
Kevin Degi384b3c52014-10-16 16:02:58 -06002845 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002846 mp = self.manifest.manifestProject
2847 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002848
Kevin Degib1a07b82015-07-27 13:33:43 -06002849 if ref_dir or mirror_git:
2850 if not mirror_git:
2851 mirror_git = os.path.join(ref_dir, self.name + '.git')
2852 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2853 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002854 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2855 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002856
Kevin Degib1a07b82015-07-27 13:33:43 -06002857 if os.path.exists(mirror_git):
2858 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002859 elif os.path.exists(repo_git):
2860 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002861 elif os.path.exists(worktrees_git):
2862 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002863 else:
2864 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002865
Kevin Degib1a07b82015-07-27 13:33:43 -06002866 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002867 if not os.path.isabs(ref_dir):
2868 # The alternate directory is relative to the object database.
2869 ref_dir = os.path.relpath(ref_dir,
2870 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002871 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2872 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002873
David Pursehousee8ace262020-02-13 12:41:15 +09002874 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002875
2876 m = self.manifest.manifestProject.config
2877 for key in ['user.name', 'user.email']:
2878 if m.Has(key, include_defaults=False):
2879 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002880 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002881 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002882 if self.manifest.IsMirror:
2883 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002884 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002885 self.config.SetString('core.bare', None)
2886 except Exception:
2887 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002888 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002889 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002890 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002891 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002892
David Pursehousee8ace262020-02-13 12:41:15 +09002893 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002894 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002895 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002896
David Pursehousee8ace262020-02-13 12:41:15 +09002897 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002898 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002899 if not os.path.exists(hooks):
2900 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002901 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002902 name = os.path.basename(stock_hook)
2903
Victor Boivie65e0f352011-04-18 11:23:29 +02002904 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002905 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002906 # Don't install a Gerrit Code Review hook if this
2907 # project does not appear to use it for reviews.
2908 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002909 # Since the manifest project is one of those, but also
2910 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002911 continue
2912
2913 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002914 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002915 continue
2916 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002917 # If the files are the same, we'll leave it alone. We create symlinks
2918 # below by default but fallback to hardlinks if the OS blocks them.
2919 # So if we're here, it's probably because we made a hardlink below.
2920 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002921 if not quiet:
2922 _warn("%s: Not replacing locally modified %s hook",
2923 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002924 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002925 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002926 platform_utils.symlink(
2927 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002928 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002929 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002930 try:
2931 os.link(stock_hook, dst)
2932 except OSError:
2933 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002934 else:
2935 raise
2936
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002937 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002938 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002940 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002941 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002942 remote.review = self.remote.review
2943 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002944
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002945 if self.worktree:
2946 remote.ResetFetch(mirror=False)
2947 else:
2948 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002949 remote.Save()
2950
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002951 def _InitMRef(self):
2952 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002953 if self.use_git_worktrees:
2954 # We can't update this ref with git worktrees until it exists.
2955 # We'll wait until the initial checkout to set it.
2956 if not os.path.exists(self.worktree):
2957 return
2958
2959 base = R_WORKTREE_M
2960 active_git = self.work_git
2961 else:
2962 base = R_M
2963 active_git = self.bare_git
2964
2965 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002967 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002968 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002969
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002970 def _InitAnyMRef(self, ref, active_git):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002971 cur = self.bare_ref.symref(ref)
2972
2973 if self.revisionId:
2974 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2975 msg = 'manifest set to %s' % self.revisionId
2976 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002977 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002978 else:
2979 remote = self.GetRemote(self.remote.name)
2980 dst = remote.ToLocal(self.revisionExpr)
2981 if cur != dst:
2982 msg = 'manifest set to %s' % self.revisionExpr
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002983 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002984
Kevin Degi384b3c52014-10-16 16:02:58 -06002985 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002986 # Git worktrees don't use symlinks to share at all.
2987 if self.use_git_worktrees:
2988 return
2989
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002990 symlink_files = self.shareable_files[:]
2991 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002992 if share_refs:
2993 symlink_files += self.working_tree_files
2994 symlink_dirs += self.working_tree_dirs
2995 to_symlink = symlink_files + symlink_dirs
2996 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002997 # Try to self-heal a bit in simple cases.
2998 dst_path = os.path.join(destdir, name)
2999 src_path = os.path.join(srcdir, name)
3000
3001 if name in self.working_tree_dirs:
3002 # If the dir is missing under .repo/projects/, create it.
3003 if not os.path.exists(src_path):
3004 os.makedirs(src_path)
3005
3006 elif name in self.working_tree_files:
3007 # If it's a file under the checkout .git/ and the .repo/projects/ has
3008 # nothing, move the file under the .repo/projects/ tree.
3009 if not os.path.exists(src_path) and os.path.isfile(dst_path):
3010 platform_utils.rename(dst_path, src_path)
3011
3012 # If the path exists under the .repo/projects/ and there's no symlink
3013 # under the checkout .git/, recreate the symlink.
3014 if name in self.working_tree_dirs or name in self.working_tree_files:
3015 if os.path.exists(src_path) and not os.path.exists(dst_path):
3016 platform_utils.symlink(
3017 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
3018
3019 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06003020 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05003021 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06003022 # Fail if the links are pointing to the wrong place
3023 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07003024 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07003025 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04003026 'work tree. If you\'re comfortable with the '
3027 'possibility of losing the work tree\'s git metadata,'
3028 ' use `repo sync --force-sync {0}` to '
3029 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06003030
David James8d201162013-10-11 17:03:19 -07003031 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
3032 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
3033
3034 Args:
3035 gitdir: The bare git repository. Must already be initialized.
3036 dotgit: The repository you would like to initialize.
3037 share_refs: If true, |dotgit| will store its refs under |gitdir|.
3038 Only one work tree can store refs under a given |gitdir|.
3039 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
3040 This saves you the effort of initializing |dotgit| yourself.
3041 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07003042 symlink_files = self.shareable_files[:]
3043 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07003044 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06003045 symlink_files += self.working_tree_files
3046 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07003047 to_symlink = symlink_files + symlink_dirs
3048
3049 to_copy = []
3050 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07003051 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07003052
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003053 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07003054 for name in set(to_copy).union(to_symlink):
3055 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003056 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07003057 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07003058
Kevin Degi384b3c52014-10-16 16:02:58 -06003059 if os.path.lexists(dst):
3060 continue
David James8d201162013-10-11 17:03:19 -07003061
3062 # If the source dir doesn't exist, create an empty dir.
3063 if name in symlink_dirs and not os.path.lexists(src):
3064 os.makedirs(src)
3065
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003066 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07003067 platform_utils.symlink(
3068 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003069 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07003070 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003071 shutil.copytree(src, dst)
3072 elif os.path.isfile(src):
3073 shutil.copy(src, dst)
3074
Conley Owens80b87fe2014-05-09 17:13:44 -07003075 # If the source file doesn't exist, ensure the destination
3076 # file doesn't either.
3077 if name in symlink_files and not os.path.lexists(src):
3078 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003079 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003080 except OSError:
3081 pass
3082
David James8d201162013-10-11 17:03:19 -07003083 except OSError as e:
3084 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003085 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003086 else:
3087 raise
3088
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003089 def _InitGitWorktree(self):
3090 """Init the project using git worktrees."""
3091 self.bare_git.worktree('prune')
3092 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3093 self.worktree, self.GetRevisionId())
3094
3095 # Rewrite the internal state files to use relative paths between the
3096 # checkouts & worktrees.
3097 dotgit = os.path.join(self.worktree, '.git')
3098 with open(dotgit, 'r') as fp:
3099 # Figure out the checkout->worktree path.
3100 setting = fp.read()
3101 assert setting.startswith('gitdir:')
3102 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05003103 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
3104 # of file permissions. Delete it and recreate it from scratch to avoid.
3105 platform_utils.remove(dotgit)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003106 # Use relative path from checkout->worktree.
3107 with open(dotgit, 'w') as fp:
3108 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3109 file=fp)
3110 # Use relative path from worktree->checkout.
3111 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3112 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3113
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05003114 self._InitMRef()
3115
Martin Kellye4e94d22017-03-21 16:05:12 -07003116 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003117 realdotgit = os.path.join(self.worktree, '.git')
3118 tmpdotgit = realdotgit + '.tmp'
3119 init_dotgit = not os.path.exists(realdotgit)
3120 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003121 if self.use_git_worktrees:
3122 self._InitGitWorktree()
3123 self._CopyAndLinkFiles()
3124 return
3125
Mike Frysingerf4545122019-11-11 04:34:16 -05003126 dotgit = tmpdotgit
3127 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3128 os.makedirs(tmpdotgit)
3129 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3130 copy_all=False)
3131 else:
3132 dotgit = realdotgit
3133
Kevin Degib1a07b82015-07-27 13:33:43 -06003134 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003135 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3136 except GitError as e:
3137 if force_sync and not init_dotgit:
3138 try:
3139 platform_utils.rmtree(dotgit)
3140 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003141 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003142 raise e
3143 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003144
Mike Frysingerf4545122019-11-11 04:34:16 -05003145 if init_dotgit:
3146 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003147
Mike Frysingerf4545122019-11-11 04:34:16 -05003148 # Now that the .git dir is fully set up, move it to its final home.
3149 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003150
Mike Frysingerf4545122019-11-11 04:34:16 -05003151 # Finish checking out the worktree.
3152 cmd = ['read-tree', '--reset', '-u']
3153 cmd.append('-v')
3154 cmd.append(HEAD)
3155 if GitCommand(self, cmd).Wait() != 0:
3156 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003157
Mike Frysingerf4545122019-11-11 04:34:16 -05003158 if submodules:
3159 self._SyncSubmodules(quiet=True)
3160 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003161
Renaud Paquay788e9622017-01-27 11:41:12 -08003162 def _get_symlink_error_message(self):
3163 if platform_utils.isWindows():
3164 return ('Unable to create symbolic link. Please re-run the command as '
3165 'Administrator, or see '
3166 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3167 'for other options.')
3168 return 'filesystem must support symlinks'
3169
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003170 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003171 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003172
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003173 def _revlist(self, *args, **kw):
3174 a = []
3175 a.extend(args)
3176 a.append('--')
3177 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003178
3179 @property
3180 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003181 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003182
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003183 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003184 """Get logs between two revisions of this project."""
3185 comp = '..'
3186 if rev1:
3187 revs = [rev1]
3188 if rev2:
3189 revs.extend([comp, rev2])
3190 cmd = ['log', ''.join(revs)]
3191 out = DiffColoring(self.config)
3192 if out.is_on and color:
3193 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003194 if pretty_format is not None:
3195 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003196 if oneline:
3197 cmd.append('--oneline')
3198
3199 try:
3200 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3201 if log.Wait() == 0:
3202 return log.stdout
3203 except GitError:
3204 # worktree may not exist if groups changed for example. In that case,
3205 # try in gitdir instead.
3206 if not os.path.exists(self.worktree):
3207 return self.bare_git.log(*cmd[1:])
3208 else:
3209 raise
3210 return None
3211
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003212 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3213 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003214 """Get the list of logs from this revision to given revisionId"""
3215 logs = {}
3216 selfId = self.GetRevisionId(self._allrefs)
3217 toId = toProject.GetRevisionId(toProject._allrefs)
3218
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003219 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3220 pretty_format=pretty_format)
3221 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3222 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003223 return logs
3224
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003225 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003226
David James8d201162013-10-11 17:03:19 -07003227 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003228 self._project = project
3229 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003230 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003231
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09003232 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
3233 def __getstate__(self):
3234 return (self._project, self._bare, self._gitdir)
3235
3236 def __setstate__(self, state):
3237 self._project, self._bare, self._gitdir = state
3238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003239 def LsOthers(self):
3240 p = GitCommand(self._project,
3241 ['ls-files',
3242 '-z',
3243 '--others',
3244 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003245 bare=False,
David James8d201162013-10-11 17:03:19 -07003246 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003247 capture_stdout=True,
3248 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003249 if p.Wait() == 0:
3250 out = p.stdout
3251 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003252 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003253 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003254 return []
3255
3256 def DiffZ(self, name, *args):
3257 cmd = [name]
3258 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003259 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003260 cmd.extend(args)
3261 p = GitCommand(self._project,
3262 cmd,
David James8d201162013-10-11 17:03:19 -07003263 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003264 bare=False,
3265 capture_stdout=True,
3266 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003267 try:
3268 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003269 if not hasattr(out, 'encode'):
3270 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003271 r = {}
3272 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003273 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003274 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003275 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003276 info = next(out)
3277 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003278 except StopIteration:
3279 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003280
3281 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003282
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003283 def __init__(self, path, omode, nmode, oid, nid, state):
3284 self.path = path
3285 self.src_path = None
3286 self.old_mode = omode
3287 self.new_mode = nmode
3288 self.old_id = oid
3289 self.new_id = nid
3290
3291 if len(state) == 1:
3292 self.status = state
3293 self.level = None
3294 else:
3295 self.status = state[:1]
3296 self.level = state[1:]
3297 while self.level.startswith('0'):
3298 self.level = self.level[1:]
3299
3300 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003301 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003302 if info.status in ('R', 'C'):
3303 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003304 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003305 r[info.path] = info
3306 return r
3307 finally:
3308 p.Wait()
3309
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003310 def GetDotgitPath(self, subpath=None):
3311 """Return the full path to the .git dir.
3312
3313 As a convenience, append |subpath| if provided.
3314 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003315 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003316 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003317 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003318 dotgit = os.path.join(self._project.worktree, '.git')
3319 if os.path.isfile(dotgit):
3320 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3321 with open(dotgit) as fp:
3322 setting = fp.read()
3323 assert setting.startswith('gitdir:')
3324 gitdir = setting.split(':', 1)[1].strip()
3325 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3326
3327 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3328
3329 def GetHead(self):
3330 """Return the ref that HEAD points to."""
3331 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003332 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003333 with open(path) as fd:
3334 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003335 except IOError as e:
3336 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003337 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303338 line = line.decode()
3339 except AttributeError:
3340 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003341 if line.startswith('ref: '):
3342 return line[5:-1]
3343 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003344
3345 def SetHead(self, ref, message=None):
3346 cmdv = []
3347 if message is not None:
3348 cmdv.extend(['-m', message])
3349 cmdv.append(HEAD)
3350 cmdv.append(ref)
3351 self.symbolic_ref(*cmdv)
3352
3353 def DetachHead(self, new, message=None):
3354 cmdv = ['--no-deref']
3355 if message is not None:
3356 cmdv.extend(['-m', message])
3357 cmdv.append(HEAD)
3358 cmdv.append(new)
3359 self.update_ref(*cmdv)
3360
3361 def UpdateRef(self, name, new, old=None,
3362 message=None,
3363 detach=False):
3364 cmdv = []
3365 if message is not None:
3366 cmdv.extend(['-m', message])
3367 if detach:
3368 cmdv.append('--no-deref')
3369 cmdv.append(name)
3370 cmdv.append(new)
3371 if old is not None:
3372 cmdv.append(old)
3373 self.update_ref(*cmdv)
3374
3375 def DeleteRef(self, name, old=None):
3376 if not old:
3377 old = self.rev_parse(name)
3378 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003379 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003380
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003381 def rev_list(self, *args, **kw):
3382 if 'format' in kw:
3383 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3384 else:
3385 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003386 cmdv.extend(args)
3387 p = GitCommand(self._project,
3388 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003389 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003390 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003391 capture_stdout=True,
3392 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003393 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003394 raise GitError('%s rev-list %s: %s' %
3395 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003396 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003397
3398 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003399 """Allow arbitrary git commands using pythonic syntax.
3400
3401 This allows you to do things like:
3402 git_obj.rev_parse('HEAD')
3403
3404 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3405 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003406 Any other positional arguments will be passed to the git command, and the
3407 following keyword arguments are supported:
3408 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003409
3410 Args:
3411 name: The name of the git command to call. Any '_' characters will
3412 be replaced with '-'.
3413
3414 Returns:
3415 A callable object that will try to call git with the named command.
3416 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003417 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003418
Dave Borowitz091f8932012-10-23 17:01:04 -07003419 def runner(*args, **kwargs):
3420 cmdv = []
3421 config = kwargs.pop('config', None)
3422 for k in kwargs:
3423 raise TypeError('%s() got an unexpected keyword argument %r'
3424 % (name, k))
3425 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303426 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003427 cmdv.append('-c')
3428 cmdv.append('%s=%s' % (k, v))
3429 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003430 cmdv.extend(args)
3431 p = GitCommand(self._project,
3432 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003433 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003434 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003435 capture_stdout=True,
3436 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003437 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003438 raise GitError('%s %s: %s' %
3439 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003440 r = p.stdout
3441 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3442 return r[:-1]
3443 return r
3444 return runner
3445
3446
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003447class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003448
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003449 def __str__(self):
3450 return 'prior sync failed; rebase still in progress'
3451
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003452
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003453class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003454
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003455 def __str__(self):
3456 return 'contains uncommitted changes'
3457
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003458
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003459class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003460
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003461 def __init__(self, project, text):
3462 self.project = project
3463 self.text = text
3464
3465 def Print(self, syncbuf):
3466 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3467 syncbuf.out.nl()
3468
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003469
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003470class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003471
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003472 def __init__(self, project, why):
3473 self.project = project
3474 self.why = why
3475
3476 def Print(self, syncbuf):
3477 syncbuf.out.fail('error: %s/: %s',
3478 self.project.relpath,
3479 str(self.why))
3480 syncbuf.out.nl()
3481
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003482
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003483class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003484
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003485 def __init__(self, project, action):
3486 self.project = project
3487 self.action = action
3488
3489 def Run(self, syncbuf):
3490 out = syncbuf.out
3491 out.project('project %s/', self.project.relpath)
3492 out.nl()
3493 try:
3494 self.action()
3495 out.nl()
3496 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003497 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003498 out.nl()
3499 return False
3500
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003501
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003502class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003503
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003504 def __init__(self, config):
3505 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003506 self.project = self.printer('header', attr='bold')
3507 self.info = self.printer('info')
3508 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003509
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003510
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003511class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003512
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003513 def __init__(self, config, detach_head=False):
3514 self._messages = []
3515 self._failures = []
3516 self._later_queue1 = []
3517 self._later_queue2 = []
3518
3519 self.out = _SyncColoring(config)
3520 self.out.redirect(sys.stderr)
3521
3522 self.detach_head = detach_head
3523 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003524 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003525
3526 def info(self, project, fmt, *args):
3527 self._messages.append(_InfoMessage(project, fmt % args))
3528
3529 def fail(self, project, err=None):
3530 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003531 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003532
3533 def later1(self, project, what):
3534 self._later_queue1.append(_Later(project, what))
3535
3536 def later2(self, project, what):
3537 self._later_queue2.append(_Later(project, what))
3538
3539 def Finish(self):
3540 self._PrintMessages()
3541 self._RunLater()
3542 self._PrintMessages()
3543 return self.clean
3544
David Rileye0684ad2017-04-05 00:02:59 -07003545 def Recently(self):
3546 recent_clean = self.recent_clean
3547 self.recent_clean = True
3548 return recent_clean
3549
3550 def _MarkUnclean(self):
3551 self.clean = False
3552 self.recent_clean = False
3553
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003554 def _RunLater(self):
3555 for q in ['_later_queue1', '_later_queue2']:
3556 if not self._RunQueue(q):
3557 return
3558
3559 def _RunQueue(self, queue):
3560 for m in getattr(self, queue):
3561 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003562 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003563 return False
3564 setattr(self, queue, [])
3565 return True
3566
3567 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003568 if self._messages or self._failures:
3569 if os.isatty(2):
3570 self.out.write(progress.CSI_ERASE_LINE)
3571 self.out.write('\r')
3572
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003573 for m in self._messages:
3574 m.Print(self)
3575 for m in self._failures:
3576 m.Print(self)
3577
3578 self._messages = []
3579 self._failures = []
3580
3581
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003582class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003583
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003584 """A special project housed under .repo.
3585 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003586
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003587 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003588 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003589 manifest=manifest,
3590 name=name,
3591 gitdir=gitdir,
3592 objdir=gitdir,
3593 worktree=worktree,
3594 remote=RemoteSpec('origin'),
3595 relpath='.repo/%s' % name,
3596 revisionExpr='refs/heads/master',
3597 revisionId=None,
3598 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003599
3600 def PreSync(self):
3601 if self.Exists:
3602 cb = self.CurrentBranch
3603 if cb:
3604 base = self.GetBranch(cb).merge
3605 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003606 self.revisionExpr = base
3607 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003608
Martin Kelly224a31a2017-07-10 14:46:25 -07003609 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003610 """ Prepare MetaProject for manifest branch switch
3611 """
3612
3613 # detach and delete manifest branch, allowing a new
3614 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003615 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003616 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003617 syncbuf.Finish()
3618
3619 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003620 ['update-ref', '-d', 'refs/heads/default'],
3621 capture_stdout=True,
3622 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003623
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003624 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003625 def LastFetch(self):
3626 try:
3627 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3628 return os.path.getmtime(fh)
3629 except OSError:
3630 return 0
3631
3632 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003633 def HasChanges(self):
3634 """Has the remote received new commits not yet checked out?
3635 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003636 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003637 return False
3638
David Pursehouse8a68ff92012-09-24 12:15:13 +09003639 all_refs = self.bare_ref.all
3640 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003641 head = self.work_git.GetHead()
3642 if head.startswith(R_HEADS):
3643 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003644 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003645 except KeyError:
3646 head = None
3647
3648 if revid == head:
3649 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003650 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003651 return True
3652 return False