blob: 66a4b3be45e23635ce17d92e63c07b73f7a5da00 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Mike Frysinger3164d402019-11-11 05:40:22 -050061 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500204 labels=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000205 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200206 private=False,
Vadim Bendeburybd8f6582018-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,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500217 labels=labels,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000218 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700220 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200221 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200222 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800223 validate_certs=validate_certs,
224 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700226 def GetPublishedRefs(self):
227 refs = {}
228 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229 self.branch.remote.SshReviewUrl(self.project.UserEmail),
230 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700231 for line in output.split('\n'):
232 try:
233 (sha, ref) = line.split()
234 refs[sha] = ref
235 except ValueError:
236 pass
237
238 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700242
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 def __init__(self, config):
244 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100245 self.project = self.printer('header', attr='bold')
246 self.branch = self.printer('header', attr='bold')
247 self.nobranch = self.printer('nobranch', fg='red')
248 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
Anthony King7bdac712014-07-16 12:56:40 +0100250 self.added = self.printer('added', fg='green')
251 self.changed = self.printer('changed', fg='red')
252 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253
254
255class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700256
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257 def __init__(self, config):
258 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100259 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400260 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
Anthony King7bdac712014-07-16 12:56:40 +0100263class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700264
James W. Mills24c13082012-04-12 15:04:13 -0500265 def __init__(self, name, value, keep):
266 self.name = name
267 self.value = value
268 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700270
Mike Frysingere6a202f2019-08-02 15:57:57 -0400271def _SafeExpandPath(base, subpath, skipfinal=False):
272 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700273
Mike Frysingere6a202f2019-08-02 15:57:57 -0400274 We make sure no intermediate symlinks are traversed, and that the final path
275 is not a special file (e.g. not a socket or fifo).
276
277 NB: We rely on a number of paths already being filtered out while parsing the
278 manifest. See the validation logic in manifest_xml.py for more details.
279 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500280 # Split up the path by its components. We can't use os.path.sep exclusively
281 # as some platforms (like Windows) will convert / to \ and that bypasses all
282 # our constructed logic here. Especially since manifest authors only use
283 # / in their paths.
284 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
285 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400286 if skipfinal:
287 # Whether the caller handles the final component itself.
288 finalpart = components.pop()
289
290 path = base
291 for part in components:
292 if part in {'.', '..'}:
293 raise ManifestInvalidPathError(
294 '%s: "%s" not allowed in paths' % (subpath, part))
295
296 path = os.path.join(path, part)
297 if platform_utils.islink(path):
298 raise ManifestInvalidPathError(
299 '%s: traversing symlinks not allow' % (path,))
300
301 if os.path.exists(path):
302 if not os.path.isfile(path) and not platform_utils.isdir(path):
303 raise ManifestInvalidPathError(
304 '%s: only regular files & directories allowed' % (path,))
305
306 if skipfinal:
307 path = os.path.join(path, finalpart)
308
309 return path
310
311
312class _CopyFile(object):
313 """Container for <copyfile> manifest element."""
314
315 def __init__(self, git_worktree, src, topdir, dest):
316 """Register a <copyfile> request.
317
318 Args:
319 git_worktree: Absolute path to the git project checkout.
320 src: Relative path under |git_worktree| of file to read.
321 topdir: Absolute path to the top of the repo client checkout.
322 dest: Relative path under |topdir| of file to write.
323 """
324 self.git_worktree = git_worktree
325 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700326 self.src = src
327 self.dest = dest
328
329 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400330 src = _SafeExpandPath(self.git_worktree, self.src)
331 dest = _SafeExpandPath(self.topdir, self.dest)
332
333 if platform_utils.isdir(src):
334 raise ManifestInvalidPathError(
335 '%s: copying from directory not supported' % (self.src,))
336 if platform_utils.isdir(dest):
337 raise ManifestInvalidPathError(
338 '%s: copying to directory not allowed' % (self.dest,))
339
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340 # copy file if it does not exist or is out of date
341 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
342 try:
343 # remove existing file first, since it might be read-only
344 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800345 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400346 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200347 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700348 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200349 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350 shutil.copy(src, dest)
351 # make the file read-only
352 mode = os.stat(dest)[stat.ST_MODE]
353 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
354 os.chmod(dest, mode)
355 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700356 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700358
Anthony King7bdac712014-07-16 12:56:40 +0100359class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400360 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700361
Mike Frysingere6a202f2019-08-02 15:57:57 -0400362 def __init__(self, git_worktree, src, topdir, dest):
363 """Register a <linkfile> request.
364
365 Args:
366 git_worktree: Absolute path to the git project checkout.
367 src: Target of symlink relative to path under |git_worktree|.
368 topdir: Absolute path to the top of the repo client checkout.
369 dest: Relative path under |topdir| of symlink to create.
370 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700371 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400372 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500373 self.src = src
374 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500375
Wink Saville4c426ef2015-06-03 08:05:17 -0700376 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700378 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500379 try:
380 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800381 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800382 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500383 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700384 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700385 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500386 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700387 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500388 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700389 _error('Cannot link file %s to %s', relSrc, absDest)
390
391 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400392 """Link the self.src & self.dest paths.
393
394 Handles wild cards on the src linking all of the files in the source in to
395 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700396 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500397 # Some people use src="." to create stable links to projects. Lets allow
398 # that but reject all other uses of "." to keep things simple.
399 if self.src == '.':
400 src = self.git_worktree
401 else:
402 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403
404 if os.path.exists(src):
405 # Entity exists so just a simple one to one link operation.
406 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
407 # dest & src are absolute paths at this point. Make sure the target of
408 # the symlink is relative in the context of the repo client checkout.
409 relpath = os.path.relpath(src, os.path.dirname(dest))
410 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400412 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700413 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400414 if os.path.exists(dest) and not platform_utils.isdir(dest):
415 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700416 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400417 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700418 # Create a releative path from source dir to destination dir
419 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400420 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700421
422 # Get the source file name
423 srcFile = os.path.basename(absSrcFile)
424
425 # Now form the final full paths to srcFile. They will be
426 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400427 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700428 relSrc = os.path.join(relSrcDir, srcFile)
429 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500430
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700431
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700432class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700433
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700434 def __init__(self,
435 name,
Anthony King7bdac712014-07-16 12:56:40 +0100436 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700437 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100438 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700439 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700440 orig_name=None,
441 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700442 self.name = name
443 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700444 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700445 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100446 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700447 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700448 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700449
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700450
Doug Anderson37282b42011-03-04 11:54:18 -0800451class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700452
Doug Anderson37282b42011-03-04 11:54:18 -0800453 """A RepoHook contains information about a script to run as a hook.
454
455 Hooks are used to run a python script before running an upload (for instance,
456 to run presubmit checks). Eventually, we may have hooks for other actions.
457
458 This shouldn't be confused with files in the 'repo/hooks' directory. Those
459 files are copied into each '.git/hooks' folder for each project. Repo-level
460 hooks are associated instead with repo actions.
461
462 Hooks are always python. When a hook is run, we will load the hook into the
463 interpreter and execute its main() function.
464 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700465
Doug Anderson37282b42011-03-04 11:54:18 -0800466 def __init__(self,
467 hook_type,
468 hooks_project,
469 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400470 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800471 abort_if_user_denies=False):
472 """RepoHook constructor.
473
474 Params:
475 hook_type: A string representing the type of hook. This is also used
476 to figure out the name of the file containing the hook. For
477 example: 'pre-upload'.
478 hooks_project: The project containing the repo hooks. If you have a
479 manifest, this is manifest.repo_hooks_project. OK if this is None,
480 which will make the hook a no-op.
481 topdir: Repo's top directory (the one containing the .repo directory).
482 Scripts will run with CWD as this directory. If you have a manifest,
483 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400484 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800485 abort_if_user_denies: If True, we'll throw a HookError() if the user
486 doesn't allow us to run the hook.
487 """
488 self._hook_type = hook_type
489 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400490 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800491 self._topdir = topdir
492 self._abort_if_user_denies = abort_if_user_denies
493
494 # Store the full path to the script for convenience.
495 if self._hooks_project:
496 self._script_fullpath = os.path.join(self._hooks_project.worktree,
497 self._hook_type + '.py')
498 else:
499 self._script_fullpath = None
500
501 def _GetHash(self):
502 """Return a hash of the contents of the hooks directory.
503
504 We'll just use git to do this. This hash has the property that if anything
505 changes in the directory we will return a different has.
506
507 SECURITY CONSIDERATION:
508 This hash only represents the contents of files in the hook directory, not
509 any other files imported or called by hooks. Changes to imported files
510 can change the script behavior without affecting the hash.
511
512 Returns:
513 A string representing the hash. This will always be ASCII so that it can
514 be printed to the user easily.
515 """
516 assert self._hooks_project, "Must have hooks to calculate their hash."
517
518 # We will use the work_git object rather than just calling GetRevisionId().
519 # That gives us a hash of the latest checked in version of the files that
520 # the user will actually be executing. Specifically, GetRevisionId()
521 # doesn't appear to change even if a user checks out a different version
522 # of the hooks repo (via git checkout) nor if a user commits their own revs.
523 #
524 # NOTE: Local (non-committed) changes will not be factored into this hash.
525 # I think this is OK, since we're really only worried about warning the user
526 # about upstream changes.
527 return self._hooks_project.work_git.rev_parse('HEAD')
528
529 def _GetMustVerb(self):
530 """Return 'must' if the hook is required; 'should' if not."""
531 if self._abort_if_user_denies:
532 return 'must'
533 else:
534 return 'should'
535
536 def _CheckForHookApproval(self):
537 """Check to see whether this hook has been approved.
538
Mike Frysinger40252c22016-08-15 21:23:44 -0400539 We'll accept approval of manifest URLs if they're using secure transports.
540 This way the user can say they trust the manifest hoster. For insecure
541 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800542
543 Note that we ask permission for each individual hook even though we use
544 the hash of all hooks when detecting changes. We'd like the user to be
545 able to approve / deny each hook individually. We only use the hash of all
546 hooks because there is no other easy way to detect changes to local imports.
547
548 Returns:
549 True if this hook is approved to run; False otherwise.
550
551 Raises:
552 HookError: Raised if the user doesn't approve and abort_if_user_denies
553 was passed to the consturctor.
554 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400555 if self._ManifestUrlHasSecureScheme():
556 return self._CheckForHookApprovalManifest()
557 else:
558 return self._CheckForHookApprovalHash()
559
560 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
561 changed_prompt):
562 """Check for approval for a particular attribute and hook.
563
564 Args:
565 subkey: The git config key under [repo.hooks.<hook_type>] to store the
566 last approved string.
567 new_val: The new value to compare against the last approved one.
568 main_prompt: Message to display to the user to ask for approval.
569 changed_prompt: Message explaining why we're re-asking for approval.
570
571 Returns:
572 True if this hook is approved to run; False otherwise.
573
574 Raises:
575 HookError: Raised if the user doesn't approve and abort_if_user_denies
576 was passed to the consturctor.
577 """
Doug Anderson37282b42011-03-04 11:54:18 -0800578 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400579 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800580
Mike Frysinger40252c22016-08-15 21:23:44 -0400581 # Get the last value that the user approved for this hook; may be None.
582 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800583
Mike Frysinger40252c22016-08-15 21:23:44 -0400584 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800585 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400586 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800587 # Approval matched. We're done.
588 return True
589 else:
590 # Give the user a reason why we're prompting, since they last told
591 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400592 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800593 else:
594 prompt = ''
595
596 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
597 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400598 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530599 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900600 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800601
602 # User is doing a one-time approval.
603 if response in ('y', 'yes'):
604 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400605 elif response == 'always':
606 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800607 return True
608
609 # For anything else, we'll assume no approval.
610 if self._abort_if_user_denies:
611 raise HookError('You must allow the %s hook or use --no-verify.' %
612 self._hook_type)
613
614 return False
615
Mike Frysinger40252c22016-08-15 21:23:44 -0400616 def _ManifestUrlHasSecureScheme(self):
617 """Check if the URI for the manifest is a secure transport."""
618 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
619 parse_results = urllib.parse.urlparse(self._manifest_url)
620 return parse_results.scheme in secure_schemes
621
622 def _CheckForHookApprovalManifest(self):
623 """Check whether the user has approved this manifest host.
624
625 Returns:
626 True if this hook is approved to run; False otherwise.
627 """
628 return self._CheckForHookApprovalHelper(
629 'approvedmanifest',
630 self._manifest_url,
631 'Run hook scripts from %s' % (self._manifest_url,),
632 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
633
634 def _CheckForHookApprovalHash(self):
635 """Check whether the user has approved the hooks repo.
636
637 Returns:
638 True if this hook is approved to run; False otherwise.
639 """
640 prompt = ('Repo %s run the script:\n'
641 ' %s\n'
642 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700643 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400644 return self._CheckForHookApprovalHelper(
645 'approvedhash',
646 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700647 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400648 'Scripts have changed since %s was allowed.' % (self._hook_type,))
649
Mike Frysingerf7c51602019-06-18 17:23:39 -0400650 @staticmethod
651 def _ExtractInterpFromShebang(data):
652 """Extract the interpreter used in the shebang.
653
654 Try to locate the interpreter the script is using (ignoring `env`).
655
656 Args:
657 data: The file content of the script.
658
659 Returns:
660 The basename of the main script interpreter, or None if a shebang is not
661 used or could not be parsed out.
662 """
663 firstline = data.splitlines()[:1]
664 if not firstline:
665 return None
666
667 # The format here can be tricky.
668 shebang = firstline[0].strip()
669 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
670 if not m:
671 return None
672
673 # If the using `env`, find the target program.
674 interp = m.group(1)
675 if os.path.basename(interp) == 'env':
676 interp = m.group(2)
677
678 return interp
679
680 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
681 """Execute the hook script through |interp|.
682
683 Note: Support for this feature should be dropped ~Jun 2021.
684
685 Args:
686 interp: The Python program to run.
687 context: Basic Python context to execute the hook inside.
688 kwargs: Arbitrary arguments to pass to the hook script.
689
690 Raises:
691 HookError: When the hooks failed for any reason.
692 """
693 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
694 script = """
695import json, os, sys
696path = '''%(path)s'''
697kwargs = json.loads('''%(kwargs)s''')
698context = json.loads('''%(context)s''')
699sys.path.insert(0, os.path.dirname(path))
700data = open(path).read()
701exec(compile(data, path, 'exec'), context)
702context['main'](**kwargs)
703""" % {
704 'path': self._script_fullpath,
705 'kwargs': json.dumps(kwargs),
706 'context': json.dumps(context),
707 }
708
709 # We pass the script via stdin to avoid OS argv limits. It also makes
710 # unhandled exception tracebacks less verbose/confusing for users.
711 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
712 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
713 proc.communicate(input=script.encode('utf-8'))
714 if proc.returncode:
715 raise HookError('Failed to run %s hook.' % (self._hook_type,))
716
717 def _ExecuteHookViaImport(self, data, context, **kwargs):
718 """Execute the hook code in |data| directly.
719
720 Args:
721 data: The code of the hook to execute.
722 context: Basic Python context to execute the hook inside.
723 kwargs: Arbitrary arguments to pass to the hook script.
724
725 Raises:
726 HookError: When the hooks failed for any reason.
727 """
728 # Exec, storing global context in the context dict. We catch exceptions
729 # and convert to a HookError w/ just the failing traceback.
730 try:
731 exec(compile(data, self._script_fullpath, 'exec'), context)
732 except Exception:
733 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
734 (traceback.format_exc(), self._hook_type))
735
736 # Running the script should have defined a main() function.
737 if 'main' not in context:
738 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
739
740 # Call the main function in the hook. If the hook should cause the
741 # build to fail, it will raise an Exception. We'll catch that convert
742 # to a HookError w/ just the failing traceback.
743 try:
744 context['main'](**kwargs)
745 except Exception:
746 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
747 'above.' % (traceback.format_exc(), self._hook_type))
748
Doug Anderson37282b42011-03-04 11:54:18 -0800749 def _ExecuteHook(self, **kwargs):
750 """Actually execute the given hook.
751
752 This will run the hook's 'main' function in our python interpreter.
753
754 Args:
755 kwargs: Keyword arguments to pass to the hook. These are often specific
756 to the hook type. For instance, pre-upload hooks will contain
757 a project_list.
758 """
759 # Keep sys.path and CWD stashed away so that we can always restore them
760 # upon function exit.
761 orig_path = os.getcwd()
762 orig_syspath = sys.path
763
764 try:
765 # Always run hooks with CWD as topdir.
766 os.chdir(self._topdir)
767
768 # Put the hook dir as the first item of sys.path so hooks can do
769 # relative imports. We want to replace the repo dir as [0] so
770 # hooks can't import repo files.
771 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
772
Mike Frysingerf7c51602019-06-18 17:23:39 -0400773 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500774 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800775
Doug Anderson37282b42011-03-04 11:54:18 -0800776 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
777 # We don't actually want hooks to define their main with this argument--
778 # it's there to remind them that their hook should always take **kwargs.
779 # For instance, a pre-upload hook should be defined like:
780 # def main(project_list, **kwargs):
781 #
782 # This allows us to later expand the API without breaking old hooks.
783 kwargs = kwargs.copy()
784 kwargs['hook_should_take_kwargs'] = True
785
Mike Frysingerf7c51602019-06-18 17:23:39 -0400786 # See what version of python the hook has been written against.
787 data = open(self._script_fullpath).read()
788 interp = self._ExtractInterpFromShebang(data)
789 reexec = False
790 if interp:
791 prog = os.path.basename(interp)
792 if prog.startswith('python2') and sys.version_info.major != 2:
793 reexec = True
794 elif prog.startswith('python3') and sys.version_info.major == 2:
795 reexec = True
796
797 # Attempt to execute the hooks through the requested version of Python.
798 if reexec:
799 try:
800 self._ExecuteHookViaReexec(interp, context, **kwargs)
801 except OSError as e:
802 if e.errno == errno.ENOENT:
803 # We couldn't find the interpreter, so fallback to importing.
804 reexec = False
805 else:
806 raise
807
808 # Run the hook by importing directly.
809 if not reexec:
810 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800811 finally:
812 # Restore sys.path and CWD.
813 sys.path = orig_syspath
814 os.chdir(orig_path)
815
816 def Run(self, user_allows_all_hooks, **kwargs):
817 """Run the hook.
818
819 If the hook doesn't exist (because there is no hooks project or because
820 this particular hook is not enabled), this is a no-op.
821
822 Args:
823 user_allows_all_hooks: If True, we will never prompt about running the
824 hook--we'll just assume it's OK to run it.
825 kwargs: Keyword arguments to pass to the hook. These are often specific
826 to the hook type. For instance, pre-upload hooks will contain
827 a project_list.
828
829 Raises:
830 HookError: If there was a problem finding the hook or the user declined
831 to run a required hook (from _CheckForHookApproval).
832 """
833 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700834 if ((not self._hooks_project) or (self._hook_type not in
835 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800836 return
837
838 # Bail with a nice error if we can't find the hook.
839 if not os.path.isfile(self._script_fullpath):
840 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
841
842 # Make sure the user is OK with running the hook.
843 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
844 return
845
846 # Run the hook with the same version of python we're using.
847 self._ExecuteHook(**kwargs)
848
849
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700850class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600851 # These objects can be shared between several working trees.
852 shareable_files = ['description', 'info']
853 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
854 # These objects can only be used by a single working tree.
855 working_tree_files = ['config', 'packed-refs', 'shallow']
856 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700857
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 def __init__(self,
859 manifest,
860 name,
861 remote,
862 gitdir,
David James8d201162013-10-11 17:03:19 -0700863 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700864 worktree,
865 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700866 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800867 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100868 rebase=True,
869 groups=None,
870 sync_c=False,
871 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900872 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100873 clone_depth=None,
874 upstream=None,
875 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500876 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100877 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900878 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700879 optimized_fetch=False,
880 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800881 """Init a Project object.
882
883 Args:
884 manifest: The XmlManifest object.
885 name: The `name` attribute of manifest.xml's project element.
886 remote: RemoteSpec object specifying its remote's properties.
887 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700888 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800889 worktree: Absolute path of git working tree.
890 relpath: Relative path of git working tree to repo's top directory.
891 revisionExpr: The `revision` attribute of manifest.xml's project element.
892 revisionId: git commit id for checking out.
893 rebase: The `rebase` attribute of manifest.xml's project element.
894 groups: The `groups` attribute of manifest.xml's project element.
895 sync_c: The `sync-c` attribute of manifest.xml's project element.
896 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900897 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800898 upstream: The `upstream` attribute of manifest.xml's project element.
899 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500900 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800901 is_derived: False if the project was explicitly defined in the manifest;
902 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400903 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900904 optimized_fetch: If True, when a project is set to a sha1 revision, only
905 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700906 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800907 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700908 self.manifest = manifest
909 self.name = name
910 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800911 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700912 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800913 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700914 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800915 else:
916 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700917 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700918 self.revisionExpr = revisionExpr
919
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700920 if revisionId is None \
921 and revisionExpr \
922 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700923 self.revisionId = revisionExpr
924 else:
925 self.revisionId = revisionId
926
Mike Pontillod3153822012-02-28 11:53:24 -0800927 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700928 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700929 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800930 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900931 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900932 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700933 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800934 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500935 # NB: Do not use this setting in __init__ to change behavior so that the
936 # manifest.git checkout can inspect & change it after instantiating. See
937 # the XmlManifest init code for more info.
938 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800939 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900940 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800941 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800942
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700944 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500945 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500946 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700947 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
948 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700949
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800950 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700951 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800952 else:
953 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700954 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700955 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700956 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400957 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700958 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700959
Doug Anderson37282b42011-03-04 11:54:18 -0800960 # This will be filled in if a project is later identified to be the
961 # project containing repo hooks.
962 self.enabled_repo_hooks = []
963
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700964 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800965 def Derived(self):
966 return self.is_derived
967
968 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700970 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700971
972 @property
973 def CurrentBranch(self):
974 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400975
976 The branch name omits the 'refs/heads/' prefix.
977 None is returned if the project is on a detached HEAD, or if the work_git is
978 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400980 try:
981 b = self.work_git.GetHead()
982 except NoManifestException:
983 # If the local checkout is in a bad state, don't barf. Let the callers
984 # process this like the head is unreadable.
985 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700986 if b.startswith(R_HEADS):
987 return b[len(R_HEADS):]
988 return None
989
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700990 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500991 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
992 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
993 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200994
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995 def IsDirty(self, consider_untracked=True):
996 """Is the working directory modified in some way?
997 """
998 self.work_git.update_index('-q',
999 '--unmerged',
1000 '--ignore-missing',
1001 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +09001002 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 return True
1004 if self.work_git.DiffZ('diff-files'):
1005 return True
1006 if consider_untracked and self.work_git.LsOthers():
1007 return True
1008 return False
1009
1010 _userident_name = None
1011 _userident_email = None
1012
1013 @property
1014 def UserName(self):
1015 """Obtain the user's personal name.
1016 """
1017 if self._userident_name is None:
1018 self._LoadUserIdentity()
1019 return self._userident_name
1020
1021 @property
1022 def UserEmail(self):
1023 """Obtain the user's email address. This is very likely
1024 to be their Gerrit login.
1025 """
1026 if self._userident_email is None:
1027 self._LoadUserIdentity()
1028 return self._userident_email
1029
1030 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001031 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1032 m = re.compile("^(.*) <([^>]*)> ").match(u)
1033 if m:
1034 self._userident_name = m.group(1)
1035 self._userident_email = m.group(2)
1036 else:
1037 self._userident_name = ''
1038 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039
1040 def GetRemote(self, name):
1041 """Get the configuration for a single remote.
1042 """
1043 return self.config.GetRemote(name)
1044
1045 def GetBranch(self, name):
1046 """Get the configuration for a single branch.
1047 """
1048 return self.config.GetBranch(name)
1049
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001050 def GetBranches(self):
1051 """Get all existing local branches.
1052 """
1053 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001054 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001055 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001056
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301057 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001058 if name.startswith(R_HEADS):
1059 name = name[len(R_HEADS):]
1060 b = self.GetBranch(name)
1061 b.current = name == current
1062 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001063 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001064 heads[name] = b
1065
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301066 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001067 if name.startswith(R_PUB):
1068 name = name[len(R_PUB):]
1069 b = heads.get(name)
1070 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001071 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001072
1073 return heads
1074
Colin Cross5acde752012-03-28 20:15:45 -07001075 def MatchesGroups(self, manifest_groups):
1076 """Returns true if the manifest groups specified at init should cause
1077 this project to be synced.
1078 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001079 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001080
1081 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001082 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001083 manifest_groups: "-group1,group2"
1084 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001085
1086 The special manifest group "default" will match any project that
1087 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001088 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001089 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001090 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001091 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001092 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001093
Conley Owens971de8e2012-04-16 10:36:08 -07001094 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001095 for group in expanded_manifest_groups:
1096 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001097 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001098 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001099 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001100
Conley Owens971de8e2012-04-16 10:36:08 -07001101 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001103# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001104 def UncommitedFiles(self, get_all=True):
1105 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001107 Args:
1108 get_all: a boolean, if True - get information about all different
1109 uncommitted files. If False - return as soon as any kind of
1110 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001111 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001112 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001113 self.work_git.update_index('-q',
1114 '--unmerged',
1115 '--ignore-missing',
1116 '--refresh')
1117 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001118 details.append("rebase in progress")
1119 if not get_all:
1120 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001121
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001122 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1123 if changes:
1124 details.extend(changes)
1125 if not get_all:
1126 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001127
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001128 changes = self.work_git.DiffZ('diff-files').keys()
1129 if changes:
1130 details.extend(changes)
1131 if not get_all:
1132 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001133
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001134 changes = self.work_git.LsOthers()
1135 if changes:
1136 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001137
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001138 return details
1139
1140 def HasChanges(self):
1141 """Returns true if there are uncommitted changes.
1142 """
1143 if self.UncommitedFiles(get_all=False):
1144 return True
1145 else:
1146 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001147
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001148 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001150
1151 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001152 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001153 quiet: If True then only print the project name. Do not print
1154 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001156 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001157 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001158 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001159 print(file=output_redir)
1160 print('project %s/' % self.relpath, file=output_redir)
1161 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 return
1163
1164 self.work_git.update_index('-q',
1165 '--unmerged',
1166 '--ignore-missing',
1167 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001168 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1170 df = self.work_git.DiffZ('diff-files')
1171 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001172 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001173 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174
1175 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001176 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001177 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001178 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001180 if quiet:
1181 out.nl()
1182 return 'DIRTY'
1183
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184 branch = self.CurrentBranch
1185 if branch is None:
1186 out.nobranch('(*** NO BRANCH ***)')
1187 else:
1188 out.branch('branch %s', branch)
1189 out.nl()
1190
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001191 if rb:
1192 out.important('prior sync failed; rebase still in progress')
1193 out.nl()
1194
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 paths = list()
1196 paths.extend(di.keys())
1197 paths.extend(df.keys())
1198 paths.extend(do)
1199
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301200 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001201 try:
1202 i = di[p]
1203 except KeyError:
1204 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001206 try:
1207 f = df[p]
1208 except KeyError:
1209 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001210
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001211 if i:
1212 i_status = i.status.upper()
1213 else:
1214 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001216 if f:
1217 f_status = f.status.lower()
1218 else:
1219 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220
1221 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001222 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001223 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 else:
1225 line = ' %s%s\t%s' % (i_status, f_status, p)
1226
1227 if i and not f:
1228 out.added('%s', line)
1229 elif (i and f) or (not i and f):
1230 out.changed('%s', line)
1231 elif not i and not f:
1232 out.untracked('%s', line)
1233 else:
1234 out.write('%s', line)
1235 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001236
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001237 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001238
pelyad67872d2012-03-28 14:49:58 +03001239 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240 """Prints the status of the repository to stdout.
1241 """
1242 out = DiffColoring(self.config)
1243 cmd = ['diff']
1244 if out.is_on:
1245 cmd.append('--color')
1246 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001247 if absolute_paths:
1248 cmd.append('--src-prefix=a/%s/' % self.relpath)
1249 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001250 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001251 try:
1252 p = GitCommand(self,
1253 cmd,
1254 capture_stdout=True,
1255 capture_stderr=True)
1256 except GitError as e:
1257 out.nl()
1258 out.project('project %s/' % self.relpath)
1259 out.nl()
1260 out.fail('%s', str(e))
1261 out.nl()
1262 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263 has_diff = False
1264 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001265 if not hasattr(line, 'encode'):
1266 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 if not has_diff:
1268 out.nl()
1269 out.project('project %s/' % self.relpath)
1270 out.nl()
1271 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001272 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001273 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001275# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001276 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277 """Was the branch published (uploaded) for code review?
1278 If so, returns the SHA-1 hash of the last published
1279 state for the branch.
1280 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001281 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001282 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001283 try:
1284 return self.bare_git.rev_parse(key)
1285 except GitError:
1286 return None
1287 else:
1288 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001289 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001290 except KeyError:
1291 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
David Pursehouse8a68ff92012-09-24 12:15:13 +09001293 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 """Prunes any stale published refs.
1295 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001296 if all_refs is None:
1297 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 heads = set()
1299 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301300 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 if name.startswith(R_HEADS):
1302 heads.add(name)
1303 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301306 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 n = name[len(R_PUB):]
1308 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001309 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001311 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 """List any branches which can be uploaded for review.
1313 """
1314 heads = {}
1315 pubed = {}
1316
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301317 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001319 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001321 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
1323 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301324 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001325 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001327 if selected_branch and branch != selected_branch:
1328 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001330 rb = self.GetUploadableBranch(branch)
1331 if rb:
1332 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 return ready
1334
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001335 def GetUploadableBranch(self, branch_name):
1336 """Get a single uploadable branch, or None.
1337 """
1338 branch = self.GetBranch(branch_name)
1339 base = branch.LocalMerge
1340 if branch.LocalMerge:
1341 rb = ReviewableBranch(self, branch, base)
1342 if rb.commits:
1343 return rb
1344 return None
1345
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001346 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001347 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001348 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001349 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001350 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001351 labels=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001352 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001353 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001354 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001355 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001356 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001357 validate_certs=True,
1358 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 """Uploads the named branch for code review.
1360 """
1361 if branch is None:
1362 branch = self.CurrentBranch
1363 if branch is None:
1364 raise GitError('not currently on a branch')
1365
1366 branch = self.GetBranch(branch)
1367 if not branch.LocalMerge:
1368 raise GitError('branch %s does not track a remote' % branch.name)
1369 if not branch.remote.review:
1370 raise GitError('remote %s has no review url' % branch.remote.name)
1371
Bryan Jacobsf609f912013-05-06 13:36:24 -04001372 if dest_branch is None:
1373 dest_branch = self.dest_branch
1374 if dest_branch is None:
1375 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001376 if not dest_branch.startswith(R_HEADS):
1377 dest_branch = R_HEADS + dest_branch
1378
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001379 if not branch.remote.projectname:
1380 branch.remote.projectname = self.name
1381 branch.remote.Save()
1382
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001383 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001384 if url is None:
1385 raise UploadError('review not configured')
1386 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001387 if dryrun:
1388 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001389
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001390 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001391 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001392
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001393 for push_option in (push_options or []):
1394 cmd.append('-o')
1395 cmd.append(push_option)
1396
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001397 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001398
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001399 if dest_branch.startswith(R_HEADS):
1400 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001401
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001402 upload_type = 'for'
1403 if draft:
1404 upload_type = 'drafts'
1405
1406 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1407 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001408 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001409 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001410 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001411 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001412 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001413
David Pursehousef25a3702018-11-14 19:01:22 -08001414 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001415 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001416 if notify:
1417 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001418 if private:
1419 opts += ['private']
1420 if wip:
1421 opts += ['wip']
1422 if opts:
1423 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001424 cmd.append(ref_spec)
1425
Anthony King7bdac712014-07-16 12:56:40 +01001426 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001427 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428
1429 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1430 self.bare_git.UpdateRef(R_PUB + branch.name,
1431 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001432 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001433
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001434# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001435 def _ExtractArchive(self, tarpath, path=None):
1436 """Extract the given tar on its current location
1437
1438 Args:
1439 - tarpath: The path to the actual tar file
1440
1441 """
1442 try:
1443 with tarfile.open(tarpath, 'r') as tar:
1444 tar.extractall(path=path)
1445 return True
1446 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001447 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001448 return False
1449
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001450 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001451 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001452 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001453 is_new=None,
1454 current_branch_only=False,
1455 force_sync=False,
1456 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001457 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001458 archive=False,
1459 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001460 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001461 submodules=False,
1462 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001463 """Perform only the network IO portion of the sync process.
1464 Local working directory/branch state is not affected.
1465 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001466 if archive and not isinstance(self, MetaProject):
1467 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001468 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001469 return False
1470
1471 name = self.relpath.replace('\\', '/')
1472 name = name.replace('/', '_')
1473 tarpath = '%s.tar' % name
1474 topdir = self.manifest.topdir
1475
1476 try:
1477 self._FetchArchive(tarpath, cwd=topdir)
1478 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001479 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001480 return False
1481
1482 # From now on, we only need absolute tarpath
1483 tarpath = os.path.join(topdir, tarpath)
1484
1485 if not self._ExtractArchive(tarpath, path=topdir):
1486 return False
1487 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001488 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001489 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001490 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001491 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001492 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001493 if is_new is None:
1494 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001495 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001496 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001497 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001498 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001499 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001500
1501 if is_new:
1502 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1503 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001504 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001505 # This works for both absolute and relative alternate directories.
1506 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001507 except IOError:
1508 alt_dir = None
1509 else:
1510 alt_dir = None
1511
Mike Frysingere50b6a72020-02-19 01:45:48 -05001512 if (clone_bundle
1513 and alt_dir is None
1514 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001515 is_new = False
1516
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001517 if not current_branch_only:
1518 if self.sync_c:
1519 current_branch_only = True
1520 elif not self.manifest._loaded:
1521 # Manifest cannot check defaults until it syncs.
1522 current_branch_only = False
1523 elif self.manifest.default.sync_c:
1524 current_branch_only = True
1525
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001526 if not self.sync_tags:
1527 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001528
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001529 if self.clone_depth:
1530 depth = self.clone_depth
1531 else:
1532 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1533
Mike Frysinger521d01b2020-02-17 01:51:49 -05001534 # See if we can skip the network fetch entirely.
1535 if not (optimized_fetch and
1536 (ID_RE.match(self.revisionExpr) and
1537 self._CheckForImmutableRevision())):
1538 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001539 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1540 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001541 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001542 submodules=submodules, force_sync=force_sync,
1543 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001544 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001545
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001546 mp = self.manifest.manifestProject
1547 dissociate = mp.config.GetBoolean('repo.dissociate')
1548 if dissociate:
1549 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1550 if os.path.exists(alternates_file):
1551 cmd = ['repack', '-a', '-d']
1552 if GitCommand(self, cmd, bare=True).Wait() != 0:
1553 return False
1554 platform_utils.remove(alternates_file)
1555
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001556 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001557 self._InitMRef()
1558 else:
1559 self._InitMirrorHead()
1560 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001561 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001562 except OSError:
1563 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001564 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001565
1566 def PostRepoUpgrade(self):
1567 self._InitHooks()
1568
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001569 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001570 if self.manifest.isGitcClient:
1571 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001572 for copyfile in self.copyfiles:
1573 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001574 for linkfile in self.linkfiles:
1575 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001576
Julien Camperguedd654222014-01-09 16:21:37 +01001577 def GetCommitRevisionId(self):
1578 """Get revisionId of a commit.
1579
1580 Use this method instead of GetRevisionId to get the id of the commit rather
1581 than the id of the current git object (for example, a tag)
1582
1583 """
1584 if not self.revisionExpr.startswith(R_TAGS):
1585 return self.GetRevisionId(self._allrefs)
1586
1587 try:
1588 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1589 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001590 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1591 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001592
David Pursehouse8a68ff92012-09-24 12:15:13 +09001593 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001594 if self.revisionId:
1595 return self.revisionId
1596
1597 rem = self.GetRemote(self.remote.name)
1598 rev = rem.ToLocal(self.revisionExpr)
1599
David Pursehouse8a68ff92012-09-24 12:15:13 +09001600 if all_refs is not None and rev in all_refs:
1601 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001602
1603 try:
1604 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1605 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001606 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1607 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001608
Martin Kellye4e94d22017-03-21 16:05:12 -07001609 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610 """Perform only the local IO portion of the sync process.
1611 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001612 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001613 if not os.path.exists(self.gitdir):
1614 syncbuf.fail(self,
1615 'Cannot checkout %s due to missing network sync; Run '
1616 '`repo sync -n %s` first.' %
1617 (self.name, self.name))
1618 return
1619
Martin Kellye4e94d22017-03-21 16:05:12 -07001620 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001621 all_refs = self.bare_ref.all
1622 self.CleanPublishedCache(all_refs)
1623 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001624
David Pursehouse1d947b32012-10-25 12:23:11 +09001625 def _doff():
1626 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001627 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001628
Martin Kellye4e94d22017-03-21 16:05:12 -07001629 def _dosubmodules():
1630 self._SyncSubmodules(quiet=True)
1631
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001632 head = self.work_git.GetHead()
1633 if head.startswith(R_HEADS):
1634 branch = head[len(R_HEADS):]
1635 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001636 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001637 except KeyError:
1638 head = None
1639 else:
1640 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001642 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643 # Currently on a detached HEAD. The user is assumed to
1644 # not have any local modifications worth worrying about.
1645 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001646 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001647 syncbuf.fail(self, _PriorSyncFailedError())
1648 return
1649
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001650 if head == revid:
1651 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001652 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001653 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001654 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001655 # The copy/linkfile config may have changed.
1656 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001657 return
1658 else:
1659 lost = self._revlist(not_rev(revid), HEAD)
1660 if lost:
1661 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001662
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001664 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001665 if submodules:
1666 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001667 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001668 syncbuf.fail(self, e)
1669 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001670 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001671 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001672
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001673 if head == revid:
1674 # No changes; don't do anything further.
1675 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001676 # The copy/linkfile config may have changed.
1677 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001678 return
1679
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001680 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001682 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001684 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001686 syncbuf.info(self,
1687 "leaving %s; does not track upstream",
1688 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001689 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001690 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001691 if submodules:
1692 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001693 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001694 syncbuf.fail(self, e)
1695 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001696 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001697 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001698
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001699 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001700
1701 # See if we can perform a fast forward merge. This can happen if our
1702 # branch isn't in the exact same state as we last published.
1703 try:
1704 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1705 # Skip the published logic.
1706 pub = False
1707 except GitError:
1708 pub = self.WasPublished(branch.name, all_refs)
1709
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001711 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001712 if not_merged:
1713 if upstream_gain:
1714 # The user has published this branch and some of those
1715 # commits are not yet merged upstream. We do not want
1716 # to rewrite the published commits so we punt.
1717 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001718 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001719 "branch %s is published (but not merged) and is now "
1720 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001721 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001722 elif pub == head:
1723 # All published commits are merged, and thus we are a
1724 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001725 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001726 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001727 if submodules:
1728 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001729 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001730
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001731 # Examine the local commits not in the remote. Find the
1732 # last one attributed to this user, if any.
1733 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001734 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001735 last_mine = None
1736 cnt_mine = 0
1737 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001738 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001739 if committer_email == self.UserEmail:
1740 last_mine = commit_id
1741 cnt_mine += 1
1742
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001743 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001744 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745
1746 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001747 syncbuf.fail(self, _DirtyError())
1748 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001749
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001750 # If the upstream switched on us, warn the user.
1751 #
1752 if branch.merge != self.revisionExpr:
1753 if branch.merge and self.revisionExpr:
1754 syncbuf.info(self,
1755 'manifest switched %s...%s',
1756 branch.merge,
1757 self.revisionExpr)
1758 elif branch.merge:
1759 syncbuf.info(self,
1760 'manifest no longer tracks %s',
1761 branch.merge)
1762
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001763 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001765 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001767 syncbuf.info(self,
1768 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001769 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001771 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001772 if not ID_RE.match(self.revisionExpr):
1773 # in case of manifest sync the revisionExpr might be a SHA1
1774 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001775 if not branch.merge.startswith('refs/'):
1776 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777 branch.Save()
1778
Mike Pontillod3153822012-02-28 11:53:24 -08001779 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001780 def _docopyandlink():
1781 self._CopyAndLinkFiles()
1782
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001783 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001784 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001785 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001786 if submodules:
1787 syncbuf.later2(self, _dosubmodules)
1788 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001789 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001791 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001792 if submodules:
1793 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001794 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001795 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001796 syncbuf.fail(self, e)
1797 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001799 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001800 if submodules:
1801 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001802
Mike Frysingere6a202f2019-08-02 15:57:57 -04001803 def AddCopyFile(self, src, dest, topdir):
1804 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001805
Mike Frysingere6a202f2019-08-02 15:57:57 -04001806 No filesystem changes occur here. Actual copying happens later on.
1807
1808 Paths should have basic validation run on them before being queued.
1809 Further checking will be handled when the actual copy happens.
1810 """
1811 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1812
1813 def AddLinkFile(self, src, dest, topdir):
1814 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1815
1816 No filesystem changes occur here. Actual linking happens later on.
1817
1818 Paths should have basic validation run on them before being queued.
1819 Further checking will be handled when the actual link happens.
1820 """
1821 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001822
James W. Mills24c13082012-04-12 15:04:13 -05001823 def AddAnnotation(self, name, value, keep):
1824 self.annotations.append(_Annotation(name, value, keep))
1825
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001826 def DownloadPatchSet(self, change_id, patch_id):
1827 """Download a single patch set of a single change to FETCH_HEAD.
1828 """
1829 remote = self.GetRemote(self.remote.name)
1830
1831 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001832 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001833 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001834 if GitCommand(self, cmd, bare=True).Wait() != 0:
1835 return None
1836 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001837 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001838 change_id,
1839 patch_id,
1840 self.bare_git.rev_parse('FETCH_HEAD'))
1841
Mike Frysingerc0d18662020-02-19 19:19:18 -05001842 def DeleteWorktree(self, quiet=False, force=False):
1843 """Delete the source checkout and any other housekeeping tasks.
1844
1845 This currently leaves behind the internal .repo/ cache state. This helps
1846 when switching branches or manifest changes get reverted as we don't have
1847 to redownload all the git objects. But we should do some GC at some point.
1848
1849 Args:
1850 quiet: Whether to hide normal messages.
1851 force: Always delete tree even if dirty.
1852
1853 Returns:
1854 True if the worktree was completely cleaned out.
1855 """
1856 if self.IsDirty():
1857 if force:
1858 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1859 (self.relpath,), file=sys.stderr)
1860 else:
1861 print('error: %s: Cannot remove project: uncommitted changes are '
1862 'present.\n' % (self.relpath,), file=sys.stderr)
1863 return False
1864
1865 if not quiet:
1866 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1867
1868 # Unlock and delink from the main worktree. We don't use git's worktree
1869 # remove because it will recursively delete projects -- we handle that
1870 # ourselves below. https://crbug.com/git/48
1871 if self.use_git_worktrees:
1872 needle = platform_utils.realpath(self.gitdir)
1873 # Find the git worktree commondir under .repo/worktrees/.
1874 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1875 assert output.startswith('worktree '), output
1876 commondir = output[9:]
1877 # Walk each of the git worktrees to see where they point.
1878 configs = os.path.join(commondir, 'worktrees')
1879 for name in os.listdir(configs):
1880 gitdir = os.path.join(configs, name, 'gitdir')
1881 with open(gitdir) as fp:
1882 relpath = fp.read().strip()
1883 # Resolve the checkout path and see if it matches this project.
1884 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1885 if fullpath == needle:
1886 platform_utils.rmtree(os.path.join(configs, name))
1887
1888 # Delete the .git directory first, so we're less likely to have a partially
1889 # working git repository around. There shouldn't be any git projects here,
1890 # so rmtree works.
1891
1892 # Try to remove plain files first in case of git worktrees. If this fails
1893 # for any reason, we'll fall back to rmtree, and that'll display errors if
1894 # it can't remove things either.
1895 try:
1896 platform_utils.remove(self.gitdir)
1897 except OSError:
1898 pass
1899 try:
1900 platform_utils.rmtree(self.gitdir)
1901 except OSError as e:
1902 if e.errno != errno.ENOENT:
1903 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1904 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1905 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1906 return False
1907
1908 # Delete everything under the worktree, except for directories that contain
1909 # another git project.
1910 dirs_to_remove = []
1911 failed = False
1912 for root, dirs, files in platform_utils.walk(self.worktree):
1913 for f in files:
1914 path = os.path.join(root, f)
1915 try:
1916 platform_utils.remove(path)
1917 except OSError as e:
1918 if e.errno != errno.ENOENT:
1919 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1920 failed = True
1921 dirs[:] = [d for d in dirs
1922 if not os.path.lexists(os.path.join(root, d, '.git'))]
1923 dirs_to_remove += [os.path.join(root, d) for d in dirs
1924 if os.path.join(root, d) not in dirs_to_remove]
1925 for d in reversed(dirs_to_remove):
1926 if platform_utils.islink(d):
1927 try:
1928 platform_utils.remove(d)
1929 except OSError as e:
1930 if e.errno != errno.ENOENT:
1931 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1932 failed = True
1933 elif not platform_utils.listdir(d):
1934 try:
1935 platform_utils.rmdir(d)
1936 except OSError as e:
1937 if e.errno != errno.ENOENT:
1938 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1939 failed = True
1940 if failed:
1941 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1942 file=sys.stderr)
1943 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1944 return False
1945
1946 # Try deleting parent dirs if they are empty.
1947 path = self.worktree
1948 while path != self.manifest.topdir:
1949 try:
1950 platform_utils.rmdir(path)
1951 except OSError as e:
1952 if e.errno != errno.ENOENT:
1953 break
1954 path = os.path.dirname(path)
1955
1956 return True
1957
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001958# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001959 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001960 """Create a new branch off the manifest's revision.
1961 """
Simran Basib9a1b732015-08-20 12:19:28 -07001962 if not branch_merge:
1963 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001964 head = self.work_git.GetHead()
1965 if head == (R_HEADS + name):
1966 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001967
David Pursehouse8a68ff92012-09-24 12:15:13 +09001968 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001969 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001970 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001971 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001972 capture_stdout=True,
1973 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001974
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001975 branch = self.GetBranch(name)
1976 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001977 branch.merge = branch_merge
1978 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1979 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001980
1981 if revision is None:
1982 revid = self.GetRevisionId(all_refs)
1983 else:
1984 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001985
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001986 if head.startswith(R_HEADS):
1987 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001988 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001989 except KeyError:
1990 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001991 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001992 ref = R_HEADS + name
1993 self.work_git.update_ref(ref, revid)
1994 self.work_git.symbolic_ref(HEAD, ref)
1995 branch.Save()
1996 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001997
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001998 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001999 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01002000 capture_stdout=True,
2001 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002002 branch.Save()
2003 return True
2004 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002005
Wink Saville02d79452009-04-10 13:01:24 -07002006 def CheckoutBranch(self, name):
2007 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002008
2009 Args:
2010 name: The name of the branch to checkout.
2011
2012 Returns:
2013 True if the checkout succeeded; False if it didn't; None if the branch
2014 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002015 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002016 rev = R_HEADS + name
2017 head = self.work_git.GetHead()
2018 if head == rev:
2019 # Already on the branch
2020 #
2021 return True
Wink Saville02d79452009-04-10 13:01:24 -07002022
David Pursehouse8a68ff92012-09-24 12:15:13 +09002023 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002024 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002025 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002026 except KeyError:
2027 # Branch does not exist in this project
2028 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002029 return None
Wink Saville02d79452009-04-10 13:01:24 -07002030
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002031 if head.startswith(R_HEADS):
2032 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002033 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002034 except KeyError:
2035 head = None
2036
2037 if head == revid:
2038 # Same revision; just update HEAD to point to the new
2039 # target branch, but otherwise take no other action.
2040 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05002041 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
2042 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002043 return True
2044
2045 return GitCommand(self,
2046 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002047 capture_stdout=True,
2048 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002049
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002050 def AbandonBranch(self, name):
2051 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002052
2053 Args:
2054 name: The name of the branch to abandon.
2055
2056 Returns:
2057 True if the abandon succeeded; False if it didn't; None if the branch
2058 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002059 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002060 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002061 all_refs = self.bare_ref.all
2062 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002063 # Doesn't exist
2064 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002065
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002066 head = self.work_git.GetHead()
2067 if head == rev:
2068 # We can't destroy the branch while we are sitting
2069 # on it. Switch to a detached HEAD.
2070 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002071 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002072
David Pursehouse8a68ff92012-09-24 12:15:13 +09002073 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002074 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05002075 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002076 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002077 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002078
2079 return GitCommand(self,
2080 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002081 capture_stdout=True,
2082 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002083
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002084 def PruneHeads(self):
2085 """Prune any topic branches already merged into upstream.
2086 """
2087 cb = self.CurrentBranch
2088 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002089 left = self._allrefs
2090 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002091 if name.startswith(R_HEADS):
2092 name = name[len(R_HEADS):]
2093 if cb is None or name != cb:
2094 kill.append(name)
2095
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002096 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002097 if cb is not None \
2098 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002099 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002100 self.work_git.DetachHead(HEAD)
2101 kill.append(cb)
2102
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002103 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002104 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002105
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002106 try:
2107 self.bare_git.DetachHead(rev)
2108
2109 b = ['branch', '-d']
2110 b.extend(kill)
2111 b = GitCommand(self, b, bare=True,
2112 capture_stdout=True,
2113 capture_stderr=True)
2114 b.Wait()
2115 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002116 if ID_RE.match(old):
2117 self.bare_git.DetachHead(old)
2118 else:
2119 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002120 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002121
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002122 for branch in kill:
2123 if (R_HEADS + branch) not in left:
2124 self.CleanPublishedCache()
2125 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002126
2127 if cb and cb not in kill:
2128 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002129 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002130
2131 kept = []
2132 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002133 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 branch = self.GetBranch(branch)
2135 base = branch.LocalMerge
2136 if not base:
2137 base = rev
2138 kept.append(ReviewableBranch(self, branch, base))
2139 return kept
2140
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002141# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002142 def GetRegisteredSubprojects(self):
2143 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002144
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002145 def rec(subprojects):
2146 if not subprojects:
2147 return
2148 result.extend(subprojects)
2149 for p in subprojects:
2150 rec(p.subprojects)
2151 rec(self.subprojects)
2152 return result
2153
2154 def _GetSubmodules(self):
2155 # Unfortunately we cannot call `git submodule status --recursive` here
2156 # because the working tree might not exist yet, and it cannot be used
2157 # without a working tree in its current implementation.
2158
2159 def get_submodules(gitdir, rev):
2160 # Parse .gitmodules for submodule sub_paths and sub_urls
2161 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2162 if not sub_paths:
2163 return []
2164 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2165 # revision of submodule repository
2166 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2167 submodules = []
2168 for sub_path, sub_url in zip(sub_paths, sub_urls):
2169 try:
2170 sub_rev = sub_revs[sub_path]
2171 except KeyError:
2172 # Ignore non-exist submodules
2173 continue
2174 submodules.append((sub_rev, sub_path, sub_url))
2175 return submodules
2176
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002177 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2178 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002179
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002180 def parse_gitmodules(gitdir, rev):
2181 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2182 try:
Anthony King7bdac712014-07-16 12:56:40 +01002183 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2184 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002185 except GitError:
2186 return [], []
2187 if p.Wait() != 0:
2188 return [], []
2189
2190 gitmodules_lines = []
2191 fd, temp_gitmodules_path = tempfile.mkstemp()
2192 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002193 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002194 os.close(fd)
2195 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002196 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2197 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002198 if p.Wait() != 0:
2199 return [], []
2200 gitmodules_lines = p.stdout.split('\n')
2201 except GitError:
2202 return [], []
2203 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002204 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002205
2206 names = set()
2207 paths = {}
2208 urls = {}
2209 for line in gitmodules_lines:
2210 if not line:
2211 continue
2212 m = re_path.match(line)
2213 if m:
2214 names.add(m.group(1))
2215 paths[m.group(1)] = m.group(2)
2216 continue
2217 m = re_url.match(line)
2218 if m:
2219 names.add(m.group(1))
2220 urls[m.group(1)] = m.group(2)
2221 continue
2222 names = sorted(names)
2223 return ([paths.get(name, '') for name in names],
2224 [urls.get(name, '') for name in names])
2225
2226 def git_ls_tree(gitdir, rev, paths):
2227 cmd = ['ls-tree', rev, '--']
2228 cmd.extend(paths)
2229 try:
Anthony King7bdac712014-07-16 12:56:40 +01002230 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2231 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002232 except GitError:
2233 return []
2234 if p.Wait() != 0:
2235 return []
2236 objects = {}
2237 for line in p.stdout.split('\n'):
2238 if not line.strip():
2239 continue
2240 object_rev, object_path = line.split()[2:4]
2241 objects[object_path] = object_rev
2242 return objects
2243
2244 try:
2245 rev = self.GetRevisionId()
2246 except GitError:
2247 return []
2248 return get_submodules(self.gitdir, rev)
2249
2250 def GetDerivedSubprojects(self):
2251 result = []
2252 if not self.Exists:
2253 # If git repo does not exist yet, querying its submodules will
2254 # mess up its states; so return here.
2255 return result
2256 for rev, path, url in self._GetSubmodules():
2257 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002258 relpath, worktree, gitdir, objdir = \
2259 self.manifest.GetSubprojectPaths(self, name, path)
2260 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002261 if project:
2262 result.extend(project.GetDerivedSubprojects())
2263 continue
David James8d201162013-10-11 17:03:19 -07002264
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002265 if url.startswith('..'):
2266 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002267 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002268 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002269 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002270 review=self.remote.review,
2271 revision=self.remote.revision)
2272 subproject = Project(manifest=self.manifest,
2273 name=name,
2274 remote=remote,
2275 gitdir=gitdir,
2276 objdir=objdir,
2277 worktree=worktree,
2278 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002279 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002280 revisionId=rev,
2281 rebase=self.rebase,
2282 groups=self.groups,
2283 sync_c=self.sync_c,
2284 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002285 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002286 parent=self,
2287 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002288 result.append(subproject)
2289 result.extend(subproject.GetDerivedSubprojects())
2290 return result
2291
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002292# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002293 def EnableRepositoryExtension(self, key, value='true', version=1):
2294 """Enable git repository extension |key| with |value|.
2295
2296 Args:
2297 key: The extension to enabled. Omit the "extensions." prefix.
2298 value: The value to use for the extension.
2299 version: The minimum git repository version needed.
2300 """
2301 # Make sure the git repo version is new enough already.
2302 found_version = self.config.GetInt('core.repositoryFormatVersion')
2303 if found_version is None:
2304 found_version = 0
2305 if found_version < version:
2306 self.config.SetString('core.repositoryFormatVersion', str(version))
2307
2308 # Enable the extension!
2309 self.config.SetString('extensions.%s' % (key,), value)
2310
Zac Livingstone4332262017-06-16 08:56:09 -06002311 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002312 try:
2313 # if revision (sha or tag) is not present then following function
2314 # throws an error.
2315 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2316 return True
2317 except GitError:
2318 # There is no such persistent revision. We have to fetch it.
2319 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002320
Julien Campergue335f5ef2013-10-16 11:02:35 +02002321 def _FetchArchive(self, tarpath, cwd=None):
2322 cmd = ['archive', '-v', '-o', tarpath]
2323 cmd.append('--remote=%s' % self.remote.url)
2324 cmd.append('--prefix=%s/' % self.relpath)
2325 cmd.append(self.revisionExpr)
2326
2327 command = GitCommand(self, cmd, cwd=cwd,
2328 capture_stdout=True,
2329 capture_stderr=True)
2330
2331 if command.Wait() != 0:
2332 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2333
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002334 def _RemoteFetch(self, name=None,
2335 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002336 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002337 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002338 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002339 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002340 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002341 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002342 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002343 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002344 force_sync=False,
2345 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002346
2347 is_sha1 = False
2348 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002349 # The depth should not be used when fetching to a mirror because
2350 # it will result in a shallow repository that cannot be cloned or
2351 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002352 # The repo project should also never be synced with partial depth.
2353 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2354 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002355
Shawn Pearce69e04d82014-01-29 12:48:54 -08002356 if depth:
2357 current_branch_only = True
2358
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002359 if ID_RE.match(self.revisionExpr) is not None:
2360 is_sha1 = True
2361
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002362 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002363 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002364 # this is a tag and its sha1 value should never change
2365 tag_name = self.revisionExpr[len(R_TAGS):]
2366
2367 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002368 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002369 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002370 print('Skipped fetching project %s (already have persistent ref)'
2371 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002372 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002373 if is_sha1 and not depth:
2374 # When syncing a specific commit and --depth is not set:
2375 # * if upstream is explicitly specified and is not a sha1, fetch only
2376 # upstream as users expect only upstream to be fetch.
2377 # Note: The commit might not be in upstream in which case the sync
2378 # will fail.
2379 # * otherwise, fetch all branches to make sure we end up with the
2380 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002381 if self.upstream:
2382 current_branch_only = not ID_RE.match(self.upstream)
2383 else:
2384 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002386 if not name:
2387 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002388
2389 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002390 remote = self.GetRemote(name)
2391 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002392 ssh_proxy = True
2393
Shawn O. Pearce88443382010-10-08 10:02:09 +02002394 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002395 if alt_dir and 'objects' == os.path.basename(alt_dir):
2396 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002397 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2398 remote = self.GetRemote(name)
2399
David Pursehouse8a68ff92012-09-24 12:15:13 +09002400 all_refs = self.bare_ref.all
2401 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002402 tmp = set()
2403
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302404 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002405 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002406 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002407 all_refs[r] = ref_id
2408 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002409 continue
2410
David Pursehouse8a68ff92012-09-24 12:15:13 +09002411 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002412 continue
2413
David Pursehouse8a68ff92012-09-24 12:15:13 +09002414 r = 'refs/_alt/%s' % ref_id
2415 all_refs[r] = ref_id
2416 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002417 tmp.add(r)
2418
heping3d7bbc92017-04-12 19:51:47 +08002419 tmp_packed_lines = []
2420 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002421
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302422 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002423 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002424 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002425 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002426 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002427
heping3d7bbc92017-04-12 19:51:47 +08002428 tmp_packed = ''.join(tmp_packed_lines)
2429 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002430 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002431 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002432 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002433
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002434 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002435
Xin Li745be2e2019-06-03 11:24:30 -07002436 if clone_filter:
2437 git_require((2, 19, 0), fail=True, msg='partial clones')
2438 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002439 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002440
Conley Owensf97e8382015-01-21 11:12:46 -08002441 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002442 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002443 else:
2444 # If this repo has shallow objects, then we don't know which refs have
2445 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2446 # do this with projects that don't have shallow objects, since it is less
2447 # efficient.
2448 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2449 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002450
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002451 if quiet:
2452 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002453 if not self.worktree:
2454 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002455 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002456
Mike Frysingere57f1142019-03-18 21:27:54 -04002457 if force_sync:
2458 cmd.append('--force')
2459
David Pursehouse74cfd272015-10-14 10:50:15 +09002460 if prune:
2461 cmd.append('--prune')
2462
Martin Kellye4e94d22017-03-21 16:05:12 -07002463 if submodules:
2464 cmd.append('--recurse-submodules=on-demand')
2465
Kuang-che Wu6856f982019-11-25 12:37:55 +08002466 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002467 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002468 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002469 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002470 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002471 spec.append('tag')
2472 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002473
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302474 if self.manifest.IsMirror and not current_branch_only:
2475 branch = None
2476 else:
2477 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002478 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002479 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002480 # Shallow checkout of a specific commit, fetch from that commit and not
2481 # the heads only as the commit might be deeper in the history.
2482 spec.append(branch)
2483 else:
2484 if is_sha1:
2485 branch = self.upstream
2486 if branch is not None and branch.strip():
2487 if not branch.startswith('refs/'):
2488 branch = R_HEADS + branch
2489 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2490
2491 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2492 # whole repo.
2493 if self.manifest.IsMirror and not spec:
2494 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2495
2496 # If using depth then we should not get all the tags since they may
2497 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002498 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002499 cmd.append('--no-tags')
2500 else:
2501 cmd.append('--tags')
2502 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2503
Conley Owens80b87fe2014-05-09 17:13:44 -07002504 cmd.extend(spec)
2505
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002506 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002507 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002508 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2509 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002510 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002511 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002512 ok = True
2513 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002514 # If needed, run the 'git remote prune' the first time through the loop
2515 elif (not _i and
2516 "error:" in gitcmd.stderr and
2517 "git remote prune" in gitcmd.stderr):
2518 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002519 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002520 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002521 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002522 break
2523 continue
Brian Harring14a66742012-09-28 20:21:57 -07002524 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002525 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2526 # in sha1 mode, we just tried sync'ing from the upstream field; it
2527 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002528 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002529 elif ret < 0:
2530 # Git died with a signal, exit immediately
2531 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002532 if not verbose:
2533 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002534 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002535
2536 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002537 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002538 if old_packed != '':
2539 _lwrite(packed_refs, old_packed)
2540 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002541 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002542 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002543
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002544 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002545 # We just synced the upstream given branch; verify we
2546 # got what we wanted, else trigger a second run of all
2547 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002548 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002549 # Sync the current branch only with depth set to None.
2550 # We always pass depth=None down to avoid infinite recursion.
2551 return self._RemoteFetch(
2552 name=name, quiet=quiet, verbose=verbose,
2553 current_branch_only=current_branch_only and depth,
2554 initial=False, alt_dir=alt_dir,
2555 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002556
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002557 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002558
Mike Frysingere50b6a72020-02-19 01:45:48 -05002559 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002560 if initial and \
2561 (self.manifest.manifestProject.config.GetString('repo.depth') or
2562 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002563 return False
2564
2565 remote = self.GetRemote(self.remote.name)
2566 bundle_url = remote.url + '/clone.bundle'
2567 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002568 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2569 'persistent-http',
2570 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002571 return False
2572
2573 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2574 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2575
2576 exist_dst = os.path.exists(bundle_dst)
2577 exist_tmp = os.path.exists(bundle_tmp)
2578
2579 if not initial and not exist_dst and not exist_tmp:
2580 return False
2581
2582 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002583 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2584 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002585 if not exist_dst:
2586 return False
2587
2588 cmd = ['fetch']
2589 if quiet:
2590 cmd.append('--quiet')
2591 if not self.worktree:
2592 cmd.append('--update-head-ok')
2593 cmd.append(bundle_dst)
2594 for f in remote.fetch:
2595 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002596 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002597
2598 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002599 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002600 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002601 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002602 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002603 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002604
Mike Frysingere50b6a72020-02-19 01:45:48 -05002605 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002606 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002607 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002608
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002609 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002610 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002611 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002612 if os.path.exists(tmpPath):
2613 size = os.stat(tmpPath).st_size
2614 if size >= 1024:
2615 cmd += ['--continue-at', '%d' % (size,)]
2616 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002617 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002618 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002619 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002620 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002621 if proxy:
2622 cmd += ['--proxy', proxy]
2623 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2624 cmd += ['--proxy', os.environ['http_proxy']]
2625 if srcUrl.startswith('persistent-https'):
2626 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2627 elif srcUrl.startswith('persistent-http'):
2628 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002629 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002630
Dave Borowitz137d0132015-01-02 11:12:54 -08002631 if IsTrace():
2632 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002633 if verbose:
2634 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2635 stdout = None if verbose else subprocess.PIPE
2636 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002637 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002638 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002639 except OSError:
2640 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002641
Mike Frysingere50b6a72020-02-19 01:45:48 -05002642 (output, _) = proc.communicate()
2643 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002644
Dave Borowitz137d0132015-01-02 11:12:54 -08002645 if curlret == 22:
2646 # From curl man page:
2647 # 22: HTTP page not retrieved. The requested url was not found or
2648 # returned another error with the HTTP error code being 400 or above.
2649 # This return code only appears if -f, --fail is used.
2650 if not quiet:
2651 print("Server does not provide clone.bundle; ignoring.",
2652 file=sys.stderr)
2653 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002654 elif curlret and not verbose and output:
2655 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002656
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002657 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002658 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002659 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002660 return True
2661 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002662 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002663 return False
2664 else:
2665 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002666
Kris Giesingc8d882a2014-12-23 13:02:32 -08002667 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002668 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002669 with open(path, 'rb') as f:
2670 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002671 return True
2672 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002673 if not quiet:
2674 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002675 return False
2676 except OSError:
2677 return False
2678
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002679 def _Checkout(self, rev, quiet=False):
2680 cmd = ['checkout']
2681 if quiet:
2682 cmd.append('-q')
2683 cmd.append(rev)
2684 cmd.append('--')
2685 if GitCommand(self, cmd).Wait() != 0:
2686 if self._allrefs:
2687 raise GitError('%s checkout %s ' % (self.name, rev))
2688
Anthony King7bdac712014-07-16 12:56:40 +01002689 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002690 cmd = ['cherry-pick']
2691 cmd.append(rev)
2692 cmd.append('--')
2693 if GitCommand(self, cmd).Wait() != 0:
2694 if self._allrefs:
2695 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2696
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302697 def _LsRemote(self, refs):
2698 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302699 p = GitCommand(self, cmd, capture_stdout=True)
2700 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002701 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302702 return None
2703
Anthony King7bdac712014-07-16 12:56:40 +01002704 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002705 cmd = ['revert']
2706 cmd.append('--no-edit')
2707 cmd.append(rev)
2708 cmd.append('--')
2709 if GitCommand(self, cmd).Wait() != 0:
2710 if self._allrefs:
2711 raise GitError('%s revert %s ' % (self.name, rev))
2712
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002713 def _ResetHard(self, rev, quiet=True):
2714 cmd = ['reset', '--hard']
2715 if quiet:
2716 cmd.append('-q')
2717 cmd.append(rev)
2718 if GitCommand(self, cmd).Wait() != 0:
2719 raise GitError('%s reset --hard %s ' % (self.name, rev))
2720
Martin Kellye4e94d22017-03-21 16:05:12 -07002721 def _SyncSubmodules(self, quiet=True):
2722 cmd = ['submodule', 'update', '--init', '--recursive']
2723 if quiet:
2724 cmd.append('-q')
2725 if GitCommand(self, cmd).Wait() != 0:
2726 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2727
Anthony King7bdac712014-07-16 12:56:40 +01002728 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002729 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002730 if onto is not None:
2731 cmd.extend(['--onto', onto])
2732 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002733 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002734 raise GitError('%s rebase %s ' % (self.name, upstream))
2735
Pierre Tardy3d125942012-05-04 12:18:12 +02002736 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002737 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002738 if ffonly:
2739 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002740 if GitCommand(self, cmd).Wait() != 0:
2741 raise GitError('%s merge %s ' % (self.name, head))
2742
David Pursehousee8ace262020-02-13 12:41:15 +09002743 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002744 init_git_dir = not os.path.exists(self.gitdir)
2745 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002746 try:
2747 # Initialize the bare repository, which contains all of the objects.
2748 if init_obj_dir:
2749 os.makedirs(self.objdir)
2750 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002751
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002752 # Enable per-worktree config file support if possible. This is more a
2753 # nice-to-have feature for users rather than a hard requirement.
2754 if self.use_git_worktrees and git_require((2, 19, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002755 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002756
Kevin Degib1a07b82015-07-27 13:33:43 -06002757 # If we have a separate directory to hold refs, initialize it as well.
2758 if self.objdir != self.gitdir:
2759 if init_git_dir:
2760 os.makedirs(self.gitdir)
2761
2762 if init_obj_dir or init_git_dir:
2763 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2764 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002765 try:
2766 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2767 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002768 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002769 print("Retrying clone after deleting %s" %
2770 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002771 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002772 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2773 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002774 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002775 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002776 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2777 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002778 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002779 raise e
2780 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002781
Kevin Degi384b3c52014-10-16 16:02:58 -06002782 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002783 mp = self.manifest.manifestProject
2784 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002785
Kevin Degib1a07b82015-07-27 13:33:43 -06002786 if ref_dir or mirror_git:
2787 if not mirror_git:
2788 mirror_git = os.path.join(ref_dir, self.name + '.git')
2789 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2790 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002791 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2792 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002793
Kevin Degib1a07b82015-07-27 13:33:43 -06002794 if os.path.exists(mirror_git):
2795 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002796 elif os.path.exists(repo_git):
2797 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002798 elif os.path.exists(worktrees_git):
2799 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002800 else:
2801 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002802
Kevin Degib1a07b82015-07-27 13:33:43 -06002803 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002804 if not os.path.isabs(ref_dir):
2805 # The alternate directory is relative to the object database.
2806 ref_dir = os.path.relpath(ref_dir,
2807 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002808 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2809 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002810
David Pursehousee8ace262020-02-13 12:41:15 +09002811 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002812
2813 m = self.manifest.manifestProject.config
2814 for key in ['user.name', 'user.email']:
2815 if m.Has(key, include_defaults=False):
2816 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002817 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002818 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002819 if self.manifest.IsMirror:
2820 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002821 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002822 self.config.SetString('core.bare', None)
2823 except Exception:
2824 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002825 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002826 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002827 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002828 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002829
David Pursehousee8ace262020-02-13 12:41:15 +09002830 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002831 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002832 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002833
David Pursehousee8ace262020-02-13 12:41:15 +09002834 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002835 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002836 if not os.path.exists(hooks):
2837 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002838 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002839 name = os.path.basename(stock_hook)
2840
Victor Boivie65e0f352011-04-18 11:23:29 +02002841 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002842 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002843 # Don't install a Gerrit Code Review hook if this
2844 # project does not appear to use it for reviews.
2845 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002846 # Since the manifest project is one of those, but also
2847 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002848 continue
2849
2850 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002851 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002852 continue
2853 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002854 # If the files are the same, we'll leave it alone. We create symlinks
2855 # below by default but fallback to hardlinks if the OS blocks them.
2856 # So if we're here, it's probably because we made a hardlink below.
2857 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002858 if not quiet:
2859 _warn("%s: Not replacing locally modified %s hook",
2860 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002861 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002862 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002863 platform_utils.symlink(
2864 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002865 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002866 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002867 try:
2868 os.link(stock_hook, dst)
2869 except OSError:
2870 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002871 else:
2872 raise
2873
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002874 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002875 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002876 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002877 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002878 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002879 remote.review = self.remote.review
2880 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002882 if self.worktree:
2883 remote.ResetFetch(mirror=False)
2884 else:
2885 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002886 remote.Save()
2887
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002888 def _InitMRef(self):
2889 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002890 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002891
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002892 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002893 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002894
2895 def _InitAnyMRef(self, ref):
2896 cur = self.bare_ref.symref(ref)
2897
2898 if self.revisionId:
2899 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2900 msg = 'manifest set to %s' % self.revisionId
2901 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002902 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002903 else:
2904 remote = self.GetRemote(self.remote.name)
2905 dst = remote.ToLocal(self.revisionExpr)
2906 if cur != dst:
2907 msg = 'manifest set to %s' % self.revisionExpr
2908 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002909
Kevin Degi384b3c52014-10-16 16:02:58 -06002910 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002911 # Git worktrees don't use symlinks to share at all.
2912 if self.use_git_worktrees:
2913 return
2914
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002915 symlink_files = self.shareable_files[:]
2916 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002917 if share_refs:
2918 symlink_files += self.working_tree_files
2919 symlink_dirs += self.working_tree_dirs
2920 to_symlink = symlink_files + symlink_dirs
2921 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002922 # Try to self-heal a bit in simple cases.
2923 dst_path = os.path.join(destdir, name)
2924 src_path = os.path.join(srcdir, name)
2925
2926 if name in self.working_tree_dirs:
2927 # If the dir is missing under .repo/projects/, create it.
2928 if not os.path.exists(src_path):
2929 os.makedirs(src_path)
2930
2931 elif name in self.working_tree_files:
2932 # If it's a file under the checkout .git/ and the .repo/projects/ has
2933 # nothing, move the file under the .repo/projects/ tree.
2934 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2935 platform_utils.rename(dst_path, src_path)
2936
2937 # If the path exists under the .repo/projects/ and there's no symlink
2938 # under the checkout .git/, recreate the symlink.
2939 if name in self.working_tree_dirs or name in self.working_tree_files:
2940 if os.path.exists(src_path) and not os.path.exists(dst_path):
2941 platform_utils.symlink(
2942 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2943
2944 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002945 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002946 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002947 # Fail if the links are pointing to the wrong place
2948 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002949 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002950 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002951 'work tree. If you\'re comfortable with the '
2952 'possibility of losing the work tree\'s git metadata,'
2953 ' use `repo sync --force-sync {0}` to '
2954 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002955
David James8d201162013-10-11 17:03:19 -07002956 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2957 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2958
2959 Args:
2960 gitdir: The bare git repository. Must already be initialized.
2961 dotgit: The repository you would like to initialize.
2962 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2963 Only one work tree can store refs under a given |gitdir|.
2964 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2965 This saves you the effort of initializing |dotgit| yourself.
2966 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002967 symlink_files = self.shareable_files[:]
2968 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002969 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002970 symlink_files += self.working_tree_files
2971 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002972 to_symlink = symlink_files + symlink_dirs
2973
2974 to_copy = []
2975 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002976 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002977
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002978 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002979 for name in set(to_copy).union(to_symlink):
2980 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002981 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002982 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002983
Kevin Degi384b3c52014-10-16 16:02:58 -06002984 if os.path.lexists(dst):
2985 continue
David James8d201162013-10-11 17:03:19 -07002986
2987 # If the source dir doesn't exist, create an empty dir.
2988 if name in symlink_dirs and not os.path.lexists(src):
2989 os.makedirs(src)
2990
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002991 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002992 platform_utils.symlink(
2993 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002994 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002995 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002996 shutil.copytree(src, dst)
2997 elif os.path.isfile(src):
2998 shutil.copy(src, dst)
2999
Conley Owens80b87fe2014-05-09 17:13:44 -07003000 # If the source file doesn't exist, ensure the destination
3001 # file doesn't either.
3002 if name in symlink_files and not os.path.lexists(src):
3003 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003004 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003005 except OSError:
3006 pass
3007
David James8d201162013-10-11 17:03:19 -07003008 except OSError as e:
3009 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003010 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003011 else:
3012 raise
3013
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003014 def _InitGitWorktree(self):
3015 """Init the project using git worktrees."""
3016 self.bare_git.worktree('prune')
3017 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3018 self.worktree, self.GetRevisionId())
3019
3020 # Rewrite the internal state files to use relative paths between the
3021 # checkouts & worktrees.
3022 dotgit = os.path.join(self.worktree, '.git')
3023 with open(dotgit, 'r') as fp:
3024 # Figure out the checkout->worktree path.
3025 setting = fp.read()
3026 assert setting.startswith('gitdir:')
3027 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05003028 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
3029 # of file permissions. Delete it and recreate it from scratch to avoid.
3030 platform_utils.remove(dotgit)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003031 # Use relative path from checkout->worktree.
3032 with open(dotgit, 'w') as fp:
3033 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3034 file=fp)
3035 # Use relative path from worktree->checkout.
3036 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3037 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3038
Martin Kellye4e94d22017-03-21 16:05:12 -07003039 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003040 realdotgit = os.path.join(self.worktree, '.git')
3041 tmpdotgit = realdotgit + '.tmp'
3042 init_dotgit = not os.path.exists(realdotgit)
3043 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003044 if self.use_git_worktrees:
3045 self._InitGitWorktree()
3046 self._CopyAndLinkFiles()
3047 return
3048
Mike Frysingerf4545122019-11-11 04:34:16 -05003049 dotgit = tmpdotgit
3050 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3051 os.makedirs(tmpdotgit)
3052 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3053 copy_all=False)
3054 else:
3055 dotgit = realdotgit
3056
Kevin Degib1a07b82015-07-27 13:33:43 -06003057 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003058 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3059 except GitError as e:
3060 if force_sync and not init_dotgit:
3061 try:
3062 platform_utils.rmtree(dotgit)
3063 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003064 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003065 raise e
3066 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003067
Mike Frysingerf4545122019-11-11 04:34:16 -05003068 if init_dotgit:
3069 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003070
Mike Frysingerf4545122019-11-11 04:34:16 -05003071 # Now that the .git dir is fully set up, move it to its final home.
3072 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003073
Mike Frysingerf4545122019-11-11 04:34:16 -05003074 # Finish checking out the worktree.
3075 cmd = ['read-tree', '--reset', '-u']
3076 cmd.append('-v')
3077 cmd.append(HEAD)
3078 if GitCommand(self, cmd).Wait() != 0:
3079 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003080
Mike Frysingerf4545122019-11-11 04:34:16 -05003081 if submodules:
3082 self._SyncSubmodules(quiet=True)
3083 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003084
Renaud Paquay788e9622017-01-27 11:41:12 -08003085 def _get_symlink_error_message(self):
3086 if platform_utils.isWindows():
3087 return ('Unable to create symbolic link. Please re-run the command as '
3088 'Administrator, or see '
3089 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3090 'for other options.')
3091 return 'filesystem must support symlinks'
3092
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003093 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003094 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003095
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003096 def _revlist(self, *args, **kw):
3097 a = []
3098 a.extend(args)
3099 a.append('--')
3100 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003101
3102 @property
3103 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003104 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003105
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003106 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003107 """Get logs between two revisions of this project."""
3108 comp = '..'
3109 if rev1:
3110 revs = [rev1]
3111 if rev2:
3112 revs.extend([comp, rev2])
3113 cmd = ['log', ''.join(revs)]
3114 out = DiffColoring(self.config)
3115 if out.is_on and color:
3116 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003117 if pretty_format is not None:
3118 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003119 if oneline:
3120 cmd.append('--oneline')
3121
3122 try:
3123 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3124 if log.Wait() == 0:
3125 return log.stdout
3126 except GitError:
3127 # worktree may not exist if groups changed for example. In that case,
3128 # try in gitdir instead.
3129 if not os.path.exists(self.worktree):
3130 return self.bare_git.log(*cmd[1:])
3131 else:
3132 raise
3133 return None
3134
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003135 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3136 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003137 """Get the list of logs from this revision to given revisionId"""
3138 logs = {}
3139 selfId = self.GetRevisionId(self._allrefs)
3140 toId = toProject.GetRevisionId(toProject._allrefs)
3141
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003142 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3143 pretty_format=pretty_format)
3144 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3145 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003146 return logs
3147
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003148 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003149
David James8d201162013-10-11 17:03:19 -07003150 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003151 self._project = project
3152 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003153 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003154
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003155 def LsOthers(self):
3156 p = GitCommand(self._project,
3157 ['ls-files',
3158 '-z',
3159 '--others',
3160 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003161 bare=False,
David James8d201162013-10-11 17:03:19 -07003162 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003163 capture_stdout=True,
3164 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003165 if p.Wait() == 0:
3166 out = p.stdout
3167 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003168 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003169 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003170 return []
3171
3172 def DiffZ(self, name, *args):
3173 cmd = [name]
3174 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003175 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003176 cmd.extend(args)
3177 p = GitCommand(self._project,
3178 cmd,
David James8d201162013-10-11 17:03:19 -07003179 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003180 bare=False,
3181 capture_stdout=True,
3182 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003183 try:
3184 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003185 if not hasattr(out, 'encode'):
3186 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003187 r = {}
3188 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003189 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003190 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003191 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003192 info = next(out)
3193 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003194 except StopIteration:
3195 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003196
3197 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003198
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003199 def __init__(self, path, omode, nmode, oid, nid, state):
3200 self.path = path
3201 self.src_path = None
3202 self.old_mode = omode
3203 self.new_mode = nmode
3204 self.old_id = oid
3205 self.new_id = nid
3206
3207 if len(state) == 1:
3208 self.status = state
3209 self.level = None
3210 else:
3211 self.status = state[:1]
3212 self.level = state[1:]
3213 while self.level.startswith('0'):
3214 self.level = self.level[1:]
3215
3216 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003217 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003218 if info.status in ('R', 'C'):
3219 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003220 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003221 r[info.path] = info
3222 return r
3223 finally:
3224 p.Wait()
3225
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003226 def GetDotgitPath(self, subpath=None):
3227 """Return the full path to the .git dir.
3228
3229 As a convenience, append |subpath| if provided.
3230 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003231 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003232 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003233 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003234 dotgit = os.path.join(self._project.worktree, '.git')
3235 if os.path.isfile(dotgit):
3236 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3237 with open(dotgit) as fp:
3238 setting = fp.read()
3239 assert setting.startswith('gitdir:')
3240 gitdir = setting.split(':', 1)[1].strip()
3241 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3242
3243 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3244
3245 def GetHead(self):
3246 """Return the ref that HEAD points to."""
3247 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003248 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003249 with open(path) as fd:
3250 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003251 except IOError as e:
3252 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003253 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303254 line = line.decode()
3255 except AttributeError:
3256 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003257 if line.startswith('ref: '):
3258 return line[5:-1]
3259 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003260
3261 def SetHead(self, ref, message=None):
3262 cmdv = []
3263 if message is not None:
3264 cmdv.extend(['-m', message])
3265 cmdv.append(HEAD)
3266 cmdv.append(ref)
3267 self.symbolic_ref(*cmdv)
3268
3269 def DetachHead(self, new, message=None):
3270 cmdv = ['--no-deref']
3271 if message is not None:
3272 cmdv.extend(['-m', message])
3273 cmdv.append(HEAD)
3274 cmdv.append(new)
3275 self.update_ref(*cmdv)
3276
3277 def UpdateRef(self, name, new, old=None,
3278 message=None,
3279 detach=False):
3280 cmdv = []
3281 if message is not None:
3282 cmdv.extend(['-m', message])
3283 if detach:
3284 cmdv.append('--no-deref')
3285 cmdv.append(name)
3286 cmdv.append(new)
3287 if old is not None:
3288 cmdv.append(old)
3289 self.update_ref(*cmdv)
3290
3291 def DeleteRef(self, name, old=None):
3292 if not old:
3293 old = self.rev_parse(name)
3294 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003295 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003296
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003297 def rev_list(self, *args, **kw):
3298 if 'format' in kw:
3299 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3300 else:
3301 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003302 cmdv.extend(args)
3303 p = GitCommand(self._project,
3304 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003305 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003306 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003307 capture_stdout=True,
3308 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003309 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003310 raise GitError('%s rev-list %s: %s' %
3311 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003312 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003313
3314 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003315 """Allow arbitrary git commands using pythonic syntax.
3316
3317 This allows you to do things like:
3318 git_obj.rev_parse('HEAD')
3319
3320 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3321 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003322 Any other positional arguments will be passed to the git command, and the
3323 following keyword arguments are supported:
3324 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003325
3326 Args:
3327 name: The name of the git command to call. Any '_' characters will
3328 be replaced with '-'.
3329
3330 Returns:
3331 A callable object that will try to call git with the named command.
3332 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003333 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003334
Dave Borowitz091f8932012-10-23 17:01:04 -07003335 def runner(*args, **kwargs):
3336 cmdv = []
3337 config = kwargs.pop('config', None)
3338 for k in kwargs:
3339 raise TypeError('%s() got an unexpected keyword argument %r'
3340 % (name, k))
3341 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303342 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003343 cmdv.append('-c')
3344 cmdv.append('%s=%s' % (k, v))
3345 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003346 cmdv.extend(args)
3347 p = GitCommand(self._project,
3348 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003349 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003350 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003351 capture_stdout=True,
3352 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003353 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003354 raise GitError('%s %s: %s' %
3355 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003356 r = p.stdout
3357 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3358 return r[:-1]
3359 return r
3360 return runner
3361
3362
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003363class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003364
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003365 def __str__(self):
3366 return 'prior sync failed; rebase still in progress'
3367
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003368
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003369class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003370
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003371 def __str__(self):
3372 return 'contains uncommitted changes'
3373
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003374
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003375class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003376
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003377 def __init__(self, project, text):
3378 self.project = project
3379 self.text = text
3380
3381 def Print(self, syncbuf):
3382 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3383 syncbuf.out.nl()
3384
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003385
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003386class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003387
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003388 def __init__(self, project, why):
3389 self.project = project
3390 self.why = why
3391
3392 def Print(self, syncbuf):
3393 syncbuf.out.fail('error: %s/: %s',
3394 self.project.relpath,
3395 str(self.why))
3396 syncbuf.out.nl()
3397
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003398
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003399class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003400
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003401 def __init__(self, project, action):
3402 self.project = project
3403 self.action = action
3404
3405 def Run(self, syncbuf):
3406 out = syncbuf.out
3407 out.project('project %s/', self.project.relpath)
3408 out.nl()
3409 try:
3410 self.action()
3411 out.nl()
3412 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003413 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003414 out.nl()
3415 return False
3416
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003417
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003418class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003419
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003420 def __init__(self, config):
3421 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003422 self.project = self.printer('header', attr='bold')
3423 self.info = self.printer('info')
3424 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003425
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003426
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003427class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003428
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003429 def __init__(self, config, detach_head=False):
3430 self._messages = []
3431 self._failures = []
3432 self._later_queue1 = []
3433 self._later_queue2 = []
3434
3435 self.out = _SyncColoring(config)
3436 self.out.redirect(sys.stderr)
3437
3438 self.detach_head = detach_head
3439 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003440 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003441
3442 def info(self, project, fmt, *args):
3443 self._messages.append(_InfoMessage(project, fmt % args))
3444
3445 def fail(self, project, err=None):
3446 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003447 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003448
3449 def later1(self, project, what):
3450 self._later_queue1.append(_Later(project, what))
3451
3452 def later2(self, project, what):
3453 self._later_queue2.append(_Later(project, what))
3454
3455 def Finish(self):
3456 self._PrintMessages()
3457 self._RunLater()
3458 self._PrintMessages()
3459 return self.clean
3460
David Rileye0684ad2017-04-05 00:02:59 -07003461 def Recently(self):
3462 recent_clean = self.recent_clean
3463 self.recent_clean = True
3464 return recent_clean
3465
3466 def _MarkUnclean(self):
3467 self.clean = False
3468 self.recent_clean = False
3469
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003470 def _RunLater(self):
3471 for q in ['_later_queue1', '_later_queue2']:
3472 if not self._RunQueue(q):
3473 return
3474
3475 def _RunQueue(self, queue):
3476 for m in getattr(self, queue):
3477 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003478 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003479 return False
3480 setattr(self, queue, [])
3481 return True
3482
3483 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003484 if self._messages or self._failures:
3485 if os.isatty(2):
3486 self.out.write(progress.CSI_ERASE_LINE)
3487 self.out.write('\r')
3488
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003489 for m in self._messages:
3490 m.Print(self)
3491 for m in self._failures:
3492 m.Print(self)
3493
3494 self._messages = []
3495 self._failures = []
3496
3497
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003498class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003499
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003500 """A special project housed under .repo.
3501 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003502
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003503 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003504 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003505 manifest=manifest,
3506 name=name,
3507 gitdir=gitdir,
3508 objdir=gitdir,
3509 worktree=worktree,
3510 remote=RemoteSpec('origin'),
3511 relpath='.repo/%s' % name,
3512 revisionExpr='refs/heads/master',
3513 revisionId=None,
3514 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003515
3516 def PreSync(self):
3517 if self.Exists:
3518 cb = self.CurrentBranch
3519 if cb:
3520 base = self.GetBranch(cb).merge
3521 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003522 self.revisionExpr = base
3523 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003524
Martin Kelly224a31a2017-07-10 14:46:25 -07003525 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003526 """ Prepare MetaProject for manifest branch switch
3527 """
3528
3529 # detach and delete manifest branch, allowing a new
3530 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003531 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003532 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003533 syncbuf.Finish()
3534
3535 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003536 ['update-ref', '-d', 'refs/heads/default'],
3537 capture_stdout=True,
3538 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003539
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003540 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003541 def LastFetch(self):
3542 try:
3543 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3544 return os.path.getmtime(fh)
3545 except OSError:
3546 return 0
3547
3548 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003549 def HasChanges(self):
3550 """Has the remote received new commits not yet checked out?
3551 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003552 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003553 return False
3554
David Pursehouse8a68ff92012-09-24 12:15:13 +09003555 all_refs = self.bare_ref.all
3556 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003557 head = self.work_git.GetHead()
3558 if head.startswith(R_HEADS):
3559 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003560 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003561 except KeyError:
3562 head = None
3563
3564 if revid == head:
3565 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003566 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003567 return True
3568 return False