blob: 43ddcd9ab914ff4f4eb7b08063920c1a3caea1ba [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, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070037 ID_RE, RefSpec
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070039from error import CacheApplyError
Mike Frysingere6a202f2019-08-02 15:57:57 -040040from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080041from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070042import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040043import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040044from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070045
Shawn O. Pearced237b692009-04-17 18:49:50 -070046from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070047
David Pursehouse59bbb582013-05-17 10:49:33 +090048from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040049if is_python3():
50 import urllib.parse
51else:
52 import imp
53 import urlparse
54 urllib = imp.new_module('urllib')
55 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090056 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053057
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070058
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059def _lwrite(path, content):
60 lock = '%s.lock' % path
61
Mike Frysinger3164d402019-11-11 05:40:22 -050062 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070064
65 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070066 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070067 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080068 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070069 raise
70
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070071
Shawn O. Pearce48244782009-04-16 08:25:57 -070072def _error(fmt, *args):
73 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070074 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070075
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
David Pursehousef33929d2015-08-24 14:39:14 +090077def _warn(fmt, *args):
78 msg = fmt % args
79 print('warn: %s' % msg, file=sys.stderr)
80
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082def not_rev(r):
83 return '^' + r
84
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070085
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080086def sq(r):
87 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
David Pursehouse819827a2020-02-12 15:20:19 +090089
Jonathan Nieder93719792015-03-17 11:29:58 -070090_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070091
92
Jonathan Nieder93719792015-03-17 11:29:58 -070093def _ProjectHooks():
94 """List the hooks present in the 'hooks' directory.
95
96 These hooks are project hooks and are copied to the '.git/hooks' directory
97 of all subprojects.
98
99 This function caches the list of hooks (based on the contents of the
100 'repo/hooks' directory) on the first call.
101
102 Returns:
103 A list of absolute paths to all of the files in the hooks directory.
104 """
105 global _project_hook_list
106 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700107 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700108 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700109 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700110 return _project_hook_list
111
112
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700113class DownloadedChange(object):
114 _commit_cache = None
115
116 def __init__(self, project, base, change_id, ps_id, commit):
117 self.project = project
118 self.base = base
119 self.change_id = change_id
120 self.ps_id = ps_id
121 self.commit = commit
122
123 @property
124 def commits(self):
125 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700126 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
127 '--abbrev-commit',
128 '--pretty=oneline',
129 '--reverse',
130 '--date-order',
131 not_rev(self.base),
132 self.commit,
133 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700134 return self._commit_cache
135
136
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137class ReviewableBranch(object):
138 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400139 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140
141 def __init__(self, project, branch, base):
142 self.project = project
143 self.branch = branch
144 self.base = base
145
146 @property
147 def name(self):
148 return self.branch.name
149
150 @property
151 def commits(self):
152 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400153 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
154 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
155 try:
156 self._commit_cache = self.project.bare_git.rev_list(*args)
157 except GitError:
158 # We weren't able to probe the commits for this branch. Was it tracking
159 # a branch that no longer exists? If so, return no commits. Otherwise,
160 # rethrow the error as we don't know what's going on.
161 if self.base_exists:
162 raise
163
164 self._commit_cache = []
165
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166 return self._commit_cache
167
168 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800169 def unabbrev_commits(self):
170 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 for commit in self.project.bare_git.rev_list(not_rev(self.base),
172 R_HEADS + self.name,
173 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800174 r[commit[0:8]] = commit
175 return r
176
177 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700179 return self.project.bare_git.log('--pretty=format:%cd',
180 '-n', '1',
181 R_HEADS + self.name,
182 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
Mike Frysinger6da17752019-09-11 18:43:17 -0400184 @property
185 def base_exists(self):
186 """Whether the branch we're tracking exists.
187
188 Normally it should, but sometimes branches we track can get deleted.
189 """
190 if self._base_exists is None:
191 try:
192 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
193 # If we're still here, the base branch exists.
194 self._base_exists = True
195 except GitError:
196 # If we failed to verify, the base branch doesn't exist.
197 self._base_exists = False
198
199 return self._base_exists
200
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700201 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500202 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700203 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500204 hashtags=(),
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700205 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200206 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700207 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200208 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200209 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800210 validate_certs=True,
211 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500212 self.project.UploadForReview(branch=self.name,
213 people=people,
214 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700215 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500216 hashtags=hashtags,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400217 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200218 private=private,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700219 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200220 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200221 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800222 validate_certs=validate_certs,
223 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700225 def GetPublishedRefs(self):
226 refs = {}
227 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700228 self.branch.remote.SshReviewUrl(self.project.UserEmail),
229 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700230 for line in output.split('\n'):
231 try:
232 (sha, ref) = line.split()
233 refs[sha] = ref
234 except ValueError:
235 pass
236
237 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700239
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700241
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 def __init__(self, config):
243 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100244 self.project = self.printer('header', attr='bold')
245 self.branch = self.printer('header', attr='bold')
246 self.nobranch = self.printer('nobranch', fg='red')
247 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248
Anthony King7bdac712014-07-16 12:56:40 +0100249 self.added = self.printer('added', fg='green')
250 self.changed = self.printer('changed', fg='red')
251 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252
253
254class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256 def __init__(self, config):
257 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100258 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400259 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700261
Anthony King7bdac712014-07-16 12:56:40 +0100262class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700263
James W. Mills24c13082012-04-12 15:04:13 -0500264 def __init__(self, name, value, keep):
265 self.name = name
266 self.value = value
267 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700268
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700269
Mike Frysingere6a202f2019-08-02 15:57:57 -0400270def _SafeExpandPath(base, subpath, skipfinal=False):
271 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700272
Mike Frysingere6a202f2019-08-02 15:57:57 -0400273 We make sure no intermediate symlinks are traversed, and that the final path
274 is not a special file (e.g. not a socket or fifo).
275
276 NB: We rely on a number of paths already being filtered out while parsing the
277 manifest. See the validation logic in manifest_xml.py for more details.
278 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500279 # Split up the path by its components. We can't use os.path.sep exclusively
280 # as some platforms (like Windows) will convert / to \ and that bypasses all
281 # our constructed logic here. Especially since manifest authors only use
282 # / in their paths.
283 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
284 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400285 if skipfinal:
286 # Whether the caller handles the final component itself.
287 finalpart = components.pop()
288
289 path = base
290 for part in components:
291 if part in {'.', '..'}:
292 raise ManifestInvalidPathError(
293 '%s: "%s" not allowed in paths' % (subpath, part))
294
295 path = os.path.join(path, part)
296 if platform_utils.islink(path):
297 raise ManifestInvalidPathError(
298 '%s: traversing symlinks not allow' % (path,))
299
300 if os.path.exists(path):
301 if not os.path.isfile(path) and not platform_utils.isdir(path):
302 raise ManifestInvalidPathError(
303 '%s: only regular files & directories allowed' % (path,))
304
305 if skipfinal:
306 path = os.path.join(path, finalpart)
307
308 return path
309
310
311class _CopyFile(object):
312 """Container for <copyfile> manifest element."""
313
314 def __init__(self, git_worktree, src, topdir, dest):
315 """Register a <copyfile> request.
316
317 Args:
318 git_worktree: Absolute path to the git project checkout.
319 src: Relative path under |git_worktree| of file to read.
320 topdir: Absolute path to the top of the repo client checkout.
321 dest: Relative path under |topdir| of file to write.
322 """
323 self.git_worktree = git_worktree
324 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700325 self.src = src
326 self.dest = dest
327
328 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400329 src = _SafeExpandPath(self.git_worktree, self.src)
330 dest = _SafeExpandPath(self.topdir, self.dest)
331
332 if platform_utils.isdir(src):
333 raise ManifestInvalidPathError(
334 '%s: copying from directory not supported' % (self.src,))
335 if platform_utils.isdir(dest):
336 raise ManifestInvalidPathError(
337 '%s: copying to directory not allowed' % (self.dest,))
338
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339 # copy file if it does not exist or is out of date
340 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
341 try:
342 # remove existing file first, since it might be read-only
343 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800344 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400345 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200346 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700347 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200348 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700349 shutil.copy(src, dest)
350 # make the file read-only
351 mode = os.stat(dest)[stat.ST_MODE]
352 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
353 os.chmod(dest, mode)
354 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700355 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700356
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700357
Anthony King7bdac712014-07-16 12:56:40 +0100358class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400359 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700360
Mike Frysingere6a202f2019-08-02 15:57:57 -0400361 def __init__(self, git_worktree, src, topdir, dest):
362 """Register a <linkfile> request.
363
364 Args:
365 git_worktree: Absolute path to the git project checkout.
366 src: Target of symlink relative to path under |git_worktree|.
367 topdir: Absolute path to the top of the repo client checkout.
368 dest: Relative path under |topdir| of symlink to create.
369 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700370 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400371 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500372 self.src = src
373 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500374
Wink Saville4c426ef2015-06-03 08:05:17 -0700375 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500376 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700377 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500378 try:
379 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800380 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800381 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500382 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700383 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700384 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500385 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700386 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500387 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700388 _error('Cannot link file %s to %s', relSrc, absDest)
389
390 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400391 """Link the self.src & self.dest paths.
392
393 Handles wild cards on the src linking all of the files in the source in to
394 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700395 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500396 # Some people use src="." to create stable links to projects. Lets allow
397 # that but reject all other uses of "." to keep things simple.
398 if self.src == '.':
399 src = self.git_worktree
400 else:
401 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400402
403 if os.path.exists(src):
404 # Entity exists so just a simple one to one link operation.
405 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
406 # dest & src are absolute paths at this point. Make sure the target of
407 # the symlink is relative in the context of the repo client checkout.
408 relpath = os.path.relpath(src, os.path.dirname(dest))
409 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700410 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400411 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700412 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400413 if os.path.exists(dest) and not platform_utils.isdir(dest):
414 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700415 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400416 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700417 # Create a releative path from source dir to destination dir
418 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400419 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700420
421 # Get the source file name
422 srcFile = os.path.basename(absSrcFile)
423
424 # Now form the final full paths to srcFile. They will be
425 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400426 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700427 relSrc = os.path.join(relSrcDir, srcFile)
428 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500429
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700430
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700431class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700432
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700433 def __init__(self,
434 name,
Anthony King7bdac712014-07-16 12:56:40 +0100435 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700436 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100437 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700438 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700439 orig_name=None,
440 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700441 self.name = name
442 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700443 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700444 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100445 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700446 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700447 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700448
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700449
Doug Anderson37282b42011-03-04 11:54:18 -0800450class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700451
Doug Anderson37282b42011-03-04 11:54:18 -0800452 """A RepoHook contains information about a script to run as a hook.
453
454 Hooks are used to run a python script before running an upload (for instance,
455 to run presubmit checks). Eventually, we may have hooks for other actions.
456
457 This shouldn't be confused with files in the 'repo/hooks' directory. Those
458 files are copied into each '.git/hooks' folder for each project. Repo-level
459 hooks are associated instead with repo actions.
460
461 Hooks are always python. When a hook is run, we will load the hook into the
462 interpreter and execute its main() function.
463 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700464
Doug Anderson37282b42011-03-04 11:54:18 -0800465 def __init__(self,
466 hook_type,
467 hooks_project,
468 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400469 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800470 abort_if_user_denies=False):
471 """RepoHook constructor.
472
473 Params:
474 hook_type: A string representing the type of hook. This is also used
475 to figure out the name of the file containing the hook. For
476 example: 'pre-upload'.
477 hooks_project: The project containing the repo hooks. If you have a
478 manifest, this is manifest.repo_hooks_project. OK if this is None,
479 which will make the hook a no-op.
480 topdir: Repo's top directory (the one containing the .repo directory).
481 Scripts will run with CWD as this directory. If you have a manifest,
482 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400483 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800484 abort_if_user_denies: If True, we'll throw a HookError() if the user
485 doesn't allow us to run the hook.
486 """
487 self._hook_type = hook_type
488 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400489 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800490 self._topdir = topdir
491 self._abort_if_user_denies = abort_if_user_denies
492
493 # Store the full path to the script for convenience.
494 if self._hooks_project:
495 self._script_fullpath = os.path.join(self._hooks_project.worktree,
496 self._hook_type + '.py')
497 else:
498 self._script_fullpath = None
499
500 def _GetHash(self):
501 """Return a hash of the contents of the hooks directory.
502
503 We'll just use git to do this. This hash has the property that if anything
504 changes in the directory we will return a different has.
505
506 SECURITY CONSIDERATION:
507 This hash only represents the contents of files in the hook directory, not
508 any other files imported or called by hooks. Changes to imported files
509 can change the script behavior without affecting the hash.
510
511 Returns:
512 A string representing the hash. This will always be ASCII so that it can
513 be printed to the user easily.
514 """
515 assert self._hooks_project, "Must have hooks to calculate their hash."
516
517 # We will use the work_git object rather than just calling GetRevisionId().
518 # That gives us a hash of the latest checked in version of the files that
519 # the user will actually be executing. Specifically, GetRevisionId()
520 # doesn't appear to change even if a user checks out a different version
521 # of the hooks repo (via git checkout) nor if a user commits their own revs.
522 #
523 # NOTE: Local (non-committed) changes will not be factored into this hash.
524 # I think this is OK, since we're really only worried about warning the user
525 # about upstream changes.
526 return self._hooks_project.work_git.rev_parse('HEAD')
527
528 def _GetMustVerb(self):
529 """Return 'must' if the hook is required; 'should' if not."""
530 if self._abort_if_user_denies:
531 return 'must'
532 else:
533 return 'should'
534
535 def _CheckForHookApproval(self):
536 """Check to see whether this hook has been approved.
537
Mike Frysinger40252c22016-08-15 21:23:44 -0400538 We'll accept approval of manifest URLs if they're using secure transports.
539 This way the user can say they trust the manifest hoster. For insecure
540 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800541
542 Note that we ask permission for each individual hook even though we use
543 the hash of all hooks when detecting changes. We'd like the user to be
544 able to approve / deny each hook individually. We only use the hash of all
545 hooks because there is no other easy way to detect changes to local imports.
546
547 Returns:
548 True if this hook is approved to run; False otherwise.
549
550 Raises:
551 HookError: Raised if the user doesn't approve and abort_if_user_denies
552 was passed to the consturctor.
553 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400554 if self._ManifestUrlHasSecureScheme():
555 return self._CheckForHookApprovalManifest()
556 else:
557 return self._CheckForHookApprovalHash()
558
559 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
560 changed_prompt):
561 """Check for approval for a particular attribute and hook.
562
563 Args:
564 subkey: The git config key under [repo.hooks.<hook_type>] to store the
565 last approved string.
566 new_val: The new value to compare against the last approved one.
567 main_prompt: Message to display to the user to ask for approval.
568 changed_prompt: Message explaining why we're re-asking for approval.
569
570 Returns:
571 True if this hook is approved to run; False otherwise.
572
573 Raises:
574 HookError: Raised if the user doesn't approve and abort_if_user_denies
575 was passed to the consturctor.
576 """
Doug Anderson37282b42011-03-04 11:54:18 -0800577 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400578 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800579
Mike Frysinger40252c22016-08-15 21:23:44 -0400580 # Get the last value that the user approved for this hook; may be None.
581 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800582
Mike Frysinger40252c22016-08-15 21:23:44 -0400583 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800584 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400585 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800586 # Approval matched. We're done.
587 return True
588 else:
589 # Give the user a reason why we're prompting, since they last told
590 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400591 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800592 else:
593 prompt = ''
594
595 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
596 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400597 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530598 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900599 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800600
601 # User is doing a one-time approval.
602 if response in ('y', 'yes'):
603 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400604 elif response == 'always':
605 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800606 return True
607
608 # For anything else, we'll assume no approval.
609 if self._abort_if_user_denies:
610 raise HookError('You must allow the %s hook or use --no-verify.' %
611 self._hook_type)
612
613 return False
614
Mike Frysinger40252c22016-08-15 21:23:44 -0400615 def _ManifestUrlHasSecureScheme(self):
616 """Check if the URI for the manifest is a secure transport."""
617 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
618 parse_results = urllib.parse.urlparse(self._manifest_url)
619 return parse_results.scheme in secure_schemes
620
621 def _CheckForHookApprovalManifest(self):
622 """Check whether the user has approved this manifest host.
623
624 Returns:
625 True if this hook is approved to run; False otherwise.
626 """
627 return self._CheckForHookApprovalHelper(
628 'approvedmanifest',
629 self._manifest_url,
630 'Run hook scripts from %s' % (self._manifest_url,),
631 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
632
633 def _CheckForHookApprovalHash(self):
634 """Check whether the user has approved the hooks repo.
635
636 Returns:
637 True if this hook is approved to run; False otherwise.
638 """
639 prompt = ('Repo %s run the script:\n'
640 ' %s\n'
641 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700642 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400643 return self._CheckForHookApprovalHelper(
644 'approvedhash',
645 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700646 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400647 'Scripts have changed since %s was allowed.' % (self._hook_type,))
648
Mike Frysingerf7c51602019-06-18 17:23:39 -0400649 @staticmethod
650 def _ExtractInterpFromShebang(data):
651 """Extract the interpreter used in the shebang.
652
653 Try to locate the interpreter the script is using (ignoring `env`).
654
655 Args:
656 data: The file content of the script.
657
658 Returns:
659 The basename of the main script interpreter, or None if a shebang is not
660 used or could not be parsed out.
661 """
662 firstline = data.splitlines()[:1]
663 if not firstline:
664 return None
665
666 # The format here can be tricky.
667 shebang = firstline[0].strip()
668 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
669 if not m:
670 return None
671
672 # If the using `env`, find the target program.
673 interp = m.group(1)
674 if os.path.basename(interp) == 'env':
675 interp = m.group(2)
676
677 return interp
678
679 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
680 """Execute the hook script through |interp|.
681
682 Note: Support for this feature should be dropped ~Jun 2021.
683
684 Args:
685 interp: The Python program to run.
686 context: Basic Python context to execute the hook inside.
687 kwargs: Arbitrary arguments to pass to the hook script.
688
689 Raises:
690 HookError: When the hooks failed for any reason.
691 """
692 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
693 script = """
694import json, os, sys
695path = '''%(path)s'''
696kwargs = json.loads('''%(kwargs)s''')
697context = json.loads('''%(context)s''')
698sys.path.insert(0, os.path.dirname(path))
699data = open(path).read()
700exec(compile(data, path, 'exec'), context)
701context['main'](**kwargs)
702""" % {
703 'path': self._script_fullpath,
704 'kwargs': json.dumps(kwargs),
705 'context': json.dumps(context),
706 }
707
708 # We pass the script via stdin to avoid OS argv limits. It also makes
709 # unhandled exception tracebacks less verbose/confusing for users.
710 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
711 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
712 proc.communicate(input=script.encode('utf-8'))
713 if proc.returncode:
714 raise HookError('Failed to run %s hook.' % (self._hook_type,))
715
716 def _ExecuteHookViaImport(self, data, context, **kwargs):
717 """Execute the hook code in |data| directly.
718
719 Args:
720 data: The code of the hook to execute.
721 context: Basic Python context to execute the hook inside.
722 kwargs: Arbitrary arguments to pass to the hook script.
723
724 Raises:
725 HookError: When the hooks failed for any reason.
726 """
727 # Exec, storing global context in the context dict. We catch exceptions
728 # and convert to a HookError w/ just the failing traceback.
729 try:
730 exec(compile(data, self._script_fullpath, 'exec'), context)
731 except Exception:
732 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
733 (traceback.format_exc(), self._hook_type))
734
735 # Running the script should have defined a main() function.
736 if 'main' not in context:
737 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
738
739 # Call the main function in the hook. If the hook should cause the
740 # build to fail, it will raise an Exception. We'll catch that convert
741 # to a HookError w/ just the failing traceback.
742 try:
743 context['main'](**kwargs)
744 except Exception:
745 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
746 'above.' % (traceback.format_exc(), self._hook_type))
747
Doug Anderson37282b42011-03-04 11:54:18 -0800748 def _ExecuteHook(self, **kwargs):
749 """Actually execute the given hook.
750
751 This will run the hook's 'main' function in our python interpreter.
752
753 Args:
754 kwargs: Keyword arguments to pass to the hook. These are often specific
755 to the hook type. For instance, pre-upload hooks will contain
756 a project_list.
757 """
758 # Keep sys.path and CWD stashed away so that we can always restore them
759 # upon function exit.
760 orig_path = os.getcwd()
761 orig_syspath = sys.path
762
763 try:
764 # Always run hooks with CWD as topdir.
765 os.chdir(self._topdir)
766
767 # Put the hook dir as the first item of sys.path so hooks can do
768 # relative imports. We want to replace the repo dir as [0] so
769 # hooks can't import repo files.
770 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
771
Mike Frysingerf7c51602019-06-18 17:23:39 -0400772 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500773 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800774
Doug Anderson37282b42011-03-04 11:54:18 -0800775 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
776 # We don't actually want hooks to define their main with this argument--
777 # it's there to remind them that their hook should always take **kwargs.
778 # For instance, a pre-upload hook should be defined like:
779 # def main(project_list, **kwargs):
780 #
781 # This allows us to later expand the API without breaking old hooks.
782 kwargs = kwargs.copy()
783 kwargs['hook_should_take_kwargs'] = True
784
Mike Frysingerf7c51602019-06-18 17:23:39 -0400785 # See what version of python the hook has been written against.
786 data = open(self._script_fullpath).read()
787 interp = self._ExtractInterpFromShebang(data)
788 reexec = False
789 if interp:
790 prog = os.path.basename(interp)
791 if prog.startswith('python2') and sys.version_info.major != 2:
792 reexec = True
793 elif prog.startswith('python3') and sys.version_info.major == 2:
794 reexec = True
795
796 # Attempt to execute the hooks through the requested version of Python.
797 if reexec:
798 try:
799 self._ExecuteHookViaReexec(interp, context, **kwargs)
800 except OSError as e:
801 if e.errno == errno.ENOENT:
802 # We couldn't find the interpreter, so fallback to importing.
803 reexec = False
804 else:
805 raise
806
807 # Run the hook by importing directly.
808 if not reexec:
809 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800810 finally:
811 # Restore sys.path and CWD.
812 sys.path = orig_syspath
813 os.chdir(orig_path)
814
815 def Run(self, user_allows_all_hooks, **kwargs):
816 """Run the hook.
817
818 If the hook doesn't exist (because there is no hooks project or because
819 this particular hook is not enabled), this is a no-op.
820
821 Args:
822 user_allows_all_hooks: If True, we will never prompt about running the
823 hook--we'll just assume it's OK to run it.
824 kwargs: Keyword arguments to pass to the hook. These are often specific
825 to the hook type. For instance, pre-upload hooks will contain
826 a project_list.
827
828 Raises:
829 HookError: If there was a problem finding the hook or the user declined
830 to run a required hook (from _CheckForHookApproval).
831 """
832 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700833 if ((not self._hooks_project) or (self._hook_type not in
834 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800835 return
836
837 # Bail with a nice error if we can't find the hook.
838 if not os.path.isfile(self._script_fullpath):
839 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
840
841 # Make sure the user is OK with running the hook.
842 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
843 return
844
845 # Run the hook with the same version of python we're using.
846 self._ExecuteHook(**kwargs)
847
848
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700849class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600850 # These objects can be shared between several working trees.
851 shareable_files = ['description', 'info']
852 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
853 # These objects can only be used by a single working tree.
854 working_tree_files = ['config', 'packed-refs', 'shallow']
855 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700856
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700857 def __init__(self,
858 manifest,
859 name,
860 remote,
861 gitdir,
David James8d201162013-10-11 17:03:19 -0700862 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863 worktree,
864 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700865 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800866 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100867 rebase=True,
868 groups=None,
869 sync_c=False,
870 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900871 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100872 clone_depth=None,
873 upstream=None,
874 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500875 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100876 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900877 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700878 optimized_fetch=False,
879 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800880 """Init a Project object.
881
882 Args:
883 manifest: The XmlManifest object.
884 name: The `name` attribute of manifest.xml's project element.
885 remote: RemoteSpec object specifying its remote's properties.
886 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700887 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800888 worktree: Absolute path of git working tree.
889 relpath: Relative path of git working tree to repo's top directory.
890 revisionExpr: The `revision` attribute of manifest.xml's project element.
891 revisionId: git commit id for checking out.
892 rebase: The `rebase` attribute of manifest.xml's project element.
893 groups: The `groups` attribute of manifest.xml's project element.
894 sync_c: The `sync-c` attribute of manifest.xml's project element.
895 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900896 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800897 upstream: The `upstream` attribute of manifest.xml's project element.
898 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500899 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800900 is_derived: False if the project was explicitly defined in the manifest;
901 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400902 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900903 optimized_fetch: If True, when a project is set to a sha1 revision, only
904 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700905 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800906 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700907 self.manifest = manifest
908 self.name = name
909 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800910 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700911 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800912 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700913 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800914 else:
915 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700916 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700917 self.revisionExpr = revisionExpr
918
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700919 if revisionId is None \
920 and revisionExpr \
921 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700922 self.revisionId = revisionExpr
923 else:
924 self.revisionId = revisionId
925
Mike Pontillod3153822012-02-28 11:53:24 -0800926 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700927 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700928 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800929 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900930 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900931 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700932 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800933 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500934 # NB: Do not use this setting in __init__ to change behavior so that the
935 # manifest.git checkout can inspect & change it after instantiating. See
936 # the XmlManifest init code for more info.
937 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800938 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900939 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800940 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800941
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500944 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500945 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700946 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
947 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800949 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700950 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800951 else:
952 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700953 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700954 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700955 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400956 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700957 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700958
Doug Anderson37282b42011-03-04 11:54:18 -0800959 # This will be filled in if a project is later identified to be the
960 # project containing repo hooks.
961 self.enabled_repo_hooks = []
962
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800964 def Derived(self):
965 return self.is_derived
966
967 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700969 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970
971 @property
972 def CurrentBranch(self):
973 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400974
975 The branch name omits the 'refs/heads/' prefix.
976 None is returned if the project is on a detached HEAD, or if the work_git is
977 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700978 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400979 try:
980 b = self.work_git.GetHead()
981 except NoManifestException:
982 # If the local checkout is in a bad state, don't barf. Let the callers
983 # process this like the head is unreadable.
984 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 if b.startswith(R_HEADS):
986 return b[len(R_HEADS):]
987 return None
988
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700989 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500990 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
991 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
992 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200993
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700994 def IsDirty(self, consider_untracked=True):
995 """Is the working directory modified in some way?
996 """
997 self.work_git.update_index('-q',
998 '--unmerged',
999 '--ignore-missing',
1000 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +09001001 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002 return True
1003 if self.work_git.DiffZ('diff-files'):
1004 return True
1005 if consider_untracked and self.work_git.LsOthers():
1006 return True
1007 return False
1008
1009 _userident_name = None
1010 _userident_email = None
1011
1012 @property
1013 def UserName(self):
1014 """Obtain the user's personal name.
1015 """
1016 if self._userident_name is None:
1017 self._LoadUserIdentity()
1018 return self._userident_name
1019
1020 @property
1021 def UserEmail(self):
1022 """Obtain the user's email address. This is very likely
1023 to be their Gerrit login.
1024 """
1025 if self._userident_email is None:
1026 self._LoadUserIdentity()
1027 return self._userident_email
1028
1029 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001030 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1031 m = re.compile("^(.*) <([^>]*)> ").match(u)
1032 if m:
1033 self._userident_name = m.group(1)
1034 self._userident_email = m.group(2)
1035 else:
1036 self._userident_name = ''
1037 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001038
1039 def GetRemote(self, name):
1040 """Get the configuration for a single remote.
1041 """
1042 return self.config.GetRemote(name)
1043
1044 def GetBranch(self, name):
1045 """Get the configuration for a single branch.
1046 """
1047 return self.config.GetBranch(name)
1048
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001049 def GetBranches(self):
1050 """Get all existing local branches.
1051 """
1052 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001053 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001054 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001055
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301056 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001057 if name.startswith(R_HEADS):
1058 name = name[len(R_HEADS):]
1059 b = self.GetBranch(name)
1060 b.current = name == current
1061 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001062 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001063 heads[name] = b
1064
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_PUB):
1067 name = name[len(R_PUB):]
1068 b = heads.get(name)
1069 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001070 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001071
1072 return heads
1073
Colin Cross5acde752012-03-28 20:15:45 -07001074 def MatchesGroups(self, manifest_groups):
1075 """Returns true if the manifest groups specified at init should cause
1076 this project to be synced.
1077 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001078 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001079
1080 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001081 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001082 manifest_groups: "-group1,group2"
1083 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001084
1085 The special manifest group "default" will match any project that
1086 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001087 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001088 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001089 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001090 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001091 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001092
Conley Owens971de8e2012-04-16 10:36:08 -07001093 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001094 for group in expanded_manifest_groups:
1095 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001096 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001097 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001098 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001099
Conley Owens971de8e2012-04-16 10:36:08 -07001100 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001102# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001103 def UncommitedFiles(self, get_all=True):
1104 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001105
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001106 Args:
1107 get_all: a boolean, if True - get information about all different
1108 uncommitted files. If False - return as soon as any kind of
1109 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001110 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001111 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001112 self.work_git.update_index('-q',
1113 '--unmerged',
1114 '--ignore-missing',
1115 '--refresh')
1116 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001117 details.append("rebase in progress")
1118 if not get_all:
1119 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001120
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001121 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1122 if changes:
1123 details.extend(changes)
1124 if not get_all:
1125 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001126
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001127 changes = self.work_git.DiffZ('diff-files').keys()
1128 if changes:
1129 details.extend(changes)
1130 if not get_all:
1131 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001132
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001133 changes = self.work_git.LsOthers()
1134 if changes:
1135 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001136
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001137 return details
1138
1139 def HasChanges(self):
1140 """Returns true if there are uncommitted changes.
1141 """
1142 if self.UncommitedFiles(get_all=False):
1143 return True
1144 else:
1145 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001146
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001147 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001148 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001149
1150 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001151 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001152 quiet: If True then only print the project name. Do not print
1153 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001154 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001155 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001156 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001157 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001158 print(file=output_redir)
1159 print('project %s/' % self.relpath, file=output_redir)
1160 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001161 return
1162
1163 self.work_git.update_index('-q',
1164 '--unmerged',
1165 '--ignore-missing',
1166 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001167 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1169 df = self.work_git.DiffZ('diff-files')
1170 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001171 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001172 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173
1174 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001175 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001176 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001177 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001178
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001179 if quiet:
1180 out.nl()
1181 return 'DIRTY'
1182
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001183 branch = self.CurrentBranch
1184 if branch is None:
1185 out.nobranch('(*** NO BRANCH ***)')
1186 else:
1187 out.branch('branch %s', branch)
1188 out.nl()
1189
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001190 if rb:
1191 out.important('prior sync failed; rebase still in progress')
1192 out.nl()
1193
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194 paths = list()
1195 paths.extend(di.keys())
1196 paths.extend(df.keys())
1197 paths.extend(do)
1198
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301199 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001200 try:
1201 i = di[p]
1202 except KeyError:
1203 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001205 try:
1206 f = df[p]
1207 except KeyError:
1208 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001209
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001210 if i:
1211 i_status = i.status.upper()
1212 else:
1213 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001214
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001215 if f:
1216 f_status = f.status.lower()
1217 else:
1218 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219
1220 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001221 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001222 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001223 else:
1224 line = ' %s%s\t%s' % (i_status, f_status, p)
1225
1226 if i and not f:
1227 out.added('%s', line)
1228 elif (i and f) or (not i and f):
1229 out.changed('%s', line)
1230 elif not i and not f:
1231 out.untracked('%s', line)
1232 else:
1233 out.write('%s', line)
1234 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001235
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001236 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237
pelyad67872d2012-03-28 14:49:58 +03001238 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001239 """Prints the status of the repository to stdout.
1240 """
1241 out = DiffColoring(self.config)
1242 cmd = ['diff']
1243 if out.is_on:
1244 cmd.append('--color')
1245 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001246 if absolute_paths:
1247 cmd.append('--src-prefix=a/%s/' % self.relpath)
1248 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001250 try:
1251 p = GitCommand(self,
1252 cmd,
1253 capture_stdout=True,
1254 capture_stderr=True)
1255 except GitError as e:
1256 out.nl()
1257 out.project('project %s/' % self.relpath)
1258 out.nl()
1259 out.fail('%s', str(e))
1260 out.nl()
1261 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001262 has_diff = False
1263 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001264 if not hasattr(line, 'encode'):
1265 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001266 if not has_diff:
1267 out.nl()
1268 out.project('project %s/' % self.relpath)
1269 out.nl()
1270 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001271 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001272 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001274# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001275 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001276 """Was the branch published (uploaded) for code review?
1277 If so, returns the SHA-1 hash of the last published
1278 state for the branch.
1279 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001280 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001281 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001282 try:
1283 return self.bare_git.rev_parse(key)
1284 except GitError:
1285 return None
1286 else:
1287 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001288 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001289 except KeyError:
1290 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291
David Pursehouse8a68ff92012-09-24 12:15:13 +09001292 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 """Prunes any stale published refs.
1294 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001295 if all_refs is None:
1296 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001297 heads = set()
1298 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301299 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300 if name.startswith(R_HEADS):
1301 heads.add(name)
1302 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001303 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301305 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 n = name[len(R_PUB):]
1307 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001308 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001309
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001310 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001311 """List any branches which can be uploaded for review.
1312 """
1313 heads = {}
1314 pubed = {}
1315
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301316 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001318 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001320 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001321
1322 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301323 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001324 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001325 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001326 if selected_branch and branch != selected_branch:
1327 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001328
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001329 rb = self.GetUploadableBranch(branch)
1330 if rb:
1331 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001332 return ready
1333
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001334 def GetUploadableBranch(self, branch_name):
1335 """Get a single uploadable branch, or None.
1336 """
1337 branch = self.GetBranch(branch_name)
1338 base = branch.LocalMerge
1339 if branch.LocalMerge:
1340 rb = ReviewableBranch(self, branch, base)
1341 if rb.commits:
1342 return rb
1343 return None
1344
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001345 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001346 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001347 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001348 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001349 hashtags=(),
Bryan Jacobsf609f912013-05-06 13:36:24 -04001350 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001351 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -07001352 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001353 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001354 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001355 validate_certs=True,
1356 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001357 """Uploads the named branch for code review.
1358 """
1359 if branch is None:
1360 branch = self.CurrentBranch
1361 if branch is None:
1362 raise GitError('not currently on a branch')
1363
1364 branch = self.GetBranch(branch)
1365 if not branch.LocalMerge:
1366 raise GitError('branch %s does not track a remote' % branch.name)
1367 if not branch.remote.review:
1368 raise GitError('remote %s has no review url' % branch.remote.name)
1369
Bryan Jacobsf609f912013-05-06 13:36:24 -04001370 if dest_branch is None:
1371 dest_branch = self.dest_branch
1372 if dest_branch is None:
1373 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 if not dest_branch.startswith(R_HEADS):
1375 dest_branch = R_HEADS + dest_branch
1376
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001377 if not branch.remote.projectname:
1378 branch.remote.projectname = self.name
1379 branch.remote.Save()
1380
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001381 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001382 if url is None:
1383 raise UploadError('review not configured')
1384 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001385 if dryrun:
1386 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001387
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001388 if url.startswith('ssh://'):
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001389 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001390
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001391 for push_option in (push_options or []):
1392 cmd.append('-o')
1393 cmd.append(push_option)
1394
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001395 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001396
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001397 if dest_branch.startswith(R_HEADS):
1398 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001399
1400 upload_type = 'for'
1401 if draft:
1402 upload_type = 'drafts'
1403
1404 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1405 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001406 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001407 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001408 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001409 opts += ['t=%s' % p for p in hashtags]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001410
David Pursehousef25a3702018-11-14 19:01:22 -08001411 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001412 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendebury75bcd242018-10-31 13:48:01 -07001413 if notify:
1414 opts += ['notify=' + notify]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001415 if private:
1416 opts += ['private']
1417 if wip:
1418 opts += ['wip']
1419 if opts:
1420 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001421 cmd.append(ref_spec)
1422
Anthony King7bdac712014-07-16 12:56:40 +01001423 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001424 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425
1426 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1427 self.bare_git.UpdateRef(R_PUB + branch.name,
1428 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001429 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001430
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001431# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001432 def _ExtractArchive(self, tarpath, path=None):
1433 """Extract the given tar on its current location
1434
1435 Args:
1436 - tarpath: The path to the actual tar file
1437
1438 """
1439 try:
1440 with tarfile.open(tarpath, 'r') as tar:
1441 tar.extractall(path=path)
1442 return True
1443 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001444 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001445 return False
1446
Ningning Xiac2fbc782016-08-22 14:24:39 -07001447 def CachePopulate(self, cache_dir, url):
1448 """Populate cache in the cache_dir.
1449
1450 Args:
1451 cache_dir: Directory to cache git files from Google Storage.
1452 url: Git url of current repository.
1453
1454 Raises:
1455 CacheApplyError if it fails to populate the git cache.
1456 """
1457 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1458 '--cache-dir', cache_dir, url]
1459
1460 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1461 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1462 'url: %s' % (cache_dir, url))
1463
1464 def CacheExists(self, cache_dir, url):
1465 """Check the existence of the cache files.
1466
1467 Args:
1468 cache_dir: Directory to cache git files.
1469 url: Git url of current repository.
1470
1471 Raises:
1472 CacheApplyError if the cache files do not exist.
1473 """
1474 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1475
1476 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1477 if exist.Wait() != 0:
1478 raise CacheApplyError('Failed to execute git cache exists cmd. '
1479 'cache_dir: %s url: %s' % (cache_dir, url))
1480
1481 if not exist.stdout or not exist.stdout.strip():
1482 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1483 'url: %s' % (cache_dir, url))
1484 return exist.stdout.strip()
1485
1486 def CacheApply(self, cache_dir):
1487 """Apply git cache files populated from Google Storage buckets.
1488
1489 Args:
1490 cache_dir: Directory to cache git files.
1491
1492 Raises:
1493 CacheApplyError if it fails to apply git caches.
1494 """
1495 remote = self.GetRemote(self.remote.name)
1496
1497 self.CachePopulate(cache_dir, remote.url)
1498
1499 mirror_dir = self.CacheExists(cache_dir, remote.url)
1500
1501 refspec = RefSpec(True, 'refs/heads/*',
1502 'refs/remotes/%s/*' % remote.name)
1503
1504 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1505 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1506 raise CacheApplyError('Failed to fetch refs %s from %s' %
1507 (mirror_dir, str(refspec)))
1508
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001509 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001510 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001511 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001512 is_new=None,
1513 current_branch_only=False,
1514 force_sync=False,
1515 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001516 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001517 archive=False,
1518 optimized_fetch=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001519 prune=False,
Mike Frysingerde72f6a2018-12-13 03:20:31 -05001520 submodules=False,
Mike Frysinger39ba6312019-07-27 12:45:51 -04001521 clone_filter=None,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001522 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001523 """Perform only the network IO portion of the sync process.
1524 Local working directory/branch state is not affected.
1525 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001526 if archive and not isinstance(self, MetaProject):
1527 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001528 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001529 return False
1530
1531 name = self.relpath.replace('\\', '/')
1532 name = name.replace('/', '_')
1533 tarpath = '%s.tar' % name
1534 topdir = self.manifest.topdir
1535
1536 try:
1537 self._FetchArchive(tarpath, cwd=topdir)
1538 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001539 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001540 return False
1541
1542 # From now on, we only need absolute tarpath
1543 tarpath = os.path.join(topdir, tarpath)
1544
1545 if not self._ExtractArchive(tarpath, path=topdir):
1546 return False
1547 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001548 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001549 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001550 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001551 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001552 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001553 if is_new is None:
1554 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001555 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001556 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001557 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001558 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001559 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001560
1561 if is_new:
1562 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1563 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001564 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001565 # This works for both absolute and relative alternate directories.
1566 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001567 except IOError:
1568 alt_dir = None
1569 else:
1570 alt_dir = None
1571
Ningning Xiac2fbc782016-08-22 14:24:39 -07001572 applied_cache = False
1573 # If cache_dir is provided, and it's a new repository without
1574 # alternative_dir, bootstrap this project repo with the git
1575 # cache files.
1576 if cache_dir is not None and is_new and alt_dir is None:
1577 try:
1578 self.CacheApply(cache_dir)
1579 applied_cache = True
1580 is_new = False
1581 except CacheApplyError as e:
1582 _error('Could not apply git cache: %s', e)
1583 _error('Please check if you have the right GS credentials.')
1584 _error('Please check if the cache files exist in GS.')
1585
Mike Frysingere50b6a72020-02-19 01:45:48 -05001586 if (clone_bundle
Mike Frysingerd70c6072020-02-25 10:22:02 -05001587 and not applied_cache
Mike Frysingere50b6a72020-02-19 01:45:48 -05001588 and alt_dir is None
1589 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001590 is_new = False
1591
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001592 if not current_branch_only:
1593 if self.sync_c:
1594 current_branch_only = True
1595 elif not self.manifest._loaded:
1596 # Manifest cannot check defaults until it syncs.
1597 current_branch_only = False
1598 elif self.manifest.default.sync_c:
1599 current_branch_only = True
1600
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001601 if not self.sync_tags:
1602 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001603
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001604 if self.clone_depth:
1605 depth = self.clone_depth
1606 else:
1607 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1608
Mike Frysinger521d01b2020-02-17 01:51:49 -05001609 # See if we can skip the network fetch entirely.
1610 if not (optimized_fetch and
1611 (ID_RE.match(self.revisionExpr) and
1612 self._CheckForImmutableRevision())):
1613 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001614 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1615 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001616 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001617 submodules=submodules, force_sync=force_sync,
1618 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001619 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001620
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001621 mp = self.manifest.manifestProject
1622 dissociate = mp.config.GetBoolean('repo.dissociate')
1623 if dissociate:
1624 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1625 if os.path.exists(alternates_file):
1626 cmd = ['repack', '-a', '-d']
1627 if GitCommand(self, cmd, bare=True).Wait() != 0:
1628 return False
1629 platform_utils.remove(alternates_file)
1630
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001631 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001632 self._InitMRef()
1633 else:
1634 self._InitMirrorHead()
1635 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001636 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001637 except OSError:
1638 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001640
1641 def PostRepoUpgrade(self):
1642 self._InitHooks()
1643
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001644 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001645 if self.manifest.isGitcClient:
1646 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001647 for copyfile in self.copyfiles:
1648 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001649 for linkfile in self.linkfiles:
1650 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001651
Julien Camperguedd654222014-01-09 16:21:37 +01001652 def GetCommitRevisionId(self):
1653 """Get revisionId of a commit.
1654
1655 Use this method instead of GetRevisionId to get the id of the commit rather
1656 than the id of the current git object (for example, a tag)
1657
1658 """
1659 if not self.revisionExpr.startswith(R_TAGS):
1660 return self.GetRevisionId(self._allrefs)
1661
1662 try:
1663 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1664 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001665 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1666 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001667
David Pursehouse8a68ff92012-09-24 12:15:13 +09001668 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001669 if self.revisionId:
1670 return self.revisionId
1671
1672 rem = self.GetRemote(self.remote.name)
1673 rev = rem.ToLocal(self.revisionExpr)
1674
David Pursehouse8a68ff92012-09-24 12:15:13 +09001675 if all_refs is not None and rev in all_refs:
1676 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001677
1678 try:
1679 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1680 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001681 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1682 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001683
Martin Kellye4e94d22017-03-21 16:05:12 -07001684 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 """Perform only the local IO portion of the sync process.
1686 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001688 if not os.path.exists(self.gitdir):
1689 syncbuf.fail(self,
1690 'Cannot checkout %s due to missing network sync; Run '
1691 '`repo sync -n %s` first.' %
1692 (self.name, self.name))
1693 return
1694
Martin Kellye4e94d22017-03-21 16:05:12 -07001695 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001696 all_refs = self.bare_ref.all
1697 self.CleanPublishedCache(all_refs)
1698 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001699
David Pursehouse1d947b32012-10-25 12:23:11 +09001700 def _doff():
1701 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001702 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001703
Martin Kellye4e94d22017-03-21 16:05:12 -07001704 def _dosubmodules():
1705 self._SyncSubmodules(quiet=True)
1706
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001707 head = self.work_git.GetHead()
1708 if head.startswith(R_HEADS):
1709 branch = head[len(R_HEADS):]
1710 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001711 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001712 except KeyError:
1713 head = None
1714 else:
1715 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001716
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001717 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001718 # Currently on a detached HEAD. The user is assumed to
1719 # not have any local modifications worth worrying about.
1720 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001721 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001722 syncbuf.fail(self, _PriorSyncFailedError())
1723 return
1724
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001725 if head == revid:
1726 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001727 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001728 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001729 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001730 # The copy/linkfile config may have changed.
1731 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001732 return
1733 else:
1734 lost = self._revlist(not_rev(revid), HEAD)
1735 if lost:
1736 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001737
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001738 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001739 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001740 if submodules:
1741 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001742 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001743 syncbuf.fail(self, e)
1744 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001745 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001746 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001748 if head == revid:
1749 # No changes; don't do anything further.
1750 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001751 # The copy/linkfile config may have changed.
1752 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001753 return
1754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001755 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001757 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001759 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001761 syncbuf.info(self,
1762 "leaving %s; does not track upstream",
1763 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001765 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001766 if submodules:
1767 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001768 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001769 syncbuf.fail(self, e)
1770 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001771 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001772 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001774 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001775
1776 # See if we can perform a fast forward merge. This can happen if our
1777 # branch isn't in the exact same state as we last published.
1778 try:
1779 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1780 # Skip the published logic.
1781 pub = False
1782 except GitError:
1783 pub = self.WasPublished(branch.name, all_refs)
1784
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001786 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001787 if not_merged:
1788 if upstream_gain:
1789 # The user has published this branch and some of those
1790 # commits are not yet merged upstream. We do not want
1791 # to rewrite the published commits so we punt.
1792 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001793 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001794 "branch %s is published (but not merged) and is now "
1795 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001796 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001797 elif pub == head:
1798 # All published commits are merged, and thus we are a
1799 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001800 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001801 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001802 if submodules:
1803 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001804 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001805
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001806 # Examine the local commits not in the remote. Find the
1807 # last one attributed to this user, if any.
1808 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001809 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001810 last_mine = None
1811 cnt_mine = 0
1812 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001813 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001814 if committer_email == self.UserEmail:
1815 last_mine = commit_id
1816 cnt_mine += 1
1817
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001818 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001819 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001820
1821 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001822 syncbuf.fail(self, _DirtyError())
1823 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001824
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001825 # If the upstream switched on us, warn the user.
1826 #
1827 if branch.merge != self.revisionExpr:
1828 if branch.merge and self.revisionExpr:
1829 syncbuf.info(self,
1830 'manifest switched %s...%s',
1831 branch.merge,
1832 self.revisionExpr)
1833 elif branch.merge:
1834 syncbuf.info(self,
1835 'manifest no longer tracks %s',
1836 branch.merge)
1837
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001838 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001839 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001840 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001841 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001842 syncbuf.info(self,
1843 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001844 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001845
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001846 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001847 if not ID_RE.match(self.revisionExpr):
1848 # in case of manifest sync the revisionExpr might be a SHA1
1849 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001850 if not branch.merge.startswith('refs/'):
1851 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001852 branch.Save()
1853
Mike Pontillod3153822012-02-28 11:53:24 -08001854 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001855 def _docopyandlink():
1856 self._CopyAndLinkFiles()
1857
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001858 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001859 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001860 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001861 if submodules:
1862 syncbuf.later2(self, _dosubmodules)
1863 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001864 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001865 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001866 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001867 if submodules:
1868 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001869 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001870 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001871 syncbuf.fail(self, e)
1872 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001873 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001874 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001875 if submodules:
1876 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001877
Mike Frysingere6a202f2019-08-02 15:57:57 -04001878 def AddCopyFile(self, src, dest, topdir):
1879 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001880
Mike Frysingere6a202f2019-08-02 15:57:57 -04001881 No filesystem changes occur here. Actual copying happens later on.
1882
1883 Paths should have basic validation run on them before being queued.
1884 Further checking will be handled when the actual copy happens.
1885 """
1886 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1887
1888 def AddLinkFile(self, src, dest, topdir):
1889 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1890
1891 No filesystem changes occur here. Actual linking happens later on.
1892
1893 Paths should have basic validation run on them before being queued.
1894 Further checking will be handled when the actual link happens.
1895 """
1896 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001897
James W. Mills24c13082012-04-12 15:04:13 -05001898 def AddAnnotation(self, name, value, keep):
1899 self.annotations.append(_Annotation(name, value, keep))
1900
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001901 def DownloadPatchSet(self, change_id, patch_id):
1902 """Download a single patch set of a single change to FETCH_HEAD.
1903 """
1904 remote = self.GetRemote(self.remote.name)
1905
1906 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001907 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001908 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001909 if GitCommand(self, cmd, bare=True).Wait() != 0:
1910 return None
1911 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001912 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001913 change_id,
1914 patch_id,
1915 self.bare_git.rev_parse('FETCH_HEAD'))
1916
Mike Frysingerc0d18662020-02-19 19:19:18 -05001917 def DeleteWorktree(self, quiet=False, force=False):
1918 """Delete the source checkout and any other housekeeping tasks.
Mike Frysingerf914edc2020-02-09 03:01:56 -05001919
Mike Frysingerc0d18662020-02-19 19:19:18 -05001920 This currently leaves behind the internal .repo/ cache state. This helps
1921 when switching branches or manifest changes get reverted as we don't have
1922 to redownload all the git objects. But we should do some GC at some point.
1923
1924 Args:
1925 quiet: Whether to hide normal messages.
1926 force: Always delete tree even if dirty.
1927
1928 Returns:
1929 True if the worktree was completely cleaned out.
1930 """
1931 if self.IsDirty():
1932 if force:
1933 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1934 (self.relpath,), file=sys.stderr)
1935 else:
1936 print('error: %s: Cannot remove project: uncommitted changes are '
1937 'present.\n' % (self.relpath,), file=sys.stderr)
1938 return False
1939
1940 if not quiet:
1941 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1942
1943 # Unlock and delink from the main worktree. We don't use git's worktree
1944 # remove because it will recursively delete projects -- we handle that
1945 # ourselves below. https://crbug.com/git/48
1946 if self.use_git_worktrees:
1947 needle = platform_utils.realpath(self.gitdir)
1948 # Find the git worktree commondir under .repo/worktrees/.
1949 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1950 assert output.startswith('worktree '), output
1951 commondir = output[9:]
1952 # Walk each of the git worktrees to see where they point.
1953 configs = os.path.join(commondir, 'worktrees')
1954 for name in os.listdir(configs):
1955 gitdir = os.path.join(configs, name, 'gitdir')
1956 with open(gitdir) as fp:
1957 relpath = fp.read().strip()
1958 # Resolve the checkout path and see if it matches this project.
1959 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1960 if fullpath == needle:
1961 platform_utils.rmtree(os.path.join(configs, name))
1962
1963 # Delete the .git directory first, so we're less likely to have a partially
1964 # working git repository around. There shouldn't be any git projects here,
1965 # so rmtree works.
1966
1967 # Try to remove plain files first in case of git worktrees. If this fails
1968 # for any reason, we'll fall back to rmtree, and that'll display errors if
1969 # it can't remove things either.
1970 try:
1971 platform_utils.remove(self.gitdir)
1972 except OSError:
1973 pass
1974 try:
1975 platform_utils.rmtree(self.gitdir)
1976 except OSError as e:
1977 if e.errno != errno.ENOENT:
1978 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1979 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1980 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1981 return False
1982
1983 # Delete everything under the worktree, except for directories that contain
1984 # another git project.
1985 dirs_to_remove = []
1986 failed = False
1987 for root, dirs, files in platform_utils.walk(self.worktree):
1988 for f in files:
1989 path = os.path.join(root, f)
1990 try:
1991 platform_utils.remove(path)
1992 except OSError as e:
1993 if e.errno != errno.ENOENT:
1994 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1995 failed = True
1996 dirs[:] = [d for d in dirs
1997 if not os.path.lexists(os.path.join(root, d, '.git'))]
1998 dirs_to_remove += [os.path.join(root, d) for d in dirs
1999 if os.path.join(root, d) not in dirs_to_remove]
2000 for d in reversed(dirs_to_remove):
2001 if platform_utils.islink(d):
2002 try:
2003 platform_utils.remove(d)
2004 except OSError as e:
2005 if e.errno != errno.ENOENT:
2006 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
2007 failed = True
2008 elif not platform_utils.listdir(d):
2009 try:
2010 platform_utils.rmdir(d)
2011 except OSError as e:
2012 if e.errno != errno.ENOENT:
2013 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
2014 failed = True
2015 if failed:
2016 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
2017 file=sys.stderr)
2018 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
2019 return False
2020
2021 # Try deleting parent dirs if they are empty.
2022 path = self.worktree
2023 while path != self.manifest.topdir:
2024 try:
2025 platform_utils.rmdir(path)
2026 except OSError as e:
2027 if e.errno != errno.ENOENT:
2028 break
2029 path = os.path.dirname(path)
2030
2031 return True
2032
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002033# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07002034 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002035 """Create a new branch off the manifest's revision.
2036 """
Simran Basib9a1b732015-08-20 12:19:28 -07002037 if not branch_merge:
2038 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002039 head = self.work_git.GetHead()
2040 if head == (R_HEADS + name):
2041 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002042
David Pursehouse8a68ff92012-09-24 12:15:13 +09002043 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01002044 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002045 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002046 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002047 capture_stdout=True,
2048 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07002049
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002050 branch = self.GetBranch(name)
2051 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07002052 branch.merge = branch_merge
2053 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
2054 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07002055
2056 if revision is None:
2057 revid = self.GetRevisionId(all_refs)
2058 else:
2059 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07002060
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002061 if head.startswith(R_HEADS):
2062 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002063 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002064 except KeyError:
2065 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002066 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05002067 ref = R_HEADS + name
2068 self.work_git.update_ref(ref, revid)
2069 self.work_git.symbolic_ref(HEAD, ref)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002070 branch.Save()
2071 return True
2072
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002073 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002074 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01002075 capture_stdout=True,
2076 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002077 branch.Save()
2078 return True
2079 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002080
Wink Saville02d79452009-04-10 13:01:24 -07002081 def CheckoutBranch(self, name):
2082 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002083
2084 Args:
2085 name: The name of the branch to checkout.
2086
2087 Returns:
2088 True if the checkout succeeded; False if it didn't; None if the branch
2089 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002090 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002091 rev = R_HEADS + name
2092 head = self.work_git.GetHead()
2093 if head == rev:
2094 # Already on the branch
2095 #
2096 return True
Wink Saville02d79452009-04-10 13:01:24 -07002097
David Pursehouse8a68ff92012-09-24 12:15:13 +09002098 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002099 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002100 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002101 except KeyError:
2102 # Branch does not exist in this project
2103 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002104 return None
Wink Saville02d79452009-04-10 13:01:24 -07002105
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002106 if head.startswith(R_HEADS):
2107 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002108 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002109 except KeyError:
2110 head = None
2111
2112 if head == revid:
2113 # Same revision; just update HEAD to point to the new
2114 # target branch, but otherwise take no other action.
2115 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05002116 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
2117 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002118 return True
2119
2120 return GitCommand(self,
2121 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002122 capture_stdout=True,
2123 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002124
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002125 def AbandonBranch(self, name):
2126 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002127
2128 Args:
2129 name: The name of the branch to abandon.
2130
2131 Returns:
2132 True if the abandon succeeded; False if it didn't; None if the branch
2133 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002134 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002135 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002136 all_refs = self.bare_ref.all
2137 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002138 # Doesn't exist
2139 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002140
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002141 head = self.work_git.GetHead()
2142 if head == rev:
2143 # We can't destroy the branch while we are sitting
2144 # on it. Switch to a detached HEAD.
2145 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002146 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002147
David Pursehouse8a68ff92012-09-24 12:15:13 +09002148 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002149 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05002150 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002151 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002152 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002153
2154 return GitCommand(self,
2155 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002156 capture_stdout=True,
2157 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002158
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002159 def PruneHeads(self):
2160 """Prune any topic branches already merged into upstream.
2161 """
2162 cb = self.CurrentBranch
2163 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002164 left = self._allrefs
2165 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002166 if name.startswith(R_HEADS):
2167 name = name[len(R_HEADS):]
2168 if cb is None or name != cb:
2169 kill.append(name)
2170
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002171 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002172 if cb is not None \
2173 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002174 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002175 self.work_git.DetachHead(HEAD)
2176 kill.append(cb)
2177
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002178 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002179 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002180
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002181 try:
2182 self.bare_git.DetachHead(rev)
2183
2184 b = ['branch', '-d']
2185 b.extend(kill)
2186 b = GitCommand(self, b, bare=True,
2187 capture_stdout=True,
2188 capture_stderr=True)
2189 b.Wait()
2190 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002191 if ID_RE.match(old):
2192 self.bare_git.DetachHead(old)
2193 else:
2194 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002195 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002196
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002197 for branch in kill:
2198 if (R_HEADS + branch) not in left:
2199 self.CleanPublishedCache()
2200 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002201
2202 if cb and cb not in kill:
2203 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002204 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002205
2206 kept = []
2207 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002208 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002209 branch = self.GetBranch(branch)
2210 base = branch.LocalMerge
2211 if not base:
2212 base = rev
2213 kept.append(ReviewableBranch(self, branch, base))
2214 return kept
2215
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002216# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002217 def GetRegisteredSubprojects(self):
2218 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002219
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002220 def rec(subprojects):
2221 if not subprojects:
2222 return
2223 result.extend(subprojects)
2224 for p in subprojects:
2225 rec(p.subprojects)
2226 rec(self.subprojects)
2227 return result
2228
2229 def _GetSubmodules(self):
2230 # Unfortunately we cannot call `git submodule status --recursive` here
2231 # because the working tree might not exist yet, and it cannot be used
2232 # without a working tree in its current implementation.
2233
2234 def get_submodules(gitdir, rev):
2235 # Parse .gitmodules for submodule sub_paths and sub_urls
2236 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2237 if not sub_paths:
2238 return []
2239 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2240 # revision of submodule repository
2241 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2242 submodules = []
2243 for sub_path, sub_url in zip(sub_paths, sub_urls):
2244 try:
2245 sub_rev = sub_revs[sub_path]
2246 except KeyError:
2247 # Ignore non-exist submodules
2248 continue
2249 submodules.append((sub_rev, sub_path, sub_url))
2250 return submodules
2251
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002252 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2253 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002254
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002255 def parse_gitmodules(gitdir, rev):
2256 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2257 try:
Anthony King7bdac712014-07-16 12:56:40 +01002258 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2259 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002260 except GitError:
2261 return [], []
2262 if p.Wait() != 0:
2263 return [], []
2264
2265 gitmodules_lines = []
2266 fd, temp_gitmodules_path = tempfile.mkstemp()
2267 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002268 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002269 os.close(fd)
2270 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002271 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2272 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002273 if p.Wait() != 0:
2274 return [], []
2275 gitmodules_lines = p.stdout.split('\n')
2276 except GitError:
2277 return [], []
2278 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002279 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002280
2281 names = set()
2282 paths = {}
2283 urls = {}
2284 for line in gitmodules_lines:
2285 if not line:
2286 continue
2287 m = re_path.match(line)
2288 if m:
2289 names.add(m.group(1))
2290 paths[m.group(1)] = m.group(2)
2291 continue
2292 m = re_url.match(line)
2293 if m:
2294 names.add(m.group(1))
2295 urls[m.group(1)] = m.group(2)
2296 continue
2297 names = sorted(names)
2298 return ([paths.get(name, '') for name in names],
2299 [urls.get(name, '') for name in names])
2300
2301 def git_ls_tree(gitdir, rev, paths):
2302 cmd = ['ls-tree', rev, '--']
2303 cmd.extend(paths)
2304 try:
Anthony King7bdac712014-07-16 12:56:40 +01002305 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2306 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002307 except GitError:
2308 return []
2309 if p.Wait() != 0:
2310 return []
2311 objects = {}
2312 for line in p.stdout.split('\n'):
2313 if not line.strip():
2314 continue
2315 object_rev, object_path = line.split()[2:4]
2316 objects[object_path] = object_rev
2317 return objects
2318
2319 try:
2320 rev = self.GetRevisionId()
2321 except GitError:
2322 return []
2323 return get_submodules(self.gitdir, rev)
2324
2325 def GetDerivedSubprojects(self):
2326 result = []
2327 if not self.Exists:
2328 # If git repo does not exist yet, querying its submodules will
2329 # mess up its states; so return here.
2330 return result
2331 for rev, path, url in self._GetSubmodules():
2332 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002333 relpath, worktree, gitdir, objdir = \
2334 self.manifest.GetSubprojectPaths(self, name, path)
2335 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002336 if project:
2337 result.extend(project.GetDerivedSubprojects())
2338 continue
David James8d201162013-10-11 17:03:19 -07002339
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002340 if url.startswith('..'):
2341 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002342 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002343 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002344 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002345 review=self.remote.review,
2346 revision=self.remote.revision)
2347 subproject = Project(manifest=self.manifest,
2348 name=name,
2349 remote=remote,
2350 gitdir=gitdir,
2351 objdir=objdir,
2352 worktree=worktree,
2353 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002354 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002355 revisionId=rev,
2356 rebase=self.rebase,
2357 groups=self.groups,
2358 sync_c=self.sync_c,
2359 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002360 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002361 parent=self,
2362 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002363 result.append(subproject)
2364 result.extend(subproject.GetDerivedSubprojects())
2365 return result
2366
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002367# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002368 def EnableRepositoryExtension(self, key, value='true', version=1):
2369 """Enable git repository extension |key| with |value|.
2370
2371 Args:
2372 key: The extension to enabled. Omit the "extensions." prefix.
2373 value: The value to use for the extension.
2374 version: The minimum git repository version needed.
2375 """
2376 # Make sure the git repo version is new enough already.
2377 found_version = self.config.GetInt('core.repositoryFormatVersion')
2378 if found_version is None:
2379 found_version = 0
2380 if found_version < version:
2381 self.config.SetString('core.repositoryFormatVersion', str(version))
2382
2383 # Enable the extension!
2384 self.config.SetString('extensions.%s' % (key,), value)
2385
Zac Livingstone4332262017-06-16 08:56:09 -06002386 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002387 try:
2388 # if revision (sha or tag) is not present then following function
2389 # throws an error.
2390 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2391 return True
2392 except GitError:
2393 # There is no such persistent revision. We have to fetch it.
2394 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002395
Julien Campergue335f5ef2013-10-16 11:02:35 +02002396 def _FetchArchive(self, tarpath, cwd=None):
2397 cmd = ['archive', '-v', '-o', tarpath]
2398 cmd.append('--remote=%s' % self.remote.url)
2399 cmd.append('--prefix=%s/' % self.relpath)
2400 cmd.append(self.revisionExpr)
2401
2402 command = GitCommand(self, cmd, cwd=cwd,
2403 capture_stdout=True,
2404 capture_stderr=True)
2405
2406 if command.Wait() != 0:
2407 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2408
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002409 def _RemoteFetch(self, name=None,
2410 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002411 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002412 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002413 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002414 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002415 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002416 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002417 depth=None,
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002418 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002419 force_sync=False,
2420 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002421
2422 is_sha1 = False
2423 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002424 # The depth should not be used when fetching to a mirror because
2425 # it will result in a shallow repository that cannot be cloned or
2426 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002427 # The repo project should also never be synced with partial depth.
2428 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2429 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002430
Shawn Pearce69e04d82014-01-29 12:48:54 -08002431 if depth:
2432 current_branch_only = True
2433
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002434 if ID_RE.match(self.revisionExpr) is not None:
2435 is_sha1 = True
2436
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002437 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002438 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002439 # this is a tag and its sha1 value should never change
2440 tag_name = self.revisionExpr[len(R_TAGS):]
2441
2442 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002443 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002444 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002445 print('Skipped fetching project %s (already have persistent ref)'
2446 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002447 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002448 if is_sha1 and not depth:
2449 # When syncing a specific commit and --depth is not set:
2450 # * if upstream is explicitly specified and is not a sha1, fetch only
2451 # upstream as users expect only upstream to be fetch.
2452 # Note: The commit might not be in upstream in which case the sync
2453 # will fail.
2454 # * otherwise, fetch all branches to make sure we end up with the
2455 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002456 if self.upstream:
2457 current_branch_only = not ID_RE.match(self.upstream)
2458 else:
2459 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002460
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002461 if not name:
2462 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002463
2464 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002465 remote = self.GetRemote(name)
2466 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002467 ssh_proxy = True
2468
Shawn O. Pearce88443382010-10-08 10:02:09 +02002469 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002470 if alt_dir and 'objects' == os.path.basename(alt_dir):
2471 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002472 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2473 remote = self.GetRemote(name)
2474
David Pursehouse8a68ff92012-09-24 12:15:13 +09002475 all_refs = self.bare_ref.all
2476 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002477 tmp = set()
2478
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302479 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002480 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002481 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002482 all_refs[r] = ref_id
2483 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002484 continue
2485
David Pursehouse8a68ff92012-09-24 12:15:13 +09002486 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002487 continue
2488
David Pursehouse8a68ff92012-09-24 12:15:13 +09002489 r = 'refs/_alt/%s' % ref_id
2490 all_refs[r] = ref_id
2491 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002492 tmp.add(r)
2493
heping3d7bbc92017-04-12 19:51:47 +08002494 tmp_packed_lines = []
2495 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002496
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302497 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002498 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002499 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002500 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002501 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002502
heping3d7bbc92017-04-12 19:51:47 +08002503 tmp_packed = ''.join(tmp_packed_lines)
2504 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002505 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002506 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002507 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002508
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002509 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002510
Xin Li745be2e2019-06-03 11:24:30 -07002511 if clone_filter:
2512 git_require((2, 19, 0), fail=True, msg='partial clones')
2513 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002514 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002515
Conley Owensf97e8382015-01-21 11:12:46 -08002516 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002517 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002518 else:
2519 # If this repo has shallow objects, then we don't know which refs have
2520 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2521 # do this with projects that don't have shallow objects, since it is less
2522 # efficient.
2523 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2524 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002525
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002526 if quiet:
2527 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002528 if not self.worktree:
2529 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002530 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002531
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002532 if force_sync:
2533 cmd.append('--force')
2534
David Pursehouse74cfd272015-10-14 10:50:15 +09002535 if prune:
2536 cmd.append('--prune')
2537
Martin Kellye4e94d22017-03-21 16:05:12 -07002538 if submodules:
2539 cmd.append('--recurse-submodules=on-demand')
2540
Kuang-che Wu6856f982019-11-25 12:37:55 +08002541 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002542 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002543 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002544 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002545 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002546 spec.append('tag')
2547 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002548
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302549 if self.manifest.IsMirror and not current_branch_only:
2550 branch = None
2551 else:
2552 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002553 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002554 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002555 # Shallow checkout of a specific commit, fetch from that commit and not
2556 # the heads only as the commit might be deeper in the history.
2557 spec.append(branch)
2558 else:
2559 if is_sha1:
2560 branch = self.upstream
2561 if branch is not None and branch.strip():
2562 if not branch.startswith('refs/'):
2563 branch = R_HEADS + branch
2564 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2565
2566 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2567 # whole repo.
2568 if self.manifest.IsMirror and not spec:
2569 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2570
2571 # If using depth then we should not get all the tags since they may
2572 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002573 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002574 cmd.append('--no-tags')
2575 else:
2576 cmd.append('--tags')
2577 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2578
Conley Owens80b87fe2014-05-09 17:13:44 -07002579 cmd.extend(spec)
2580
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002581 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002582 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002583 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2584 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002585 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002586 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002587 ok = True
2588 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002589 # If needed, run the 'git remote prune' the first time through the loop
2590 elif (not _i and
2591 "error:" in gitcmd.stderr and
2592 "git remote prune" in gitcmd.stderr):
2593 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002594 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002595 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002596 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002597 break
2598 continue
Brian Harring14a66742012-09-28 20:21:57 -07002599 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002600 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2601 # in sha1 mode, we just tried sync'ing from the upstream field; it
2602 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002603 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002604 elif ret < 0:
2605 # Git died with a signal, exit immediately
2606 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002607 if not verbose:
2608 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002609 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002610
2611 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002612 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002613 if old_packed != '':
2614 _lwrite(packed_refs, old_packed)
2615 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002616 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002617 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002618
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002619 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002620 # We just synced the upstream given branch; verify we
2621 # got what we wanted, else trigger a second run of all
2622 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002623 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002624 # Sync the current branch only with depth set to None.
2625 # We always pass depth=None down to avoid infinite recursion.
2626 return self._RemoteFetch(
2627 name=name, quiet=quiet, verbose=verbose,
2628 current_branch_only=current_branch_only and depth,
2629 initial=False, alt_dir=alt_dir,
2630 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002631
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002632 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002633
Mike Frysingere50b6a72020-02-19 01:45:48 -05002634 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002635 if initial and \
2636 (self.manifest.manifestProject.config.GetString('repo.depth') or
2637 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002638 return False
2639
2640 remote = self.GetRemote(self.remote.name)
2641 bundle_url = remote.url + '/clone.bundle'
2642 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002643 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2644 'persistent-http',
2645 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002646 return False
2647
2648 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2649 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2650
2651 exist_dst = os.path.exists(bundle_dst)
2652 exist_tmp = os.path.exists(bundle_tmp)
2653
2654 if not initial and not exist_dst and not exist_tmp:
2655 return False
2656
2657 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002658 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2659 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002660 if not exist_dst:
2661 return False
2662
2663 cmd = ['fetch']
2664 if quiet:
2665 cmd.append('--quiet')
2666 if not self.worktree:
2667 cmd.append('--update-head-ok')
2668 cmd.append(bundle_dst)
2669 for f in remote.fetch:
2670 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002671 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002672
2673 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002674 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002675 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002676 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002677 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002678 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002679
Mike Frysingere50b6a72020-02-19 01:45:48 -05002680 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002681 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002682 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002683
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002684 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002685 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002686 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002687 if os.path.exists(tmpPath):
2688 size = os.stat(tmpPath).st_size
2689 if size >= 1024:
2690 cmd += ['--continue-at', '%d' % (size,)]
2691 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002692 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002693 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002694 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002695 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002696 if proxy:
2697 cmd += ['--proxy', proxy]
2698 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2699 cmd += ['--proxy', os.environ['http_proxy']]
2700 if srcUrl.startswith('persistent-https'):
2701 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2702 elif srcUrl.startswith('persistent-http'):
2703 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002704 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002705
Dave Borowitz137d0132015-01-02 11:12:54 -08002706 if IsTrace():
2707 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002708 if verbose:
2709 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2710 stdout = None if verbose else subprocess.PIPE
2711 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002712 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002713 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002714 except OSError:
2715 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002716
Mike Frysingere50b6a72020-02-19 01:45:48 -05002717 (output, _) = proc.communicate()
2718 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002719
Dave Borowitz137d0132015-01-02 11:12:54 -08002720 if curlret == 22:
2721 # From curl man page:
2722 # 22: HTTP page not retrieved. The requested url was not found or
2723 # returned another error with the HTTP error code being 400 or above.
2724 # This return code only appears if -f, --fail is used.
2725 if not quiet:
2726 print("Server does not provide clone.bundle; ignoring.",
2727 file=sys.stderr)
2728 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002729 elif curlret and not verbose and output:
2730 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002731
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002732 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002733 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002734 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002735 return True
2736 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002737 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002738 return False
2739 else:
2740 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002741
Kris Giesingc8d882a2014-12-23 13:02:32 -08002742 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002743 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002744 with open(path, 'rb') as f:
2745 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002746 return True
2747 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002748 if not quiet:
2749 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002750 return False
2751 except OSError:
2752 return False
2753
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002754 def _Checkout(self, rev, quiet=False):
2755 cmd = ['checkout']
2756 if quiet:
2757 cmd.append('-q')
2758 cmd.append(rev)
2759 cmd.append('--')
2760 if GitCommand(self, cmd).Wait() != 0:
2761 if self._allrefs:
2762 raise GitError('%s checkout %s ' % (self.name, rev))
2763
Anthony King7bdac712014-07-16 12:56:40 +01002764 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002765 cmd = ['cherry-pick']
2766 cmd.append(rev)
2767 cmd.append('--')
2768 if GitCommand(self, cmd).Wait() != 0:
2769 if self._allrefs:
2770 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2771
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302772 def _LsRemote(self, refs):
2773 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302774 p = GitCommand(self, cmd, capture_stdout=True)
2775 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002776 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302777 return None
2778
Anthony King7bdac712014-07-16 12:56:40 +01002779 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002780 cmd = ['revert']
2781 cmd.append('--no-edit')
2782 cmd.append(rev)
2783 cmd.append('--')
2784 if GitCommand(self, cmd).Wait() != 0:
2785 if self._allrefs:
2786 raise GitError('%s revert %s ' % (self.name, rev))
2787
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002788 def _ResetHard(self, rev, quiet=True):
2789 cmd = ['reset', '--hard']
2790 if quiet:
2791 cmd.append('-q')
2792 cmd.append(rev)
2793 if GitCommand(self, cmd).Wait() != 0:
2794 raise GitError('%s reset --hard %s ' % (self.name, rev))
2795
Martin Kellye4e94d22017-03-21 16:05:12 -07002796 def _SyncSubmodules(self, quiet=True):
2797 cmd = ['submodule', 'update', '--init', '--recursive']
2798 if quiet:
2799 cmd.append('-q')
2800 if GitCommand(self, cmd).Wait() != 0:
2801 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2802
Anthony King7bdac712014-07-16 12:56:40 +01002803 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002804 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002805 if onto is not None:
2806 cmd.extend(['--onto', onto])
2807 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002808 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002809 raise GitError('%s rebase %s ' % (self.name, upstream))
2810
Pierre Tardy3d125942012-05-04 12:18:12 +02002811 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002812 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002813 if ffonly:
2814 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002815 if GitCommand(self, cmd).Wait() != 0:
2816 raise GitError('%s merge %s ' % (self.name, head))
2817
David Pursehousee8ace262020-02-13 12:41:15 +09002818 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002819 init_git_dir = not os.path.exists(self.gitdir)
2820 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002821 try:
2822 # Initialize the bare repository, which contains all of the objects.
2823 if init_obj_dir:
2824 os.makedirs(self.objdir)
2825 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002826
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002827 # Enable per-worktree config file support if possible. This is more a
2828 # nice-to-have feature for users rather than a hard requirement.
2829 if self.use_git_worktrees and git_require((2, 19, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002830 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002831
Kevin Degib1a07b82015-07-27 13:33:43 -06002832 # If we have a separate directory to hold refs, initialize it as well.
2833 if self.objdir != self.gitdir:
2834 if init_git_dir:
2835 os.makedirs(self.gitdir)
2836
2837 if init_obj_dir or init_git_dir:
2838 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2839 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002840 try:
2841 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2842 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002843 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002844 print("Retrying clone after deleting %s" %
2845 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002846 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002847 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2848 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002849 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002850 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002851 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2852 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002853 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002854 raise e
2855 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002856
Kevin Degi384b3c52014-10-16 16:02:58 -06002857 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002858 mp = self.manifest.manifestProject
2859 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002860
Kevin Degib1a07b82015-07-27 13:33:43 -06002861 if ref_dir or mirror_git:
2862 if not mirror_git:
2863 mirror_git = os.path.join(ref_dir, self.name + '.git')
2864 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2865 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002866 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2867 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002868
Kevin Degib1a07b82015-07-27 13:33:43 -06002869 if os.path.exists(mirror_git):
2870 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002871 elif os.path.exists(repo_git):
2872 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002873 elif os.path.exists(worktrees_git):
2874 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002875 else:
2876 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002877
Kevin Degib1a07b82015-07-27 13:33:43 -06002878 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002879 if not os.path.isabs(ref_dir):
2880 # The alternate directory is relative to the object database.
2881 ref_dir = os.path.relpath(ref_dir,
2882 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002883 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2884 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002885
David Pursehousee8ace262020-02-13 12:41:15 +09002886 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002887
2888 m = self.manifest.manifestProject.config
2889 for key in ['user.name', 'user.email']:
2890 if m.Has(key, include_defaults=False):
2891 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002892 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002893 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002894 if self.manifest.IsMirror:
2895 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002896 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002897 self.config.SetString('core.bare', None)
2898 except Exception:
2899 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002900 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002901 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002902 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002903 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002904
David Pursehousee8ace262020-02-13 12:41:15 +09002905 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002906 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002907 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002908
David Pursehousee8ace262020-02-13 12:41:15 +09002909 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002910 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002911 if not os.path.exists(hooks):
2912 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002913 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002914 name = os.path.basename(stock_hook)
2915
Victor Boivie65e0f352011-04-18 11:23:29 +02002916 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002917 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002918 # Don't install a Gerrit Code Review hook if this
2919 # project does not appear to use it for reviews.
2920 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002921 # Since the manifest project is one of those, but also
2922 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002923 continue
2924
2925 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002926 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002927 continue
2928 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002929 # If the files are the same, we'll leave it alone. We create symlinks
2930 # below by default but fallback to hardlinks if the OS blocks them.
2931 # So if we're here, it's probably because we made a hardlink below.
2932 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002933 if not quiet:
2934 _warn("%s: Not replacing locally modified %s hook",
2935 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002936 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002937 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002938 platform_utils.symlink(
2939 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002940 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002941 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002942 try:
2943 os.link(stock_hook, dst)
2944 except OSError:
2945 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002946 else:
2947 raise
2948
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002949 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002950 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002951 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002952 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002953 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002954 remote.review = self.remote.review
2955 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002956
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002957 if self.worktree:
2958 remote.ResetFetch(mirror=False)
2959 else:
2960 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002961 remote.Save()
2962
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002963 def _InitMRef(self):
2964 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002965 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002967 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002968 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002969
2970 def _InitAnyMRef(self, ref):
2971 cur = self.bare_ref.symref(ref)
2972
2973 if self.revisionId:
2974 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2975 msg = 'manifest set to %s' % self.revisionId
2976 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002977 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002978 else:
2979 remote = self.GetRemote(self.remote.name)
2980 dst = remote.ToLocal(self.revisionExpr)
2981 if cur != dst:
2982 msg = 'manifest set to %s' % self.revisionExpr
2983 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002984
Kevin Degi384b3c52014-10-16 16:02:58 -06002985 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002986 # Git worktrees don't use symlinks to share at all.
2987 if self.use_git_worktrees:
2988 return
2989
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002990 symlink_files = self.shareable_files[:]
2991 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002992 if share_refs:
2993 symlink_files += self.working_tree_files
2994 symlink_dirs += self.working_tree_dirs
2995 to_symlink = symlink_files + symlink_dirs
2996 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002997 # Try to self-heal a bit in simple cases.
2998 dst_path = os.path.join(destdir, name)
2999 src_path = os.path.join(srcdir, name)
3000
3001 if name in self.working_tree_dirs:
3002 # If the dir is missing under .repo/projects/, create it.
3003 if not os.path.exists(src_path):
3004 os.makedirs(src_path)
3005
3006 elif name in self.working_tree_files:
3007 # If it's a file under the checkout .git/ and the .repo/projects/ has
3008 # nothing, move the file under the .repo/projects/ tree.
3009 if not os.path.exists(src_path) and os.path.isfile(dst_path):
3010 platform_utils.rename(dst_path, src_path)
3011
3012 # If the path exists under the .repo/projects/ and there's no symlink
3013 # under the checkout .git/, recreate the symlink.
3014 if name in self.working_tree_dirs or name in self.working_tree_files:
3015 if os.path.exists(src_path) and not os.path.exists(dst_path):
3016 platform_utils.symlink(
3017 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
3018
3019 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06003020 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05003021 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06003022 # Fail if the links are pointing to the wrong place
3023 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07003024 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07003025 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04003026 'work tree. If you\'re comfortable with the '
3027 'possibility of losing the work tree\'s git metadata,'
3028 ' use `repo sync --force-sync {0}` to '
3029 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06003030
David James8d201162013-10-11 17:03:19 -07003031 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
3032 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
3033
3034 Args:
3035 gitdir: The bare git repository. Must already be initialized.
3036 dotgit: The repository you would like to initialize.
3037 share_refs: If true, |dotgit| will store its refs under |gitdir|.
3038 Only one work tree can store refs under a given |gitdir|.
3039 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
3040 This saves you the effort of initializing |dotgit| yourself.
3041 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07003042 symlink_files = self.shareable_files[:]
3043 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07003044 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06003045 symlink_files += self.working_tree_files
3046 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07003047 to_symlink = symlink_files + symlink_dirs
3048
3049 to_copy = []
3050 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07003051 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07003052
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003053 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07003054 for name in set(to_copy).union(to_symlink):
3055 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003056 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07003057 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07003058
Kevin Degi384b3c52014-10-16 16:02:58 -06003059 if os.path.lexists(dst):
3060 continue
David James8d201162013-10-11 17:03:19 -07003061
3062 # If the source dir doesn't exist, create an empty dir.
3063 if name in symlink_dirs and not os.path.lexists(src):
3064 os.makedirs(src)
3065
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003066 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07003067 platform_utils.symlink(
3068 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003069 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07003070 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003071 shutil.copytree(src, dst)
3072 elif os.path.isfile(src):
3073 shutil.copy(src, dst)
3074
Conley Owens80b87fe2014-05-09 17:13:44 -07003075 # If the source file doesn't exist, ensure the destination
3076 # file doesn't either.
3077 if name in symlink_files and not os.path.lexists(src):
3078 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003079 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003080 except OSError:
3081 pass
3082
David James8d201162013-10-11 17:03:19 -07003083 except OSError as e:
3084 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003085 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003086 else:
3087 raise
3088
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003089 def _InitGitWorktree(self):
3090 """Init the project using git worktrees."""
3091 self.bare_git.worktree('prune')
3092 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3093 self.worktree, self.GetRevisionId())
3094
3095 # Rewrite the internal state files to use relative paths between the
3096 # checkouts & worktrees.
3097 dotgit = os.path.join(self.worktree, '.git')
3098 with open(dotgit, 'r') as fp:
3099 # Figure out the checkout->worktree path.
3100 setting = fp.read()
3101 assert setting.startswith('gitdir:')
3102 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05003103 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
3104 # of file permissions. Delete it and recreate it from scratch to avoid.
3105 platform_utils.remove(dotgit)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003106 # Use relative path from checkout->worktree.
3107 with open(dotgit, 'w') as fp:
3108 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3109 file=fp)
3110 # Use relative path from worktree->checkout.
3111 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3112 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3113
Martin Kellye4e94d22017-03-21 16:05:12 -07003114 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003115 realdotgit = os.path.join(self.worktree, '.git')
3116 tmpdotgit = realdotgit + '.tmp'
3117 init_dotgit = not os.path.exists(realdotgit)
3118 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003119 if self.use_git_worktrees:
3120 self._InitGitWorktree()
3121 self._CopyAndLinkFiles()
3122 return
3123
Mike Frysingerf4545122019-11-11 04:34:16 -05003124 dotgit = tmpdotgit
3125 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3126 os.makedirs(tmpdotgit)
3127 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3128 copy_all=False)
3129 else:
3130 dotgit = realdotgit
3131
Kevin Degib1a07b82015-07-27 13:33:43 -06003132 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003133 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3134 except GitError as e:
3135 if force_sync and not init_dotgit:
3136 try:
3137 platform_utils.rmtree(dotgit)
3138 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003139 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003140 raise e
3141 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142
Mike Frysingerf4545122019-11-11 04:34:16 -05003143 if init_dotgit:
3144 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003145
Mike Frysingerf4545122019-11-11 04:34:16 -05003146 # Now that the .git dir is fully set up, move it to its final home.
3147 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003148
Mike Frysingerf4545122019-11-11 04:34:16 -05003149 # Finish checking out the worktree.
3150 cmd = ['read-tree', '--reset', '-u']
3151 cmd.append('-v')
3152 cmd.append(HEAD)
3153 if GitCommand(self, cmd).Wait() != 0:
3154 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003155
Mike Frysingerf4545122019-11-11 04:34:16 -05003156 if submodules:
3157 self._SyncSubmodules(quiet=True)
3158 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003159
Renaud Paquay788e9622017-01-27 11:41:12 -08003160 def _get_symlink_error_message(self):
3161 if platform_utils.isWindows():
3162 return ('Unable to create symbolic link. Please re-run the command as '
3163 'Administrator, or see '
3164 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3165 'for other options.')
3166 return 'filesystem must support symlinks'
3167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003169 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003170
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003171 def _revlist(self, *args, **kw):
3172 a = []
3173 a.extend(args)
3174 a.append('--')
3175 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003176
3177 @property
3178 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003179 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003180
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003181 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003182 """Get logs between two revisions of this project."""
3183 comp = '..'
3184 if rev1:
3185 revs = [rev1]
3186 if rev2:
3187 revs.extend([comp, rev2])
3188 cmd = ['log', ''.join(revs)]
3189 out = DiffColoring(self.config)
3190 if out.is_on and color:
3191 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003192 if pretty_format is not None:
3193 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003194 if oneline:
3195 cmd.append('--oneline')
3196
3197 try:
3198 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3199 if log.Wait() == 0:
3200 return log.stdout
3201 except GitError:
3202 # worktree may not exist if groups changed for example. In that case,
3203 # try in gitdir instead.
3204 if not os.path.exists(self.worktree):
3205 return self.bare_git.log(*cmd[1:])
3206 else:
3207 raise
3208 return None
3209
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003210 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3211 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003212 """Get the list of logs from this revision to given revisionId"""
3213 logs = {}
3214 selfId = self.GetRevisionId(self._allrefs)
3215 toId = toProject.GetRevisionId(toProject._allrefs)
3216
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003217 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3218 pretty_format=pretty_format)
3219 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3220 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003221 return logs
3222
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003223 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003224
David James8d201162013-10-11 17:03:19 -07003225 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003226 self._project = project
3227 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003228 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003229
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003230 def LsOthers(self):
3231 p = GitCommand(self._project,
3232 ['ls-files',
3233 '-z',
3234 '--others',
3235 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003236 bare=False,
David James8d201162013-10-11 17:03:19 -07003237 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003238 capture_stdout=True,
3239 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003240 if p.Wait() == 0:
3241 out = p.stdout
3242 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003243 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003244 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003245 return []
3246
3247 def DiffZ(self, name, *args):
3248 cmd = [name]
3249 cmd.append('-z')
Eli Ribble7b4f0192019-05-02 18:21:42 -07003250 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003251 cmd.extend(args)
3252 p = GitCommand(self._project,
3253 cmd,
David James8d201162013-10-11 17:03:19 -07003254 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003255 bare=False,
3256 capture_stdout=True,
3257 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003258 try:
3259 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003260 if not hasattr(out, 'encode'):
3261 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003262 r = {}
3263 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003264 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003265 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003266 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003267 info = next(out)
3268 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003269 except StopIteration:
3270 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003271
3272 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003273
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003274 def __init__(self, path, omode, nmode, oid, nid, state):
3275 self.path = path
3276 self.src_path = None
3277 self.old_mode = omode
3278 self.new_mode = nmode
3279 self.old_id = oid
3280 self.new_id = nid
3281
3282 if len(state) == 1:
3283 self.status = state
3284 self.level = None
3285 else:
3286 self.status = state[:1]
3287 self.level = state[1:]
3288 while self.level.startswith('0'):
3289 self.level = self.level[1:]
3290
3291 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003292 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003293 if info.status in ('R', 'C'):
3294 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003295 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003296 r[info.path] = info
3297 return r
3298 finally:
3299 p.Wait()
3300
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003301 def GetDotgitPath(self, subpath=None):
3302 """Return the full path to the .git dir.
3303
3304 As a convenience, append |subpath| if provided.
3305 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003306 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003307 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003308 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003309 dotgit = os.path.join(self._project.worktree, '.git')
3310 if os.path.isfile(dotgit):
3311 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3312 with open(dotgit) as fp:
3313 setting = fp.read()
3314 assert setting.startswith('gitdir:')
3315 gitdir = setting.split(':', 1)[1].strip()
3316 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3317
3318 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3319
3320 def GetHead(self):
3321 """Return the ref that HEAD points to."""
3322 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003323 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003324 with open(path) as fd:
3325 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003326 except IOError as e:
3327 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003328 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303329 line = line.decode()
3330 except AttributeError:
3331 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003332 if line.startswith('ref: '):
3333 return line[5:-1]
3334 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003335
3336 def SetHead(self, ref, message=None):
3337 cmdv = []
3338 if message is not None:
3339 cmdv.extend(['-m', message])
3340 cmdv.append(HEAD)
3341 cmdv.append(ref)
3342 self.symbolic_ref(*cmdv)
3343
3344 def DetachHead(self, new, message=None):
3345 cmdv = ['--no-deref']
3346 if message is not None:
3347 cmdv.extend(['-m', message])
3348 cmdv.append(HEAD)
3349 cmdv.append(new)
3350 self.update_ref(*cmdv)
3351
3352 def UpdateRef(self, name, new, old=None,
3353 message=None,
3354 detach=False):
3355 cmdv = []
3356 if message is not None:
3357 cmdv.extend(['-m', message])
3358 if detach:
3359 cmdv.append('--no-deref')
3360 cmdv.append(name)
3361 cmdv.append(new)
3362 if old is not None:
3363 cmdv.append(old)
3364 self.update_ref(*cmdv)
3365
3366 def DeleteRef(self, name, old=None):
3367 if not old:
3368 old = self.rev_parse(name)
3369 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003370 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003371
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003372 def rev_list(self, *args, **kw):
3373 if 'format' in kw:
3374 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3375 else:
3376 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003377 cmdv.extend(args)
3378 p = GitCommand(self._project,
3379 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003380 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003381 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003382 capture_stdout=True,
3383 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003384 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003385 raise GitError('%s rev-list %s: %s' %
3386 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003387 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003388
3389 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003390 """Allow arbitrary git commands using pythonic syntax.
3391
3392 This allows you to do things like:
3393 git_obj.rev_parse('HEAD')
3394
3395 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3396 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003397 Any other positional arguments will be passed to the git command, and the
3398 following keyword arguments are supported:
3399 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003400
3401 Args:
3402 name: The name of the git command to call. Any '_' characters will
3403 be replaced with '-'.
3404
3405 Returns:
3406 A callable object that will try to call git with the named command.
3407 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003408 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003409
Dave Borowitz091f8932012-10-23 17:01:04 -07003410 def runner(*args, **kwargs):
3411 cmdv = []
3412 config = kwargs.pop('config', None)
3413 for k in kwargs:
3414 raise TypeError('%s() got an unexpected keyword argument %r'
3415 % (name, k))
3416 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303417 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003418 cmdv.append('-c')
3419 cmdv.append('%s=%s' % (k, v))
3420 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003421 cmdv.extend(args)
3422 p = GitCommand(self._project,
3423 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003424 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003425 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003426 capture_stdout=True,
3427 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003428 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003429 raise GitError('%s %s: %s' %
3430 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003431 r = p.stdout
3432 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3433 return r[:-1]
3434 return r
3435 return runner
3436
3437
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003438class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003439
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003440 def __str__(self):
3441 return 'prior sync failed; rebase still in progress'
3442
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003443
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003444class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003445
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003446 def __str__(self):
3447 return 'contains uncommitted changes'
3448
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003449
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003450class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003451
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003452 def __init__(self, project, text):
3453 self.project = project
3454 self.text = text
3455
3456 def Print(self, syncbuf):
3457 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3458 syncbuf.out.nl()
3459
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003460
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003461class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003462
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003463 def __init__(self, project, why):
3464 self.project = project
3465 self.why = why
3466
3467 def Print(self, syncbuf):
3468 syncbuf.out.fail('error: %s/: %s',
3469 self.project.relpath,
3470 str(self.why))
3471 syncbuf.out.nl()
3472
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003473
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003474class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003475
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003476 def __init__(self, project, action):
3477 self.project = project
3478 self.action = action
3479
3480 def Run(self, syncbuf):
3481 out = syncbuf.out
3482 out.project('project %s/', self.project.relpath)
3483 out.nl()
3484 try:
3485 self.action()
3486 out.nl()
3487 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003488 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003489 out.nl()
3490 return False
3491
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003492
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003493class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003494
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003495 def __init__(self, config):
3496 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003497 self.project = self.printer('header', attr='bold')
3498 self.info = self.printer('info')
3499 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003500
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003501
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003502class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003503
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003504 def __init__(self, config, detach_head=False):
3505 self._messages = []
3506 self._failures = []
3507 self._later_queue1 = []
3508 self._later_queue2 = []
3509
3510 self.out = _SyncColoring(config)
3511 self.out.redirect(sys.stderr)
3512
3513 self.detach_head = detach_head
3514 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003515 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003516
3517 def info(self, project, fmt, *args):
3518 self._messages.append(_InfoMessage(project, fmt % args))
3519
3520 def fail(self, project, err=None):
3521 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003522 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003523
3524 def later1(self, project, what):
3525 self._later_queue1.append(_Later(project, what))
3526
3527 def later2(self, project, what):
3528 self._later_queue2.append(_Later(project, what))
3529
3530 def Finish(self):
3531 self._PrintMessages()
3532 self._RunLater()
3533 self._PrintMessages()
3534 return self.clean
3535
David Rileye0684ad2017-04-05 00:02:59 -07003536 def Recently(self):
3537 recent_clean = self.recent_clean
3538 self.recent_clean = True
3539 return recent_clean
3540
3541 def _MarkUnclean(self):
3542 self.clean = False
3543 self.recent_clean = False
3544
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003545 def _RunLater(self):
3546 for q in ['_later_queue1', '_later_queue2']:
3547 if not self._RunQueue(q):
3548 return
3549
3550 def _RunQueue(self, queue):
3551 for m in getattr(self, queue):
3552 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003553 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003554 return False
3555 setattr(self, queue, [])
3556 return True
3557
3558 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003559 if self._messages or self._failures:
3560 if os.isatty(2):
3561 self.out.write(progress.CSI_ERASE_LINE)
3562 self.out.write('\r')
3563
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003564 for m in self._messages:
3565 m.Print(self)
3566 for m in self._failures:
3567 m.Print(self)
3568
3569 self._messages = []
3570 self._failures = []
3571
3572
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003573class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003574
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003575 """A special project housed under .repo.
3576 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003578 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003579 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003580 manifest=manifest,
3581 name=name,
3582 gitdir=gitdir,
3583 objdir=gitdir,
3584 worktree=worktree,
3585 remote=RemoteSpec('origin'),
3586 relpath='.repo/%s' % name,
3587 revisionExpr='refs/heads/master',
3588 revisionId=None,
3589 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003590
3591 def PreSync(self):
3592 if self.Exists:
3593 cb = self.CurrentBranch
3594 if cb:
3595 base = self.GetBranch(cb).merge
3596 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003597 self.revisionExpr = base
3598 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003599
Martin Kelly224a31a2017-07-10 14:46:25 -07003600 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003601 """ Prepare MetaProject for manifest branch switch
3602 """
3603
3604 # detach and delete manifest branch, allowing a new
3605 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003606 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003607 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003608 syncbuf.Finish()
3609
3610 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003611 ['update-ref', '-d', 'refs/heads/default'],
3612 capture_stdout=True,
3613 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003614
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003615 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003616 def LastFetch(self):
3617 try:
3618 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3619 return os.path.getmtime(fh)
3620 except OSError:
3621 return 0
3622
3623 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003624 def HasChanges(self):
3625 """Has the remote received new commits not yet checked out?
3626 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003627 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003628 return False
3629
David Pursehouse8a68ff92012-09-24 12:15:13 +09003630 all_refs = self.bare_ref.all
3631 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003632 head = self.work_git.GetHead()
3633 if head.startswith(R_HEADS):
3634 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003635 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003636 except KeyError:
3637 head = None
3638
3639 if revid == head:
3640 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003641 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003642 return True
3643 return False