blob: fdbf9e4a66ba34346ce1b59a33fa6b393e108ddd [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
Zac Livingstone4332262017-06-16 08:56:09 -06002314 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002315 try:
2316 # if revision (sha or tag) is not present then following function
2317 # throws an error.
2318 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2319 return True
2320 except GitError:
2321 # There is no such persistent revision. We have to fetch it.
2322 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002323
Julien Campergue335f5ef2013-10-16 11:02:35 +02002324 def _FetchArchive(self, tarpath, cwd=None):
2325 cmd = ['archive', '-v', '-o', tarpath]
2326 cmd.append('--remote=%s' % self.remote.url)
2327 cmd.append('--prefix=%s/' % self.relpath)
2328 cmd.append(self.revisionExpr)
2329
2330 command = GitCommand(self, cmd, cwd=cwd,
2331 capture_stdout=True,
2332 capture_stderr=True)
2333
2334 if command.Wait() != 0:
2335 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2336
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002337 def _RemoteFetch(self, name=None,
2338 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002339 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002340 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002341 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002342 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002343 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002344 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002345 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002346 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002347 force_sync=False,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002348 clone_filter=None,
2349 retry_fetches=2,
2350 retry_sleep_initial_sec=4.0,
2351 retry_exp_factor=2.0):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002352 is_sha1 = False
2353 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002354 # The depth should not be used when fetching to a mirror because
2355 # it will result in a shallow repository that cannot be cloned or
2356 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002357 # The repo project should also never be synced with partial depth.
2358 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2359 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002360
Shawn Pearce69e04d82014-01-29 12:48:54 -08002361 if depth:
2362 current_branch_only = True
2363
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002364 if ID_RE.match(self.revisionExpr) is not None:
2365 is_sha1 = True
2366
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002367 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002368 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002369 # this is a tag and its sha1 value should never change
2370 tag_name = self.revisionExpr[len(R_TAGS):]
2371
2372 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002373 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002374 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002375 print('Skipped fetching project %s (already have persistent ref)'
2376 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002377 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002378 if is_sha1 and not depth:
2379 # When syncing a specific commit and --depth is not set:
2380 # * if upstream is explicitly specified and is not a sha1, fetch only
2381 # upstream as users expect only upstream to be fetch.
2382 # Note: The commit might not be in upstream in which case the sync
2383 # will fail.
2384 # * otherwise, fetch all branches to make sure we end up with the
2385 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002386 if self.upstream:
2387 current_branch_only = not ID_RE.match(self.upstream)
2388 else:
2389 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002390
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002391 if not name:
2392 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002393
2394 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002395 remote = self.GetRemote(name)
2396 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002397 ssh_proxy = True
2398
Shawn O. Pearce88443382010-10-08 10:02:09 +02002399 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002400 if alt_dir and 'objects' == os.path.basename(alt_dir):
2401 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002402 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2403 remote = self.GetRemote(name)
2404
David Pursehouse8a68ff92012-09-24 12:15:13 +09002405 all_refs = self.bare_ref.all
2406 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002407 tmp = set()
2408
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302409 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002410 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002411 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002412 all_refs[r] = ref_id
2413 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002414 continue
2415
David Pursehouse8a68ff92012-09-24 12:15:13 +09002416 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002417 continue
2418
David Pursehouse8a68ff92012-09-24 12:15:13 +09002419 r = 'refs/_alt/%s' % ref_id
2420 all_refs[r] = ref_id
2421 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002422 tmp.add(r)
2423
heping3d7bbc92017-04-12 19:51:47 +08002424 tmp_packed_lines = []
2425 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002426
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302427 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002428 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002429 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002430 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002431 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002432
heping3d7bbc92017-04-12 19:51:47 +08002433 tmp_packed = ''.join(tmp_packed_lines)
2434 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002435 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002436 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002437 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002438
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002439 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002440
Xin Li745be2e2019-06-03 11:24:30 -07002441 if clone_filter:
2442 git_require((2, 19, 0), fail=True, msg='partial clones')
2443 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002444 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002445
Conley Owensf97e8382015-01-21 11:12:46 -08002446 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002447 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002448 else:
2449 # If this repo has shallow objects, then we don't know which refs have
2450 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2451 # do this with projects that don't have shallow objects, since it is less
2452 # efficient.
2453 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2454 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002455
Mike Frysinger4847e052020-02-22 00:07:35 -05002456 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002457 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002458 if not quiet and sys.stdout.isatty():
2459 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002460 if not self.worktree:
2461 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002462 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002463
Mike Frysingere57f1142019-03-18 21:27:54 -04002464 if force_sync:
2465 cmd.append('--force')
2466
David Pursehouse74cfd272015-10-14 10:50:15 +09002467 if prune:
2468 cmd.append('--prune')
2469
Martin Kellye4e94d22017-03-21 16:05:12 -07002470 if submodules:
2471 cmd.append('--recurse-submodules=on-demand')
2472
Kuang-che Wu6856f982019-11-25 12:37:55 +08002473 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002474 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002475 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002476 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002477 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002478 spec.append('tag')
2479 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002480
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302481 if self.manifest.IsMirror and not current_branch_only:
2482 branch = None
2483 else:
2484 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002485 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002486 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002487 # Shallow checkout of a specific commit, fetch from that commit and not
2488 # the heads only as the commit might be deeper in the history.
2489 spec.append(branch)
2490 else:
2491 if is_sha1:
2492 branch = self.upstream
2493 if branch is not None and branch.strip():
2494 if not branch.startswith('refs/'):
2495 branch = R_HEADS + branch
2496 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2497
2498 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2499 # whole repo.
2500 if self.manifest.IsMirror and not spec:
2501 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2502
2503 # If using depth then we should not get all the tags since they may
2504 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002505 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002506 cmd.append('--no-tags')
2507 else:
2508 cmd.append('--tags')
2509 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2510
Conley Owens80b87fe2014-05-09 17:13:44 -07002511 cmd.extend(spec)
2512
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002513 # At least one retry minimum due to git remote prune.
2514 retry_fetches = max(retry_fetches, 2)
2515 retry_cur_sleep = retry_sleep_initial_sec
2516 ok = prune_tried = False
2517 for try_n in range(retry_fetches):
Mike Frysinger31990f02020-02-17 01:35:18 -05002518 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002519 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002520 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002521 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002522 ok = True
2523 break
George Engelbrecht9bc283e2020-04-02 12:36:09 -06002524
2525 # Retry later due to HTTP 429 Too Many Requests.
2526 elif ('error:' in gitcmd.stderr and
2527 'HTTP 429' in gitcmd.stderr):
2528 if not quiet:
2529 print('429 received, sleeping: %s sec' % retry_cur_sleep,
2530 file=sys.stderr)
2531 time.sleep(retry_cur_sleep)
2532 retry_cur_sleep = min(retry_exp_factor * retry_cur_sleep,
2533 MAXIMUM_RETRY_SLEEP_SEC)
2534 retry_cur_sleep *= (1 - random.uniform(-RETRY_JITTER_PERCENT,
2535 RETRY_JITTER_PERCENT))
2536 continue
2537
2538 # If this is not last attempt, try 'git remote prune'.
2539 elif (try_n < retry_fetches - 1 and
2540 'error:' in gitcmd.stderr and
2541 'git remote prune' in gitcmd.stderr and
2542 not prune_tried):
2543 prune_tried = True
John L. Villalovos126e2982015-01-29 21:58:12 -08002544 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002545 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002546 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002547 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002548 break
2549 continue
Brian Harring14a66742012-09-28 20:21:57 -07002550 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002551 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2552 # in sha1 mode, we just tried sync'ing from the upstream field; it
2553 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002554 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002555 elif ret < 0:
2556 # Git died with a signal, exit immediately
2557 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002558 if not verbose:
2559 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002560 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002561
2562 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002563 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002564 if old_packed != '':
2565 _lwrite(packed_refs, old_packed)
2566 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002567 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002568 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002569
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002570 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002571 # We just synced the upstream given branch; verify we
2572 # got what we wanted, else trigger a second run of all
2573 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002574 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002575 # Sync the current branch only with depth set to None.
2576 # We always pass depth=None down to avoid infinite recursion.
2577 return self._RemoteFetch(
2578 name=name, quiet=quiet, verbose=verbose,
2579 current_branch_only=current_branch_only and depth,
2580 initial=False, alt_dir=alt_dir,
2581 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002582
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002583 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002584
Mike Frysingere50b6a72020-02-19 01:45:48 -05002585 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002586 if initial and \
2587 (self.manifest.manifestProject.config.GetString('repo.depth') or
2588 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002589 return False
2590
2591 remote = self.GetRemote(self.remote.name)
2592 bundle_url = remote.url + '/clone.bundle'
2593 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002594 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2595 'persistent-http',
2596 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002597 return False
2598
2599 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2600 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2601
2602 exist_dst = os.path.exists(bundle_dst)
2603 exist_tmp = os.path.exists(bundle_tmp)
2604
2605 if not initial and not exist_dst and not exist_tmp:
2606 return False
2607
2608 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002609 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2610 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002611 if not exist_dst:
2612 return False
2613
2614 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002615 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002616 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002617 if not quiet and sys.stdout.isatty():
2618 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002619 if not self.worktree:
2620 cmd.append('--update-head-ok')
2621 cmd.append(bundle_dst)
2622 for f in remote.fetch:
2623 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002624 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002625
2626 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002627 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002628 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002629 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002630 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002631 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002632
Mike Frysingere50b6a72020-02-19 01:45:48 -05002633 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002634 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002635 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002636
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002637 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002638 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002639 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002640 if os.path.exists(tmpPath):
2641 size = os.stat(tmpPath).st_size
2642 if size >= 1024:
2643 cmd += ['--continue-at', '%d' % (size,)]
2644 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002645 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002646 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002647 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002648 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002649 if proxy:
2650 cmd += ['--proxy', proxy]
2651 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2652 cmd += ['--proxy', os.environ['http_proxy']]
2653 if srcUrl.startswith('persistent-https'):
2654 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2655 elif srcUrl.startswith('persistent-http'):
2656 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002657 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002658
Dave Borowitz137d0132015-01-02 11:12:54 -08002659 if IsTrace():
2660 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002661 if verbose:
2662 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2663 stdout = None if verbose else subprocess.PIPE
2664 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002665 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002666 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002667 except OSError:
2668 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002669
Mike Frysingere50b6a72020-02-19 01:45:48 -05002670 (output, _) = proc.communicate()
2671 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002672
Dave Borowitz137d0132015-01-02 11:12:54 -08002673 if curlret == 22:
2674 # From curl man page:
2675 # 22: HTTP page not retrieved. The requested url was not found or
2676 # returned another error with the HTTP error code being 400 or above.
2677 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002678 if verbose:
George Engelbrechtb6871892020-04-02 13:53:01 -06002679 print('%s: Unable to retrieve clone.bundle; ignoring.' % self.name)
2680 if output:
George Engelbrecht2fe84e12020-04-15 11:28:00 -06002681 print('Curl output:\n%s' % output)
Dave Borowitz137d0132015-01-02 11:12:54 -08002682 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002683 elif curlret and not verbose and output:
2684 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002685
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002686 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002687 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002688 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002689 return True
2690 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002691 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002692 return False
2693 else:
2694 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002695
Kris Giesingc8d882a2014-12-23 13:02:32 -08002696 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002697 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002698 with open(path, 'rb') as f:
2699 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002700 return True
2701 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002702 if not quiet:
2703 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002704 return False
2705 except OSError:
2706 return False
2707
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002708 def _Checkout(self, rev, quiet=False):
2709 cmd = ['checkout']
2710 if quiet:
2711 cmd.append('-q')
2712 cmd.append(rev)
2713 cmd.append('--')
2714 if GitCommand(self, cmd).Wait() != 0:
2715 if self._allrefs:
2716 raise GitError('%s checkout %s ' % (self.name, rev))
2717
Mike Frysinger915fda12020-03-22 12:15:20 -04002718 def _CherryPick(self, rev, ffonly=False, record_origin=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002719 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002720 if ffonly:
2721 cmd.append('--ff')
Mike Frysinger915fda12020-03-22 12:15:20 -04002722 if record_origin:
2723 cmd.append('-x')
Pierre Tardye5a21222011-03-24 16:28:18 +01002724 cmd.append(rev)
2725 cmd.append('--')
2726 if GitCommand(self, cmd).Wait() != 0:
2727 if self._allrefs:
2728 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2729
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302730 def _LsRemote(self, refs):
2731 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302732 p = GitCommand(self, cmd, capture_stdout=True)
2733 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002734 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302735 return None
2736
Anthony King7bdac712014-07-16 12:56:40 +01002737 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002738 cmd = ['revert']
2739 cmd.append('--no-edit')
2740 cmd.append(rev)
2741 cmd.append('--')
2742 if GitCommand(self, cmd).Wait() != 0:
2743 if self._allrefs:
2744 raise GitError('%s revert %s ' % (self.name, rev))
2745
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002746 def _ResetHard(self, rev, quiet=True):
2747 cmd = ['reset', '--hard']
2748 if quiet:
2749 cmd.append('-q')
2750 cmd.append(rev)
2751 if GitCommand(self, cmd).Wait() != 0:
2752 raise GitError('%s reset --hard %s ' % (self.name, rev))
2753
Martin Kellye4e94d22017-03-21 16:05:12 -07002754 def _SyncSubmodules(self, quiet=True):
2755 cmd = ['submodule', 'update', '--init', '--recursive']
2756 if quiet:
2757 cmd.append('-q')
2758 if GitCommand(self, cmd).Wait() != 0:
2759 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2760
Anthony King7bdac712014-07-16 12:56:40 +01002761 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002762 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002763 if onto is not None:
2764 cmd.extend(['--onto', onto])
2765 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002766 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002767 raise GitError('%s rebase %s ' % (self.name, upstream))
2768
Pierre Tardy3d125942012-05-04 12:18:12 +02002769 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002770 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002771 if ffonly:
2772 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002773 if GitCommand(self, cmd).Wait() != 0:
2774 raise GitError('%s merge %s ' % (self.name, head))
2775
David Pursehousee8ace262020-02-13 12:41:15 +09002776 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002777 init_git_dir = not os.path.exists(self.gitdir)
2778 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002779 try:
2780 # Initialize the bare repository, which contains all of the objects.
2781 if init_obj_dir:
2782 os.makedirs(self.objdir)
2783 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002784
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002785 if self.use_git_worktrees:
2786 # Set up the m/ space to point to the worktree-specific ref space.
2787 # We'll update the worktree-specific ref space on each checkout.
2788 if self.manifest.branch:
2789 self.bare_git.symbolic_ref(
2790 '-m', 'redirecting to worktree scope',
2791 R_M + self.manifest.branch,
2792 R_WORKTREE_M + self.manifest.branch)
2793
2794 # Enable per-worktree config file support if possible. This is more a
2795 # nice-to-have feature for users rather than a hard requirement.
Adrien Bioteau65f51ad2020-07-24 14:56:20 +02002796 if git_require((2, 20, 0)):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002797 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002798
Kevin Degib1a07b82015-07-27 13:33:43 -06002799 # If we have a separate directory to hold refs, initialize it as well.
2800 if self.objdir != self.gitdir:
2801 if init_git_dir:
2802 os.makedirs(self.gitdir)
2803
2804 if init_obj_dir or init_git_dir:
2805 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2806 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002807 try:
2808 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2809 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002810 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002811 print("Retrying clone after deleting %s" %
2812 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002813 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002814 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2815 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002816 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002817 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002818 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2819 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002820 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002821 raise e
2822 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002823
Kevin Degi384b3c52014-10-16 16:02:58 -06002824 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002825 mp = self.manifest.manifestProject
2826 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002827
Kevin Degib1a07b82015-07-27 13:33:43 -06002828 if ref_dir or mirror_git:
2829 if not mirror_git:
2830 mirror_git = os.path.join(ref_dir, self.name + '.git')
2831 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2832 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002833 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2834 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002835
Kevin Degib1a07b82015-07-27 13:33:43 -06002836 if os.path.exists(mirror_git):
2837 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002838 elif os.path.exists(repo_git):
2839 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002840 elif os.path.exists(worktrees_git):
2841 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002842 else:
2843 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002844
Kevin Degib1a07b82015-07-27 13:33:43 -06002845 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002846 if not os.path.isabs(ref_dir):
2847 # The alternate directory is relative to the object database.
2848 ref_dir = os.path.relpath(ref_dir,
2849 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002850 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2851 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002852
David Pursehousee8ace262020-02-13 12:41:15 +09002853 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002854
2855 m = self.manifest.manifestProject.config
2856 for key in ['user.name', 'user.email']:
2857 if m.Has(key, include_defaults=False):
2858 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002859 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002860 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002861 if self.manifest.IsMirror:
2862 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002863 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002864 self.config.SetString('core.bare', None)
2865 except Exception:
2866 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002867 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002868 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002869 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002870 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002871
David Pursehousee8ace262020-02-13 12:41:15 +09002872 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002873 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002874 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002875
David Pursehousee8ace262020-02-13 12:41:15 +09002876 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002877 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002878 if not os.path.exists(hooks):
2879 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002880 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002881 name = os.path.basename(stock_hook)
2882
Victor Boivie65e0f352011-04-18 11:23:29 +02002883 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002884 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002885 # Don't install a Gerrit Code Review hook if this
2886 # project does not appear to use it for reviews.
2887 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002888 # Since the manifest project is one of those, but also
2889 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002890 continue
2891
2892 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002893 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002894 continue
2895 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002896 # If the files are the same, we'll leave it alone. We create symlinks
2897 # below by default but fallback to hardlinks if the OS blocks them.
2898 # So if we're here, it's probably because we made a hardlink below.
2899 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002900 if not quiet:
2901 _warn("%s: Not replacing locally modified %s hook",
2902 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002903 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002904 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002905 platform_utils.symlink(
2906 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002907 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002908 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002909 try:
2910 os.link(stock_hook, dst)
2911 except OSError:
2912 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002913 else:
2914 raise
2915
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002916 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002917 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002918 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002919 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002920 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002921 remote.review = self.remote.review
2922 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002923
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002924 if self.worktree:
2925 remote.ResetFetch(mirror=False)
2926 else:
2927 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002928 remote.Save()
2929
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002930 def _InitMRef(self):
2931 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002932 if self.use_git_worktrees:
2933 # We can't update this ref with git worktrees until it exists.
2934 # We'll wait until the initial checkout to set it.
2935 if not os.path.exists(self.worktree):
2936 return
2937
2938 base = R_WORKTREE_M
2939 active_git = self.work_git
2940 else:
2941 base = R_M
2942 active_git = self.bare_git
2943
2944 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002945
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002946 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002947 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002948
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002949 def _InitAnyMRef(self, ref, active_git):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002950 cur = self.bare_ref.symref(ref)
2951
2952 if self.revisionId:
2953 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2954 msg = 'manifest set to %s' % self.revisionId
2955 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002956 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002957 else:
2958 remote = self.GetRemote(self.remote.name)
2959 dst = remote.ToLocal(self.revisionExpr)
2960 if cur != dst:
2961 msg = 'manifest set to %s' % self.revisionExpr
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002962 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002963
Kevin Degi384b3c52014-10-16 16:02:58 -06002964 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002965 # Git worktrees don't use symlinks to share at all.
2966 if self.use_git_worktrees:
2967 return
2968
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002969 symlink_files = self.shareable_files[:]
2970 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002971 if share_refs:
2972 symlink_files += self.working_tree_files
2973 symlink_dirs += self.working_tree_dirs
2974 to_symlink = symlink_files + symlink_dirs
2975 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002976 # Try to self-heal a bit in simple cases.
2977 dst_path = os.path.join(destdir, name)
2978 src_path = os.path.join(srcdir, name)
2979
2980 if name in self.working_tree_dirs:
2981 # If the dir is missing under .repo/projects/, create it.
2982 if not os.path.exists(src_path):
2983 os.makedirs(src_path)
2984
2985 elif name in self.working_tree_files:
2986 # If it's a file under the checkout .git/ and the .repo/projects/ has
2987 # nothing, move the file under the .repo/projects/ tree.
2988 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2989 platform_utils.rename(dst_path, src_path)
2990
2991 # If the path exists under the .repo/projects/ and there's no symlink
2992 # under the checkout .git/, recreate the symlink.
2993 if name in self.working_tree_dirs or name in self.working_tree_files:
2994 if os.path.exists(src_path) and not os.path.exists(dst_path):
2995 platform_utils.symlink(
2996 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2997
2998 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002999 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05003000 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06003001 # Fail if the links are pointing to the wrong place
3002 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07003003 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07003004 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04003005 'work tree. If you\'re comfortable with the '
3006 'possibility of losing the work tree\'s git metadata,'
3007 ' use `repo sync --force-sync {0}` to '
3008 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06003009
David James8d201162013-10-11 17:03:19 -07003010 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
3011 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
3012
3013 Args:
3014 gitdir: The bare git repository. Must already be initialized.
3015 dotgit: The repository you would like to initialize.
3016 share_refs: If true, |dotgit| will store its refs under |gitdir|.
3017 Only one work tree can store refs under a given |gitdir|.
3018 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
3019 This saves you the effort of initializing |dotgit| yourself.
3020 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07003021 symlink_files = self.shareable_files[:]
3022 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07003023 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06003024 symlink_files += self.working_tree_files
3025 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07003026 to_symlink = symlink_files + symlink_dirs
3027
3028 to_copy = []
3029 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07003030 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07003031
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003032 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07003033 for name in set(to_copy).union(to_symlink):
3034 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003035 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07003036 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07003037
Kevin Degi384b3c52014-10-16 16:02:58 -06003038 if os.path.lexists(dst):
3039 continue
David James8d201162013-10-11 17:03:19 -07003040
3041 # If the source dir doesn't exist, create an empty dir.
3042 if name in symlink_dirs and not os.path.lexists(src):
3043 os.makedirs(src)
3044
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003045 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07003046 platform_utils.symlink(
3047 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003048 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07003049 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003050 shutil.copytree(src, dst)
3051 elif os.path.isfile(src):
3052 shutil.copy(src, dst)
3053
Conley Owens80b87fe2014-05-09 17:13:44 -07003054 # If the source file doesn't exist, ensure the destination
3055 # file doesn't either.
3056 if name in symlink_files and not os.path.lexists(src):
3057 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003058 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003059 except OSError:
3060 pass
3061
David James8d201162013-10-11 17:03:19 -07003062 except OSError as e:
3063 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003064 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003065 else:
3066 raise
3067
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003068 def _InitGitWorktree(self):
3069 """Init the project using git worktrees."""
3070 self.bare_git.worktree('prune')
3071 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3072 self.worktree, self.GetRevisionId())
3073
3074 # Rewrite the internal state files to use relative paths between the
3075 # checkouts & worktrees.
3076 dotgit = os.path.join(self.worktree, '.git')
3077 with open(dotgit, 'r') as fp:
3078 # Figure out the checkout->worktree path.
3079 setting = fp.read()
3080 assert setting.startswith('gitdir:')
3081 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05003082 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
3083 # of file permissions. Delete it and recreate it from scratch to avoid.
3084 platform_utils.remove(dotgit)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003085 # Use relative path from checkout->worktree.
3086 with open(dotgit, 'w') as fp:
3087 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3088 file=fp)
3089 # Use relative path from worktree->checkout.
3090 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3091 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3092
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05003093 self._InitMRef()
3094
Martin Kellye4e94d22017-03-21 16:05:12 -07003095 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003096 realdotgit = os.path.join(self.worktree, '.git')
3097 tmpdotgit = realdotgit + '.tmp'
3098 init_dotgit = not os.path.exists(realdotgit)
3099 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003100 if self.use_git_worktrees:
3101 self._InitGitWorktree()
3102 self._CopyAndLinkFiles()
3103 return
3104
Mike Frysingerf4545122019-11-11 04:34:16 -05003105 dotgit = tmpdotgit
3106 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3107 os.makedirs(tmpdotgit)
3108 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3109 copy_all=False)
3110 else:
3111 dotgit = realdotgit
3112
Kevin Degib1a07b82015-07-27 13:33:43 -06003113 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003114 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3115 except GitError as e:
3116 if force_sync and not init_dotgit:
3117 try:
3118 platform_utils.rmtree(dotgit)
3119 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003120 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003121 raise e
3122 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003123
Mike Frysingerf4545122019-11-11 04:34:16 -05003124 if init_dotgit:
3125 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003126
Mike Frysingerf4545122019-11-11 04:34:16 -05003127 # Now that the .git dir is fully set up, move it to its final home.
3128 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003129
Mike Frysingerf4545122019-11-11 04:34:16 -05003130 # Finish checking out the worktree.
3131 cmd = ['read-tree', '--reset', '-u']
3132 cmd.append('-v')
3133 cmd.append(HEAD)
3134 if GitCommand(self, cmd).Wait() != 0:
3135 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003136
Mike Frysingerf4545122019-11-11 04:34:16 -05003137 if submodules:
3138 self._SyncSubmodules(quiet=True)
3139 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003140
Renaud Paquay788e9622017-01-27 11:41:12 -08003141 def _get_symlink_error_message(self):
3142 if platform_utils.isWindows():
3143 return ('Unable to create symbolic link. Please re-run the command as '
3144 'Administrator, or see '
3145 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3146 'for other options.')
3147 return 'filesystem must support symlinks'
3148
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003149 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003150 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003151
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003152 def _revlist(self, *args, **kw):
3153 a = []
3154 a.extend(args)
3155 a.append('--')
3156 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003157
3158 @property
3159 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003160 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003161
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003162 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003163 """Get logs between two revisions of this project."""
3164 comp = '..'
3165 if rev1:
3166 revs = [rev1]
3167 if rev2:
3168 revs.extend([comp, rev2])
3169 cmd = ['log', ''.join(revs)]
3170 out = DiffColoring(self.config)
3171 if out.is_on and color:
3172 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003173 if pretty_format is not None:
3174 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003175 if oneline:
3176 cmd.append('--oneline')
3177
3178 try:
3179 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3180 if log.Wait() == 0:
3181 return log.stdout
3182 except GitError:
3183 # worktree may not exist if groups changed for example. In that case,
3184 # try in gitdir instead.
3185 if not os.path.exists(self.worktree):
3186 return self.bare_git.log(*cmd[1:])
3187 else:
3188 raise
3189 return None
3190
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003191 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3192 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003193 """Get the list of logs from this revision to given revisionId"""
3194 logs = {}
3195 selfId = self.GetRevisionId(self._allrefs)
3196 toId = toProject.GetRevisionId(toProject._allrefs)
3197
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003198 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3199 pretty_format=pretty_format)
3200 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3201 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003202 return logs
3203
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003204 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003205
David James8d201162013-10-11 17:03:19 -07003206 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003207 self._project = project
3208 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003209 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003210
Kimiyuki Onaka0501b292020-08-28 10:05:27 +09003211 # __getstate__ and __setstate__ are required for pickling because __getattr__ exists.
3212 def __getstate__(self):
3213 return (self._project, self._bare, self._gitdir)
3214
3215 def __setstate__(self, state):
3216 self._project, self._bare, self._gitdir = state
3217
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003218 def LsOthers(self):
3219 p = GitCommand(self._project,
3220 ['ls-files',
3221 '-z',
3222 '--others',
3223 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003224 bare=False,
David James8d201162013-10-11 17:03:19 -07003225 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003226 capture_stdout=True,
3227 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003228 if p.Wait() == 0:
3229 out = p.stdout
3230 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003231 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003232 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003233 return []
3234
3235 def DiffZ(self, name, *args):
3236 cmd = [name]
3237 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003238 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003239 cmd.extend(args)
3240 p = GitCommand(self._project,
3241 cmd,
David James8d201162013-10-11 17:03:19 -07003242 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003243 bare=False,
3244 capture_stdout=True,
3245 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003246 try:
3247 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003248 if not hasattr(out, 'encode'):
3249 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003250 r = {}
3251 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003252 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003253 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003254 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003255 info = next(out)
3256 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003257 except StopIteration:
3258 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003259
3260 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003261
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003262 def __init__(self, path, omode, nmode, oid, nid, state):
3263 self.path = path
3264 self.src_path = None
3265 self.old_mode = omode
3266 self.new_mode = nmode
3267 self.old_id = oid
3268 self.new_id = nid
3269
3270 if len(state) == 1:
3271 self.status = state
3272 self.level = None
3273 else:
3274 self.status = state[:1]
3275 self.level = state[1:]
3276 while self.level.startswith('0'):
3277 self.level = self.level[1:]
3278
3279 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003280 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003281 if info.status in ('R', 'C'):
3282 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003283 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003284 r[info.path] = info
3285 return r
3286 finally:
3287 p.Wait()
3288
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003289 def GetDotgitPath(self, subpath=None):
3290 """Return the full path to the .git dir.
3291
3292 As a convenience, append |subpath| if provided.
3293 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003294 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003295 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003296 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003297 dotgit = os.path.join(self._project.worktree, '.git')
3298 if os.path.isfile(dotgit):
3299 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3300 with open(dotgit) as fp:
3301 setting = fp.read()
3302 assert setting.startswith('gitdir:')
3303 gitdir = setting.split(':', 1)[1].strip()
3304 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3305
3306 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3307
3308 def GetHead(self):
3309 """Return the ref that HEAD points to."""
3310 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003311 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003312 with open(path) as fd:
3313 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003314 except IOError as e:
3315 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003316 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303317 line = line.decode()
3318 except AttributeError:
3319 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003320 if line.startswith('ref: '):
3321 return line[5:-1]
3322 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003323
3324 def SetHead(self, ref, message=None):
3325 cmdv = []
3326 if message is not None:
3327 cmdv.extend(['-m', message])
3328 cmdv.append(HEAD)
3329 cmdv.append(ref)
3330 self.symbolic_ref(*cmdv)
3331
3332 def DetachHead(self, new, message=None):
3333 cmdv = ['--no-deref']
3334 if message is not None:
3335 cmdv.extend(['-m', message])
3336 cmdv.append(HEAD)
3337 cmdv.append(new)
3338 self.update_ref(*cmdv)
3339
3340 def UpdateRef(self, name, new, old=None,
3341 message=None,
3342 detach=False):
3343 cmdv = []
3344 if message is not None:
3345 cmdv.extend(['-m', message])
3346 if detach:
3347 cmdv.append('--no-deref')
3348 cmdv.append(name)
3349 cmdv.append(new)
3350 if old is not None:
3351 cmdv.append(old)
3352 self.update_ref(*cmdv)
3353
3354 def DeleteRef(self, name, old=None):
3355 if not old:
3356 old = self.rev_parse(name)
3357 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003358 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003359
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003360 def rev_list(self, *args, **kw):
3361 if 'format' in kw:
3362 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3363 else:
3364 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003365 cmdv.extend(args)
3366 p = GitCommand(self._project,
3367 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003368 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003369 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003370 capture_stdout=True,
3371 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003372 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003373 raise GitError('%s rev-list %s: %s' %
3374 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003375 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003376
3377 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003378 """Allow arbitrary git commands using pythonic syntax.
3379
3380 This allows you to do things like:
3381 git_obj.rev_parse('HEAD')
3382
3383 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3384 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003385 Any other positional arguments will be passed to the git command, and the
3386 following keyword arguments are supported:
3387 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003388
3389 Args:
3390 name: The name of the git command to call. Any '_' characters will
3391 be replaced with '-'.
3392
3393 Returns:
3394 A callable object that will try to call git with the named command.
3395 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003396 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003397
Dave Borowitz091f8932012-10-23 17:01:04 -07003398 def runner(*args, **kwargs):
3399 cmdv = []
3400 config = kwargs.pop('config', None)
3401 for k in kwargs:
3402 raise TypeError('%s() got an unexpected keyword argument %r'
3403 % (name, k))
3404 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303405 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003406 cmdv.append('-c')
3407 cmdv.append('%s=%s' % (k, v))
3408 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003409 cmdv.extend(args)
3410 p = GitCommand(self._project,
3411 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003412 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003413 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003414 capture_stdout=True,
3415 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003416 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003417 raise GitError('%s %s: %s' %
3418 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003419 r = p.stdout
3420 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3421 return r[:-1]
3422 return r
3423 return runner
3424
3425
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003426class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003427
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003428 def __str__(self):
3429 return 'prior sync failed; rebase still in progress'
3430
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003431
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003432class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003433
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003434 def __str__(self):
3435 return 'contains uncommitted changes'
3436
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003437
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003438class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003439
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003440 def __init__(self, project, text):
3441 self.project = project
3442 self.text = text
3443
3444 def Print(self, syncbuf):
3445 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3446 syncbuf.out.nl()
3447
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003448
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003449class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003450
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003451 def __init__(self, project, why):
3452 self.project = project
3453 self.why = why
3454
3455 def Print(self, syncbuf):
3456 syncbuf.out.fail('error: %s/: %s',
3457 self.project.relpath,
3458 str(self.why))
3459 syncbuf.out.nl()
3460
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003461
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003462class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003463
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003464 def __init__(self, project, action):
3465 self.project = project
3466 self.action = action
3467
3468 def Run(self, syncbuf):
3469 out = syncbuf.out
3470 out.project('project %s/', self.project.relpath)
3471 out.nl()
3472 try:
3473 self.action()
3474 out.nl()
3475 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003476 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003477 out.nl()
3478 return False
3479
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003480
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003481class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003482
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003483 def __init__(self, config):
3484 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003485 self.project = self.printer('header', attr='bold')
3486 self.info = self.printer('info')
3487 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003488
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003489
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003490class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003491
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003492 def __init__(self, config, detach_head=False):
3493 self._messages = []
3494 self._failures = []
3495 self._later_queue1 = []
3496 self._later_queue2 = []
3497
3498 self.out = _SyncColoring(config)
3499 self.out.redirect(sys.stderr)
3500
3501 self.detach_head = detach_head
3502 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003503 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003504
3505 def info(self, project, fmt, *args):
3506 self._messages.append(_InfoMessage(project, fmt % args))
3507
3508 def fail(self, project, err=None):
3509 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003510 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003511
3512 def later1(self, project, what):
3513 self._later_queue1.append(_Later(project, what))
3514
3515 def later2(self, project, what):
3516 self._later_queue2.append(_Later(project, what))
3517
3518 def Finish(self):
3519 self._PrintMessages()
3520 self._RunLater()
3521 self._PrintMessages()
3522 return self.clean
3523
David Rileye0684ad2017-04-05 00:02:59 -07003524 def Recently(self):
3525 recent_clean = self.recent_clean
3526 self.recent_clean = True
3527 return recent_clean
3528
3529 def _MarkUnclean(self):
3530 self.clean = False
3531 self.recent_clean = False
3532
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003533 def _RunLater(self):
3534 for q in ['_later_queue1', '_later_queue2']:
3535 if not self._RunQueue(q):
3536 return
3537
3538 def _RunQueue(self, queue):
3539 for m in getattr(self, queue):
3540 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003541 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003542 return False
3543 setattr(self, queue, [])
3544 return True
3545
3546 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003547 if self._messages or self._failures:
3548 if os.isatty(2):
3549 self.out.write(progress.CSI_ERASE_LINE)
3550 self.out.write('\r')
3551
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003552 for m in self._messages:
3553 m.Print(self)
3554 for m in self._failures:
3555 m.Print(self)
3556
3557 self._messages = []
3558 self._failures = []
3559
3560
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003561class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003562
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003563 """A special project housed under .repo.
3564 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003565
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003566 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003567 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003568 manifest=manifest,
3569 name=name,
3570 gitdir=gitdir,
3571 objdir=gitdir,
3572 worktree=worktree,
3573 remote=RemoteSpec('origin'),
3574 relpath='.repo/%s' % name,
3575 revisionExpr='refs/heads/master',
3576 revisionId=None,
3577 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003578
3579 def PreSync(self):
3580 if self.Exists:
3581 cb = self.CurrentBranch
3582 if cb:
3583 base = self.GetBranch(cb).merge
3584 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003585 self.revisionExpr = base
3586 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003587
Martin Kelly224a31a2017-07-10 14:46:25 -07003588 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003589 """ Prepare MetaProject for manifest branch switch
3590 """
3591
3592 # detach and delete manifest branch, allowing a new
3593 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003594 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003595 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003596 syncbuf.Finish()
3597
3598 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003599 ['update-ref', '-d', 'refs/heads/default'],
3600 capture_stdout=True,
3601 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003602
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003603 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003604 def LastFetch(self):
3605 try:
3606 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3607 return os.path.getmtime(fh)
3608 except OSError:
3609 return 0
3610
3611 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003612 def HasChanges(self):
3613 """Has the remote received new commits not yet checked out?
3614 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003615 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003616 return False
3617
David Pursehouse8a68ff92012-09-24 12:15:13 +09003618 all_refs = self.bare_ref.all
3619 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003620 head = self.work_git.GetHead()
3621 if head.startswith(R_HEADS):
3622 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003623 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003624 except KeyError:
3625 head = None
3626
3627 if revid == head:
3628 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003629 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003630 return True
3631 return False