blob: af2a8bc3924256d97f304411faedfe23cfc984c4 [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=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000204 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700206 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200208 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800209 validate_certs=True,
210 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500211 self.project.UploadForReview(branch=self.name,
212 people=people,
213 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700214 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=hashtags,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000216 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700218 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=validate_certs,
222 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 def GetPublishedRefs(self):
225 refs = {}
226 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227 self.branch.remote.SshReviewUrl(self.project.UserEmail),
228 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700229 for line in output.split('\n'):
230 try:
231 (sha, ref) = line.split()
232 refs[sha] = ref
233 except ValueError:
234 pass
235
236 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def __init__(self, config):
242 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.project = self.printer('header', attr='bold')
244 self.branch = self.printer('header', attr='bold')
245 self.nobranch = self.printer('nobranch', fg='red')
246 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
Anthony King7bdac712014-07-16 12:56:40 +0100248 self.added = self.printer('added', fg='green')
249 self.changed = self.printer('changed', fg='red')
250 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252
253class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
256 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400258 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
James W. Mills24c13082012-04-12 15:04:13 -0500263 def __init__(self, name, value, keep):
264 self.name = name
265 self.value = value
266 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Mike Frysingere6a202f2019-08-02 15:57:57 -0400269def _SafeExpandPath(base, subpath, skipfinal=False):
270 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700271
Mike Frysingere6a202f2019-08-02 15:57:57 -0400272 We make sure no intermediate symlinks are traversed, and that the final path
273 is not a special file (e.g. not a socket or fifo).
274
275 NB: We rely on a number of paths already being filtered out while parsing the
276 manifest. See the validation logic in manifest_xml.py for more details.
277 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500278 # Split up the path by its components. We can't use os.path.sep exclusively
279 # as some platforms (like Windows) will convert / to \ and that bypasses all
280 # our constructed logic here. Especially since manifest authors only use
281 # / in their paths.
282 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
283 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400284 if skipfinal:
285 # Whether the caller handles the final component itself.
286 finalpart = components.pop()
287
288 path = base
289 for part in components:
290 if part in {'.', '..'}:
291 raise ManifestInvalidPathError(
292 '%s: "%s" not allowed in paths' % (subpath, part))
293
294 path = os.path.join(path, part)
295 if platform_utils.islink(path):
296 raise ManifestInvalidPathError(
297 '%s: traversing symlinks not allow' % (path,))
298
299 if os.path.exists(path):
300 if not os.path.isfile(path) and not platform_utils.isdir(path):
301 raise ManifestInvalidPathError(
302 '%s: only regular files & directories allowed' % (path,))
303
304 if skipfinal:
305 path = os.path.join(path, finalpart)
306
307 return path
308
309
310class _CopyFile(object):
311 """Container for <copyfile> manifest element."""
312
313 def __init__(self, git_worktree, src, topdir, dest):
314 """Register a <copyfile> request.
315
316 Args:
317 git_worktree: Absolute path to the git project checkout.
318 src: Relative path under |git_worktree| of file to read.
319 topdir: Absolute path to the top of the repo client checkout.
320 dest: Relative path under |topdir| of file to write.
321 """
322 self.git_worktree = git_worktree
323 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700324 self.src = src
325 self.dest = dest
326
327 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400328 src = _SafeExpandPath(self.git_worktree, self.src)
329 dest = _SafeExpandPath(self.topdir, self.dest)
330
331 if platform_utils.isdir(src):
332 raise ManifestInvalidPathError(
333 '%s: copying from directory not supported' % (self.src,))
334 if platform_utils.isdir(dest):
335 raise ManifestInvalidPathError(
336 '%s: copying to directory not allowed' % (self.dest,))
337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700338 # copy file if it does not exist or is out of date
339 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
340 try:
341 # remove existing file first, since it might be read-only
342 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800343 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400344 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200345 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700346 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200347 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 shutil.copy(src, dest)
349 # make the file read-only
350 mode = os.stat(dest)[stat.ST_MODE]
351 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
352 os.chmod(dest, mode)
353 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700354 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700355
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700356
Anthony King7bdac712014-07-16 12:56:40 +0100357class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400358 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700359
Mike Frysingere6a202f2019-08-02 15:57:57 -0400360 def __init__(self, git_worktree, src, topdir, dest):
361 """Register a <linkfile> request.
362
363 Args:
364 git_worktree: Absolute path to the git project checkout.
365 src: Target of symlink relative to path under |git_worktree|.
366 topdir: Absolute path to the top of the repo client checkout.
367 dest: Relative path under |topdir| of symlink to create.
368 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700369 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400370 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500371 self.src = src
372 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500373
Wink Saville4c426ef2015-06-03 08:05:17 -0700374 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500375 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700376 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 try:
378 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800379 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800380 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700382 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700383 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500384 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700385 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500386 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 _error('Cannot link file %s to %s', relSrc, absDest)
388
389 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400390 """Link the self.src & self.dest paths.
391
392 Handles wild cards on the src linking all of the files in the source in to
393 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700394 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500395 # Some people use src="." to create stable links to projects. Lets allow
396 # that but reject all other uses of "." to keep things simple.
397 if self.src == '.':
398 src = self.git_worktree
399 else:
400 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400401
402 if os.path.exists(src):
403 # Entity exists so just a simple one to one link operation.
404 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
405 # dest & src are absolute paths at this point. Make sure the target of
406 # the symlink is relative in the context of the repo client checkout.
407 relpath = os.path.relpath(src, os.path.dirname(dest))
408 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400412 if os.path.exists(dest) and not platform_utils.isdir(dest):
413 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700414 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400415 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700416 # Create a releative path from source dir to destination dir
417 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419
420 # Get the source file name
421 srcFile = os.path.basename(absSrcFile)
422
423 # Now form the final full paths to srcFile. They will be
424 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400425 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700426 relSrc = os.path.join(relSrcDir, srcFile)
427 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500428
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700429
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700430class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700431
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700432 def __init__(self,
433 name,
Anthony King7bdac712014-07-16 12:56:40 +0100434 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700435 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100436 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700437 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700438 orig_name=None,
439 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700440 self.name = name
441 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700442 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700443 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100444 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700445 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700446 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700447
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700448
Doug Anderson37282b42011-03-04 11:54:18 -0800449class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700450
Doug Anderson37282b42011-03-04 11:54:18 -0800451 """A RepoHook contains information about a script to run as a hook.
452
453 Hooks are used to run a python script before running an upload (for instance,
454 to run presubmit checks). Eventually, we may have hooks for other actions.
455
456 This shouldn't be confused with files in the 'repo/hooks' directory. Those
457 files are copied into each '.git/hooks' folder for each project. Repo-level
458 hooks are associated instead with repo actions.
459
460 Hooks are always python. When a hook is run, we will load the hook into the
461 interpreter and execute its main() function.
462 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700463
Doug Anderson37282b42011-03-04 11:54:18 -0800464 def __init__(self,
465 hook_type,
466 hooks_project,
467 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400468 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800469 abort_if_user_denies=False):
470 """RepoHook constructor.
471
472 Params:
473 hook_type: A string representing the type of hook. This is also used
474 to figure out the name of the file containing the hook. For
475 example: 'pre-upload'.
476 hooks_project: The project containing the repo hooks. If you have a
477 manifest, this is manifest.repo_hooks_project. OK if this is None,
478 which will make the hook a no-op.
479 topdir: Repo's top directory (the one containing the .repo directory).
480 Scripts will run with CWD as this directory. If you have a manifest,
481 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400482 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800483 abort_if_user_denies: If True, we'll throw a HookError() if the user
484 doesn't allow us to run the hook.
485 """
486 self._hook_type = hook_type
487 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400488 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800489 self._topdir = topdir
490 self._abort_if_user_denies = abort_if_user_denies
491
492 # Store the full path to the script for convenience.
493 if self._hooks_project:
494 self._script_fullpath = os.path.join(self._hooks_project.worktree,
495 self._hook_type + '.py')
496 else:
497 self._script_fullpath = None
498
499 def _GetHash(self):
500 """Return a hash of the contents of the hooks directory.
501
502 We'll just use git to do this. This hash has the property that if anything
503 changes in the directory we will return a different has.
504
505 SECURITY CONSIDERATION:
506 This hash only represents the contents of files in the hook directory, not
507 any other files imported or called by hooks. Changes to imported files
508 can change the script behavior without affecting the hash.
509
510 Returns:
511 A string representing the hash. This will always be ASCII so that it can
512 be printed to the user easily.
513 """
514 assert self._hooks_project, "Must have hooks to calculate their hash."
515
516 # We will use the work_git object rather than just calling GetRevisionId().
517 # That gives us a hash of the latest checked in version of the files that
518 # the user will actually be executing. Specifically, GetRevisionId()
519 # doesn't appear to change even if a user checks out a different version
520 # of the hooks repo (via git checkout) nor if a user commits their own revs.
521 #
522 # NOTE: Local (non-committed) changes will not be factored into this hash.
523 # I think this is OK, since we're really only worried about warning the user
524 # about upstream changes.
525 return self._hooks_project.work_git.rev_parse('HEAD')
526
527 def _GetMustVerb(self):
528 """Return 'must' if the hook is required; 'should' if not."""
529 if self._abort_if_user_denies:
530 return 'must'
531 else:
532 return 'should'
533
534 def _CheckForHookApproval(self):
535 """Check to see whether this hook has been approved.
536
Mike Frysinger40252c22016-08-15 21:23:44 -0400537 We'll accept approval of manifest URLs if they're using secure transports.
538 This way the user can say they trust the manifest hoster. For insecure
539 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800540
541 Note that we ask permission for each individual hook even though we use
542 the hash of all hooks when detecting changes. We'd like the user to be
543 able to approve / deny each hook individually. We only use the hash of all
544 hooks because there is no other easy way to detect changes to local imports.
545
546 Returns:
547 True if this hook is approved to run; False otherwise.
548
549 Raises:
550 HookError: Raised if the user doesn't approve and abort_if_user_denies
551 was passed to the consturctor.
552 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400553 if self._ManifestUrlHasSecureScheme():
554 return self._CheckForHookApprovalManifest()
555 else:
556 return self._CheckForHookApprovalHash()
557
558 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
559 changed_prompt):
560 """Check for approval for a particular attribute and hook.
561
562 Args:
563 subkey: The git config key under [repo.hooks.<hook_type>] to store the
564 last approved string.
565 new_val: The new value to compare against the last approved one.
566 main_prompt: Message to display to the user to ask for approval.
567 changed_prompt: Message explaining why we're re-asking for approval.
568
569 Returns:
570 True if this hook is approved to run; False otherwise.
571
572 Raises:
573 HookError: Raised if the user doesn't approve and abort_if_user_denies
574 was passed to the consturctor.
575 """
Doug Anderson37282b42011-03-04 11:54:18 -0800576 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400577 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800578
Mike Frysinger40252c22016-08-15 21:23:44 -0400579 # Get the last value that the user approved for this hook; may be None.
580 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800581
Mike Frysinger40252c22016-08-15 21:23:44 -0400582 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800583 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400584 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800585 # Approval matched. We're done.
586 return True
587 else:
588 # Give the user a reason why we're prompting, since they last told
589 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400590 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800591 else:
592 prompt = ''
593
594 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
595 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400596 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530597 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900598 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800599
600 # User is doing a one-time approval.
601 if response in ('y', 'yes'):
602 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400603 elif response == 'always':
604 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800605 return True
606
607 # For anything else, we'll assume no approval.
608 if self._abort_if_user_denies:
609 raise HookError('You must allow the %s hook or use --no-verify.' %
610 self._hook_type)
611
612 return False
613
Mike Frysinger40252c22016-08-15 21:23:44 -0400614 def _ManifestUrlHasSecureScheme(self):
615 """Check if the URI for the manifest is a secure transport."""
616 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
617 parse_results = urllib.parse.urlparse(self._manifest_url)
618 return parse_results.scheme in secure_schemes
619
620 def _CheckForHookApprovalManifest(self):
621 """Check whether the user has approved this manifest host.
622
623 Returns:
624 True if this hook is approved to run; False otherwise.
625 """
626 return self._CheckForHookApprovalHelper(
627 'approvedmanifest',
628 self._manifest_url,
629 'Run hook scripts from %s' % (self._manifest_url,),
630 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
631
632 def _CheckForHookApprovalHash(self):
633 """Check whether the user has approved the hooks repo.
634
635 Returns:
636 True if this hook is approved to run; False otherwise.
637 """
638 prompt = ('Repo %s run the script:\n'
639 ' %s\n'
640 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700641 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400642 return self._CheckForHookApprovalHelper(
643 'approvedhash',
644 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700645 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400646 'Scripts have changed since %s was allowed.' % (self._hook_type,))
647
Mike Frysingerf7c51602019-06-18 17:23:39 -0400648 @staticmethod
649 def _ExtractInterpFromShebang(data):
650 """Extract the interpreter used in the shebang.
651
652 Try to locate the interpreter the script is using (ignoring `env`).
653
654 Args:
655 data: The file content of the script.
656
657 Returns:
658 The basename of the main script interpreter, or None if a shebang is not
659 used or could not be parsed out.
660 """
661 firstline = data.splitlines()[:1]
662 if not firstline:
663 return None
664
665 # The format here can be tricky.
666 shebang = firstline[0].strip()
667 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
668 if not m:
669 return None
670
671 # If the using `env`, find the target program.
672 interp = m.group(1)
673 if os.path.basename(interp) == 'env':
674 interp = m.group(2)
675
676 return interp
677
678 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
679 """Execute the hook script through |interp|.
680
681 Note: Support for this feature should be dropped ~Jun 2021.
682
683 Args:
684 interp: The Python program to run.
685 context: Basic Python context to execute the hook inside.
686 kwargs: Arbitrary arguments to pass to the hook script.
687
688 Raises:
689 HookError: When the hooks failed for any reason.
690 """
691 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
692 script = """
693import json, os, sys
694path = '''%(path)s'''
695kwargs = json.loads('''%(kwargs)s''')
696context = json.loads('''%(context)s''')
697sys.path.insert(0, os.path.dirname(path))
698data = open(path).read()
699exec(compile(data, path, 'exec'), context)
700context['main'](**kwargs)
701""" % {
702 'path': self._script_fullpath,
703 'kwargs': json.dumps(kwargs),
704 'context': json.dumps(context),
705 }
706
707 # We pass the script via stdin to avoid OS argv limits. It also makes
708 # unhandled exception tracebacks less verbose/confusing for users.
709 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
710 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
711 proc.communicate(input=script.encode('utf-8'))
712 if proc.returncode:
713 raise HookError('Failed to run %s hook.' % (self._hook_type,))
714
715 def _ExecuteHookViaImport(self, data, context, **kwargs):
716 """Execute the hook code in |data| directly.
717
718 Args:
719 data: The code of the hook to execute.
720 context: Basic Python context to execute the hook inside.
721 kwargs: Arbitrary arguments to pass to the hook script.
722
723 Raises:
724 HookError: When the hooks failed for any reason.
725 """
726 # Exec, storing global context in the context dict. We catch exceptions
727 # and convert to a HookError w/ just the failing traceback.
728 try:
729 exec(compile(data, self._script_fullpath, 'exec'), context)
730 except Exception:
731 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
732 (traceback.format_exc(), self._hook_type))
733
734 # Running the script should have defined a main() function.
735 if 'main' not in context:
736 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
737
738 # Call the main function in the hook. If the hook should cause the
739 # build to fail, it will raise an Exception. We'll catch that convert
740 # to a HookError w/ just the failing traceback.
741 try:
742 context['main'](**kwargs)
743 except Exception:
744 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
745 'above.' % (traceback.format_exc(), self._hook_type))
746
Doug Anderson37282b42011-03-04 11:54:18 -0800747 def _ExecuteHook(self, **kwargs):
748 """Actually execute the given hook.
749
750 This will run the hook's 'main' function in our python interpreter.
751
752 Args:
753 kwargs: Keyword arguments to pass to the hook. These are often specific
754 to the hook type. For instance, pre-upload hooks will contain
755 a project_list.
756 """
757 # Keep sys.path and CWD stashed away so that we can always restore them
758 # upon function exit.
759 orig_path = os.getcwd()
760 orig_syspath = sys.path
761
762 try:
763 # Always run hooks with CWD as topdir.
764 os.chdir(self._topdir)
765
766 # Put the hook dir as the first item of sys.path so hooks can do
767 # relative imports. We want to replace the repo dir as [0] so
768 # hooks can't import repo files.
769 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
770
Mike Frysingerf7c51602019-06-18 17:23:39 -0400771 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500772 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800773
Doug Anderson37282b42011-03-04 11:54:18 -0800774 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
775 # We don't actually want hooks to define their main with this argument--
776 # it's there to remind them that their hook should always take **kwargs.
777 # For instance, a pre-upload hook should be defined like:
778 # def main(project_list, **kwargs):
779 #
780 # This allows us to later expand the API without breaking old hooks.
781 kwargs = kwargs.copy()
782 kwargs['hook_should_take_kwargs'] = True
783
Mike Frysingerf7c51602019-06-18 17:23:39 -0400784 # See what version of python the hook has been written against.
785 data = open(self._script_fullpath).read()
786 interp = self._ExtractInterpFromShebang(data)
787 reexec = False
788 if interp:
789 prog = os.path.basename(interp)
790 if prog.startswith('python2') and sys.version_info.major != 2:
791 reexec = True
792 elif prog.startswith('python3') and sys.version_info.major == 2:
793 reexec = True
794
795 # Attempt to execute the hooks through the requested version of Python.
796 if reexec:
797 try:
798 self._ExecuteHookViaReexec(interp, context, **kwargs)
799 except OSError as e:
800 if e.errno == errno.ENOENT:
801 # We couldn't find the interpreter, so fallback to importing.
802 reexec = False
803 else:
804 raise
805
806 # Run the hook by importing directly.
807 if not reexec:
808 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800809 finally:
810 # Restore sys.path and CWD.
811 sys.path = orig_syspath
812 os.chdir(orig_path)
813
814 def Run(self, user_allows_all_hooks, **kwargs):
815 """Run the hook.
816
817 If the hook doesn't exist (because there is no hooks project or because
818 this particular hook is not enabled), this is a no-op.
819
820 Args:
821 user_allows_all_hooks: If True, we will never prompt about running the
822 hook--we'll just assume it's OK to run it.
823 kwargs: Keyword arguments to pass to the hook. These are often specific
824 to the hook type. For instance, pre-upload hooks will contain
825 a project_list.
826
827 Raises:
828 HookError: If there was a problem finding the hook or the user declined
829 to run a required hook (from _CheckForHookApproval).
830 """
831 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700832 if ((not self._hooks_project) or (self._hook_type not in
833 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800834 return
835
836 # Bail with a nice error if we can't find the hook.
837 if not os.path.isfile(self._script_fullpath):
838 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
839
840 # Make sure the user is OK with running the hook.
841 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
842 return
843
844 # Run the hook with the same version of python we're using.
845 self._ExecuteHook(**kwargs)
846
847
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600849 # These objects can be shared between several working trees.
850 shareable_files = ['description', 'info']
851 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
852 # These objects can only be used by a single working tree.
853 working_tree_files = ['config', 'packed-refs', 'shallow']
854 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700855
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856 def __init__(self,
857 manifest,
858 name,
859 remote,
860 gitdir,
David James8d201162013-10-11 17:03:19 -0700861 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 worktree,
863 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700864 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800865 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100866 rebase=True,
867 groups=None,
868 sync_c=False,
869 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900870 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100871 clone_depth=None,
872 upstream=None,
873 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500874 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100875 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900876 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700877 optimized_fetch=False,
878 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800879 """Init a Project object.
880
881 Args:
882 manifest: The XmlManifest object.
883 name: The `name` attribute of manifest.xml's project element.
884 remote: RemoteSpec object specifying its remote's properties.
885 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700886 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800887 worktree: Absolute path of git working tree.
888 relpath: Relative path of git working tree to repo's top directory.
889 revisionExpr: The `revision` attribute of manifest.xml's project element.
890 revisionId: git commit id for checking out.
891 rebase: The `rebase` attribute of manifest.xml's project element.
892 groups: The `groups` attribute of manifest.xml's project element.
893 sync_c: The `sync-c` attribute of manifest.xml's project element.
894 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900895 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800896 upstream: The `upstream` attribute of manifest.xml's project element.
897 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500898 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800899 is_derived: False if the project was explicitly defined in the manifest;
900 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400901 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900902 optimized_fetch: If True, when a project is set to a sha1 revision, only
903 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700904 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800905 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 self.manifest = manifest
907 self.name = name
908 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800909 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700910 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800911 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700912 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800913 else:
914 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700916 self.revisionExpr = revisionExpr
917
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700918 if revisionId is None \
919 and revisionExpr \
920 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700921 self.revisionId = revisionExpr
922 else:
923 self.revisionId = revisionId
924
Mike Pontillod3153822012-02-28 11:53:24 -0800925 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700926 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700927 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800928 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900929 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900930 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700931 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800932 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500933 # NB: Do not use this setting in __init__ to change behavior so that the
934 # manifest.git checkout can inspect & change it after instantiating. See
935 # the XmlManifest init code for more info.
936 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800937 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900938 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800939 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800940
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500943 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500944 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700945 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
946 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800948 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700949 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800950 else:
951 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700952 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700953 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700954 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400955 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700956 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
Doug Anderson37282b42011-03-04 11:54:18 -0800958 # This will be filled in if a project is later identified to be the
959 # project containing repo hooks.
960 self.enabled_repo_hooks = []
961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800963 def Derived(self):
964 return self.is_derived
965
966 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700968 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969
970 @property
971 def CurrentBranch(self):
972 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400973
974 The branch name omits the 'refs/heads/' prefix.
975 None is returned if the project is on a detached HEAD, or if the work_git is
976 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400978 try:
979 b = self.work_git.GetHead()
980 except NoManifestException:
981 # If the local checkout is in a bad state, don't barf. Let the callers
982 # process this like the head is unreadable.
983 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 if b.startswith(R_HEADS):
985 return b[len(R_HEADS):]
986 return None
987
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700988 def IsRebaseInProgress(self):
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -0500989 return (os.path.exists(self.work_git.GetDotgitPath('rebase-apply')) or
990 os.path.exists(self.work_git.GetDotgitPath('rebase-merge')) or
991 os.path.exists(os.path.join(self.worktree, '.dotest')))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200992
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993 def IsDirty(self, consider_untracked=True):
994 """Is the working directory modified in some way?
995 """
996 self.work_git.update_index('-q',
997 '--unmerged',
998 '--ignore-missing',
999 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +09001000 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001 return True
1002 if self.work_git.DiffZ('diff-files'):
1003 return True
1004 if consider_untracked and self.work_git.LsOthers():
1005 return True
1006 return False
1007
1008 _userident_name = None
1009 _userident_email = None
1010
1011 @property
1012 def UserName(self):
1013 """Obtain the user's personal name.
1014 """
1015 if self._userident_name is None:
1016 self._LoadUserIdentity()
1017 return self._userident_name
1018
1019 @property
1020 def UserEmail(self):
1021 """Obtain the user's email address. This is very likely
1022 to be their Gerrit login.
1023 """
1024 if self._userident_email is None:
1025 self._LoadUserIdentity()
1026 return self._userident_email
1027
1028 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001029 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1030 m = re.compile("^(.*) <([^>]*)> ").match(u)
1031 if m:
1032 self._userident_name = m.group(1)
1033 self._userident_email = m.group(2)
1034 else:
1035 self._userident_name = ''
1036 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001037
1038 def GetRemote(self, name):
1039 """Get the configuration for a single remote.
1040 """
1041 return self.config.GetRemote(name)
1042
1043 def GetBranch(self, name):
1044 """Get the configuration for a single branch.
1045 """
1046 return self.config.GetBranch(name)
1047
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001048 def GetBranches(self):
1049 """Get all existing local branches.
1050 """
1051 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001052 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001053 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001054
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301055 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001056 if name.startswith(R_HEADS):
1057 name = name[len(R_HEADS):]
1058 b = self.GetBranch(name)
1059 b.current = name == current
1060 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001061 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001062 heads[name] = b
1063
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301064 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001065 if name.startswith(R_PUB):
1066 name = name[len(R_PUB):]
1067 b = heads.get(name)
1068 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001069 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001070
1071 return heads
1072
Colin Cross5acde752012-03-28 20:15:45 -07001073 def MatchesGroups(self, manifest_groups):
1074 """Returns true if the manifest groups specified at init should cause
1075 this project to be synced.
1076 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001077 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001078
1079 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001080 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001081 manifest_groups: "-group1,group2"
1082 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001083
1084 The special manifest group "default" will match any project that
1085 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001086 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001087 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001088 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001089 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001090 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001091
Conley Owens971de8e2012-04-16 10:36:08 -07001092 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001093 for group in expanded_manifest_groups:
1094 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001095 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001096 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001097 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001098
Conley Owens971de8e2012-04-16 10:36:08 -07001099 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001101# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001102 def UncommitedFiles(self, get_all=True):
1103 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001105 Args:
1106 get_all: a boolean, if True - get information about all different
1107 uncommitted files. If False - return as soon as any kind of
1108 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001109 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001110 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001111 self.work_git.update_index('-q',
1112 '--unmerged',
1113 '--ignore-missing',
1114 '--refresh')
1115 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001116 details.append("rebase in progress")
1117 if not get_all:
1118 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001119
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001120 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1121 if changes:
1122 details.extend(changes)
1123 if not get_all:
1124 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001125
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001126 changes = self.work_git.DiffZ('diff-files').keys()
1127 if changes:
1128 details.extend(changes)
1129 if not get_all:
1130 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001131
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001132 changes = self.work_git.LsOthers()
1133 if changes:
1134 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001135
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001136 return details
1137
1138 def HasChanges(self):
1139 """Returns true if there are uncommitted changes.
1140 """
1141 if self.UncommitedFiles(get_all=False):
1142 return True
1143 else:
1144 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001145
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001146 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001147 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001148
1149 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001150 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001151 quiet: If True then only print the project name. Do not print
1152 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001154 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001155 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001156 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001157 print(file=output_redir)
1158 print('project %s/' % self.relpath, file=output_redir)
1159 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160 return
1161
1162 self.work_git.update_index('-q',
1163 '--unmerged',
1164 '--ignore-missing',
1165 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001166 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1168 df = self.work_git.DiffZ('diff-files')
1169 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001170 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001171 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001172
1173 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001174 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001175 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001176 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001177
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001178 if quiet:
1179 out.nl()
1180 return 'DIRTY'
1181
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001182 branch = self.CurrentBranch
1183 if branch is None:
1184 out.nobranch('(*** NO BRANCH ***)')
1185 else:
1186 out.branch('branch %s', branch)
1187 out.nl()
1188
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001189 if rb:
1190 out.important('prior sync failed; rebase still in progress')
1191 out.nl()
1192
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 paths = list()
1194 paths.extend(di.keys())
1195 paths.extend(df.keys())
1196 paths.extend(do)
1197
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301198 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001199 try:
1200 i = di[p]
1201 except KeyError:
1202 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001204 try:
1205 f = df[p]
1206 except KeyError:
1207 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001208
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001209 if i:
1210 i_status = i.status.upper()
1211 else:
1212 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001213
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001214 if f:
1215 f_status = f.status.lower()
1216 else:
1217 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001218
1219 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001220 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001221 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001222 else:
1223 line = ' %s%s\t%s' % (i_status, f_status, p)
1224
1225 if i and not f:
1226 out.added('%s', line)
1227 elif (i and f) or (not i and f):
1228 out.changed('%s', line)
1229 elif not i and not f:
1230 out.untracked('%s', line)
1231 else:
1232 out.write('%s', line)
1233 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001234
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001235 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001236
pelyad67872d2012-03-28 14:49:58 +03001237 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001238 """Prints the status of the repository to stdout.
1239 """
1240 out = DiffColoring(self.config)
1241 cmd = ['diff']
1242 if out.is_on:
1243 cmd.append('--color')
1244 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001245 if absolute_paths:
1246 cmd.append('--src-prefix=a/%s/' % self.relpath)
1247 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001249 try:
1250 p = GitCommand(self,
1251 cmd,
1252 capture_stdout=True,
1253 capture_stderr=True)
1254 except GitError as e:
1255 out.nl()
1256 out.project('project %s/' % self.relpath)
1257 out.nl()
1258 out.fail('%s', str(e))
1259 out.nl()
1260 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261 has_diff = False
1262 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001263 if not hasattr(line, 'encode'):
1264 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001265 if not has_diff:
1266 out.nl()
1267 out.project('project %s/' % self.relpath)
1268 out.nl()
1269 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001270 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001271 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001272
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001273# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001274 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275 """Was the branch published (uploaded) for code review?
1276 If so, returns the SHA-1 hash of the last published
1277 state for the branch.
1278 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001279 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001280 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001281 try:
1282 return self.bare_git.rev_parse(key)
1283 except GitError:
1284 return None
1285 else:
1286 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001287 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001288 except KeyError:
1289 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001290
David Pursehouse8a68ff92012-09-24 12:15:13 +09001291 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292 """Prunes any stale published refs.
1293 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001294 if all_refs is None:
1295 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296 heads = set()
1297 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301298 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 if name.startswith(R_HEADS):
1300 heads.add(name)
1301 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001302 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301304 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305 n = name[len(R_PUB):]
1306 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001307 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001309 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310 """List any branches which can be uploaded for review.
1311 """
1312 heads = {}
1313 pubed = {}
1314
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301315 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001317 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001319 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320
1321 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301322 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001323 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001325 if selected_branch and branch != selected_branch:
1326 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001328 rb = self.GetUploadableBranch(branch)
1329 if rb:
1330 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331 return ready
1332
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001333 def GetUploadableBranch(self, branch_name):
1334 """Get a single uploadable branch, or None.
1335 """
1336 branch = self.GetBranch(branch_name)
1337 base = branch.LocalMerge
1338 if branch.LocalMerge:
1339 rb = ReviewableBranch(self, branch, base)
1340 if rb.commits:
1341 return rb
1342 return None
1343
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001344 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001345 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001346 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001347 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001348 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001349 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001350 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001351 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001352 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001353 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001354 validate_certs=True,
1355 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001356 """Uploads the named branch for code review.
1357 """
1358 if branch is None:
1359 branch = self.CurrentBranch
1360 if branch is None:
1361 raise GitError('not currently on a branch')
1362
1363 branch = self.GetBranch(branch)
1364 if not branch.LocalMerge:
1365 raise GitError('branch %s does not track a remote' % branch.name)
1366 if not branch.remote.review:
1367 raise GitError('remote %s has no review url' % branch.remote.name)
1368
Bryan Jacobsf609f912013-05-06 13:36:24 -04001369 if dest_branch is None:
1370 dest_branch = self.dest_branch
1371 if dest_branch is None:
1372 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001373 if not dest_branch.startswith(R_HEADS):
1374 dest_branch = R_HEADS + dest_branch
1375
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001376 if not branch.remote.projectname:
1377 branch.remote.projectname = self.name
1378 branch.remote.Save()
1379
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001380 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001381 if url is None:
1382 raise UploadError('review not configured')
1383 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001384 if dryrun:
1385 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001386
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001387 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001388 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001389
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001390 for push_option in (push_options or []):
1391 cmd.append('-o')
1392 cmd.append(push_option)
1393
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001394 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001395
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001396 if dest_branch.startswith(R_HEADS):
1397 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001398
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001399 upload_type = 'for'
1400 if draft:
1401 upload_type = 'drafts'
1402
1403 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1404 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001405 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001406 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001407 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001408 opts += ['t=%s' % p for p in hashtags]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001409
David Pursehousef25a3702018-11-14 19:01:22 -08001410 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001411 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001412 if notify:
1413 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001414 if private:
1415 opts += ['private']
1416 if wip:
1417 opts += ['wip']
1418 if opts:
1419 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001420 cmd.append(ref_spec)
1421
Anthony King7bdac712014-07-16 12:56:40 +01001422 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001423 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001424
1425 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1426 self.bare_git.UpdateRef(R_PUB + branch.name,
1427 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001428 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001429
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001430# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001431 def _ExtractArchive(self, tarpath, path=None):
1432 """Extract the given tar on its current location
1433
1434 Args:
1435 - tarpath: The path to the actual tar file
1436
1437 """
1438 try:
1439 with tarfile.open(tarpath, 'r') as tar:
1440 tar.extractall(path=path)
1441 return True
1442 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001443 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001444 return False
1445
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001446 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001447 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001448 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001449 is_new=None,
1450 current_branch_only=False,
1451 force_sync=False,
1452 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001453 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001454 archive=False,
1455 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001456 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001457 submodules=False,
1458 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459 """Perform only the network IO portion of the sync process.
1460 Local working directory/branch state is not affected.
1461 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001462 if archive and not isinstance(self, MetaProject):
1463 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001464 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001465 return False
1466
1467 name = self.relpath.replace('\\', '/')
1468 name = name.replace('/', '_')
1469 tarpath = '%s.tar' % name
1470 topdir = self.manifest.topdir
1471
1472 try:
1473 self._FetchArchive(tarpath, cwd=topdir)
1474 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001475 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001476 return False
1477
1478 # From now on, we only need absolute tarpath
1479 tarpath = os.path.join(topdir, tarpath)
1480
1481 if not self._ExtractArchive(tarpath, path=topdir):
1482 return False
1483 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001484 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001485 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001486 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001487 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001488 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001489 if is_new is None:
1490 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001491 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001492 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001493 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001494 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001495 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001496
1497 if is_new:
1498 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1499 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001500 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001501 # This works for both absolute and relative alternate directories.
1502 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001503 except IOError:
1504 alt_dir = None
1505 else:
1506 alt_dir = None
1507
Mike Frysingere50b6a72020-02-19 01:45:48 -05001508 if (clone_bundle
1509 and alt_dir is None
1510 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001511 is_new = False
1512
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001513 if not current_branch_only:
1514 if self.sync_c:
1515 current_branch_only = True
1516 elif not self.manifest._loaded:
1517 # Manifest cannot check defaults until it syncs.
1518 current_branch_only = False
1519 elif self.manifest.default.sync_c:
1520 current_branch_only = True
1521
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001522 if not self.sync_tags:
1523 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001524
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001525 if self.clone_depth:
1526 depth = self.clone_depth
1527 else:
1528 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1529
Mike Frysinger521d01b2020-02-17 01:51:49 -05001530 # See if we can skip the network fetch entirely.
1531 if not (optimized_fetch and
1532 (ID_RE.match(self.revisionExpr) and
1533 self._CheckForImmutableRevision())):
1534 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001535 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1536 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001537 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001538 submodules=submodules, force_sync=force_sync,
1539 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001540 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001541
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001542 mp = self.manifest.manifestProject
1543 dissociate = mp.config.GetBoolean('repo.dissociate')
1544 if dissociate:
1545 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1546 if os.path.exists(alternates_file):
1547 cmd = ['repack', '-a', '-d']
1548 if GitCommand(self, cmd, bare=True).Wait() != 0:
1549 return False
1550 platform_utils.remove(alternates_file)
1551
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001552 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001553 self._InitMRef()
1554 else:
1555 self._InitMirrorHead()
1556 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001557 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001558 except OSError:
1559 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001561
1562 def PostRepoUpgrade(self):
1563 self._InitHooks()
1564
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001565 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001566 if self.manifest.isGitcClient:
1567 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001568 for copyfile in self.copyfiles:
1569 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001570 for linkfile in self.linkfiles:
1571 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572
Julien Camperguedd654222014-01-09 16:21:37 +01001573 def GetCommitRevisionId(self):
1574 """Get revisionId of a commit.
1575
1576 Use this method instead of GetRevisionId to get the id of the commit rather
1577 than the id of the current git object (for example, a tag)
1578
1579 """
1580 if not self.revisionExpr.startswith(R_TAGS):
1581 return self.GetRevisionId(self._allrefs)
1582
1583 try:
1584 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1585 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001586 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1587 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001588
David Pursehouse8a68ff92012-09-24 12:15:13 +09001589 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001590 if self.revisionId:
1591 return self.revisionId
1592
1593 rem = self.GetRemote(self.remote.name)
1594 rev = rem.ToLocal(self.revisionExpr)
1595
David Pursehouse8a68ff92012-09-24 12:15:13 +09001596 if all_refs is not None and rev in all_refs:
1597 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001598
1599 try:
1600 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1601 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001602 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1603 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001604
Martin Kellye4e94d22017-03-21 16:05:12 -07001605 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001606 """Perform only the local IO portion of the sync process.
1607 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001608 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001609 if not os.path.exists(self.gitdir):
1610 syncbuf.fail(self,
1611 'Cannot checkout %s due to missing network sync; Run '
1612 '`repo sync -n %s` first.' %
1613 (self.name, self.name))
1614 return
1615
Martin Kellye4e94d22017-03-21 16:05:12 -07001616 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001617 all_refs = self.bare_ref.all
1618 self.CleanPublishedCache(all_refs)
1619 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001620
David Pursehouse1d947b32012-10-25 12:23:11 +09001621 def _doff():
1622 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001623 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001624
Martin Kellye4e94d22017-03-21 16:05:12 -07001625 def _dosubmodules():
1626 self._SyncSubmodules(quiet=True)
1627
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001628 head = self.work_git.GetHead()
1629 if head.startswith(R_HEADS):
1630 branch = head[len(R_HEADS):]
1631 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001632 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001633 except KeyError:
1634 head = None
1635 else:
1636 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001637
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001638 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639 # Currently on a detached HEAD. The user is assumed to
1640 # not have any local modifications worth worrying about.
1641 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001642 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001643 syncbuf.fail(self, _PriorSyncFailedError())
1644 return
1645
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001646 if head == revid:
1647 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001648 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001649 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001650 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001651 # The copy/linkfile config may have changed.
1652 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001653 return
1654 else:
1655 lost = self._revlist(not_rev(revid), HEAD)
1656 if lost:
1657 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001658
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001659 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001660 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001661 if submodules:
1662 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001663 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001664 syncbuf.fail(self, e)
1665 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001666 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001667 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001668
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001669 if head == revid:
1670 # No changes; don't do anything further.
1671 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001672 # The copy/linkfile config may have changed.
1673 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001674 return
1675
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001676 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001677
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001678 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001679 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001680 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001682 syncbuf.info(self,
1683 "leaving %s; does not track upstream",
1684 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001686 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001687 if submodules:
1688 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001689 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001690 syncbuf.fail(self, e)
1691 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001692 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001693 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001694
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001695 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001696
1697 # See if we can perform a fast forward merge. This can happen if our
1698 # branch isn't in the exact same state as we last published.
1699 try:
1700 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1701 # Skip the published logic.
1702 pub = False
1703 except GitError:
1704 pub = self.WasPublished(branch.name, all_refs)
1705
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001706 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001707 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708 if not_merged:
1709 if upstream_gain:
1710 # The user has published this branch and some of those
1711 # commits are not yet merged upstream. We do not want
1712 # to rewrite the published commits so we punt.
1713 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001714 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001715 "branch %s is published (but not merged) and is now "
1716 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001717 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001718 elif pub == head:
1719 # All published commits are merged, and thus we are a
1720 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001721 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001722 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001723 if submodules:
1724 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001725 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001726
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001727 # Examine the local commits not in the remote. Find the
1728 # last one attributed to this user, if any.
1729 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001730 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001731 last_mine = None
1732 cnt_mine = 0
1733 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001734 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001735 if committer_email == self.UserEmail:
1736 last_mine = commit_id
1737 cnt_mine += 1
1738
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001739 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001740 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741
1742 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001743 syncbuf.fail(self, _DirtyError())
1744 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001746 # If the upstream switched on us, warn the user.
1747 #
1748 if branch.merge != self.revisionExpr:
1749 if branch.merge and self.revisionExpr:
1750 syncbuf.info(self,
1751 'manifest switched %s...%s',
1752 branch.merge,
1753 self.revisionExpr)
1754 elif branch.merge:
1755 syncbuf.info(self,
1756 'manifest no longer tracks %s',
1757 branch.merge)
1758
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001759 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001760 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001761 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001763 syncbuf.info(self,
1764 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001765 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001767 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001768 if not ID_RE.match(self.revisionExpr):
1769 # in case of manifest sync the revisionExpr might be a SHA1
1770 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001771 if not branch.merge.startswith('refs/'):
1772 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773 branch.Save()
1774
Mike Pontillod3153822012-02-28 11:53:24 -08001775 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001776 def _docopyandlink():
1777 self._CopyAndLinkFiles()
1778
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001779 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001780 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001781 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001782 if submodules:
1783 syncbuf.later2(self, _dosubmodules)
1784 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001785 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001787 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001788 if submodules:
1789 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001790 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001791 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001792 syncbuf.fail(self, e)
1793 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001795 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001796 if submodules:
1797 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001798
Mike Frysingere6a202f2019-08-02 15:57:57 -04001799 def AddCopyFile(self, src, dest, topdir):
1800 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801
Mike Frysingere6a202f2019-08-02 15:57:57 -04001802 No filesystem changes occur here. Actual copying happens later on.
1803
1804 Paths should have basic validation run on them before being queued.
1805 Further checking will be handled when the actual copy happens.
1806 """
1807 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1808
1809 def AddLinkFile(self, src, dest, topdir):
1810 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1811
1812 No filesystem changes occur here. Actual linking happens later on.
1813
1814 Paths should have basic validation run on them before being queued.
1815 Further checking will be handled when the actual link happens.
1816 """
1817 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001818
James W. Mills24c13082012-04-12 15:04:13 -05001819 def AddAnnotation(self, name, value, keep):
1820 self.annotations.append(_Annotation(name, value, keep))
1821
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001822 def DownloadPatchSet(self, change_id, patch_id):
1823 """Download a single patch set of a single change to FETCH_HEAD.
1824 """
1825 remote = self.GetRemote(self.remote.name)
1826
1827 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001828 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001829 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001830 if GitCommand(self, cmd, bare=True).Wait() != 0:
1831 return None
1832 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001833 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001834 change_id,
1835 patch_id,
1836 self.bare_git.rev_parse('FETCH_HEAD'))
1837
Mike Frysingerc0d18662020-02-19 19:19:18 -05001838 def DeleteWorktree(self, quiet=False, force=False):
1839 """Delete the source checkout and any other housekeeping tasks.
1840
1841 This currently leaves behind the internal .repo/ cache state. This helps
1842 when switching branches or manifest changes get reverted as we don't have
1843 to redownload all the git objects. But we should do some GC at some point.
1844
1845 Args:
1846 quiet: Whether to hide normal messages.
1847 force: Always delete tree even if dirty.
1848
1849 Returns:
1850 True if the worktree was completely cleaned out.
1851 """
1852 if self.IsDirty():
1853 if force:
1854 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1855 (self.relpath,), file=sys.stderr)
1856 else:
1857 print('error: %s: Cannot remove project: uncommitted changes are '
1858 'present.\n' % (self.relpath,), file=sys.stderr)
1859 return False
1860
1861 if not quiet:
1862 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1863
1864 # Unlock and delink from the main worktree. We don't use git's worktree
1865 # remove because it will recursively delete projects -- we handle that
1866 # ourselves below. https://crbug.com/git/48
1867 if self.use_git_worktrees:
1868 needle = platform_utils.realpath(self.gitdir)
1869 # Find the git worktree commondir under .repo/worktrees/.
1870 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1871 assert output.startswith('worktree '), output
1872 commondir = output[9:]
1873 # Walk each of the git worktrees to see where they point.
1874 configs = os.path.join(commondir, 'worktrees')
1875 for name in os.listdir(configs):
1876 gitdir = os.path.join(configs, name, 'gitdir')
1877 with open(gitdir) as fp:
1878 relpath = fp.read().strip()
1879 # Resolve the checkout path and see if it matches this project.
1880 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1881 if fullpath == needle:
1882 platform_utils.rmtree(os.path.join(configs, name))
1883
1884 # Delete the .git directory first, so we're less likely to have a partially
1885 # working git repository around. There shouldn't be any git projects here,
1886 # so rmtree works.
1887
1888 # Try to remove plain files first in case of git worktrees. If this fails
1889 # for any reason, we'll fall back to rmtree, and that'll display errors if
1890 # it can't remove things either.
1891 try:
1892 platform_utils.remove(self.gitdir)
1893 except OSError:
1894 pass
1895 try:
1896 platform_utils.rmtree(self.gitdir)
1897 except OSError as e:
1898 if e.errno != errno.ENOENT:
1899 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1900 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1901 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1902 return False
1903
1904 # Delete everything under the worktree, except for directories that contain
1905 # another git project.
1906 dirs_to_remove = []
1907 failed = False
1908 for root, dirs, files in platform_utils.walk(self.worktree):
1909 for f in files:
1910 path = os.path.join(root, f)
1911 try:
1912 platform_utils.remove(path)
1913 except OSError as e:
1914 if e.errno != errno.ENOENT:
1915 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1916 failed = True
1917 dirs[:] = [d for d in dirs
1918 if not os.path.lexists(os.path.join(root, d, '.git'))]
1919 dirs_to_remove += [os.path.join(root, d) for d in dirs
1920 if os.path.join(root, d) not in dirs_to_remove]
1921 for d in reversed(dirs_to_remove):
1922 if platform_utils.islink(d):
1923 try:
1924 platform_utils.remove(d)
1925 except OSError as e:
1926 if e.errno != errno.ENOENT:
1927 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1928 failed = True
1929 elif not platform_utils.listdir(d):
1930 try:
1931 platform_utils.rmdir(d)
1932 except OSError as e:
1933 if e.errno != errno.ENOENT:
1934 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1935 failed = True
1936 if failed:
1937 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1938 file=sys.stderr)
1939 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1940 return False
1941
1942 # Try deleting parent dirs if they are empty.
1943 path = self.worktree
1944 while path != self.manifest.topdir:
1945 try:
1946 platform_utils.rmdir(path)
1947 except OSError as e:
1948 if e.errno != errno.ENOENT:
1949 break
1950 path = os.path.dirname(path)
1951
1952 return True
1953
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001954# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001955 def GetHeadPath(self):
1956 """Return the full path to the HEAD ref."""
1957 dotgit = os.path.join(self.worktree, '.git')
1958 if os.path.isfile(dotgit):
1959 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1960 with open(dotgit) as fp:
1961 setting = fp.read()
1962 assert setting.startswith('gitdir:')
1963 gitdir = setting.split(':', 1)[1].strip()
1964 dotgit = os.path.join(self.worktree, gitdir)
1965 return os.path.join(dotgit, HEAD)
1966
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001967 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001968 """Create a new branch off the manifest's revision.
1969 """
Simran Basib9a1b732015-08-20 12:19:28 -07001970 if not branch_merge:
1971 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001972 head = self.work_git.GetHead()
1973 if head == (R_HEADS + name):
1974 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001975
David Pursehouse8a68ff92012-09-24 12:15:13 +09001976 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001977 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001978 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001979 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001980 capture_stdout=True,
1981 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001982
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001983 branch = self.GetBranch(name)
1984 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001985 branch.merge = branch_merge
1986 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1987 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001988
1989 if revision is None:
1990 revid = self.GetRevisionId(all_refs)
1991 else:
1992 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001993
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001994 if head.startswith(R_HEADS):
1995 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001996 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001997 except KeyError:
1998 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001999 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05002000 ref = R_HEADS + name
2001 self.work_git.update_ref(ref, revid)
2002 self.work_git.symbolic_ref(HEAD, ref)
2003 branch.Save()
2004 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002005
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002006 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002007 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01002008 capture_stdout=True,
2009 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002010 branch.Save()
2011 return True
2012 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002013
Wink Saville02d79452009-04-10 13:01:24 -07002014 def CheckoutBranch(self, name):
2015 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002016
2017 Args:
2018 name: The name of the branch to checkout.
2019
2020 Returns:
2021 True if the checkout succeeded; False if it didn't; None if the branch
2022 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002023 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002024 rev = R_HEADS + name
2025 head = self.work_git.GetHead()
2026 if head == rev:
2027 # Already on the branch
2028 #
2029 return True
Wink Saville02d79452009-04-10 13:01:24 -07002030
David Pursehouse8a68ff92012-09-24 12:15:13 +09002031 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002032 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002033 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002034 except KeyError:
2035 # Branch does not exist in this project
2036 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002037 return None
Wink Saville02d79452009-04-10 13:01:24 -07002038
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002039 if head.startswith(R_HEADS):
2040 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002041 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002042 except KeyError:
2043 head = None
2044
2045 if head == revid:
2046 # Same revision; just update HEAD to point to the new
2047 # target branch, but otherwise take no other action.
2048 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05002049 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002050 return True
2051
2052 return GitCommand(self,
2053 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002054 capture_stdout=True,
2055 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002056
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002057 def AbandonBranch(self, name):
2058 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002059
2060 Args:
2061 name: The name of the branch to abandon.
2062
2063 Returns:
2064 True if the abandon succeeded; False if it didn't; None if the branch
2065 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002066 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002067 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002068 all_refs = self.bare_ref.all
2069 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002070 # Doesn't exist
2071 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002072
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002073 head = self.work_git.GetHead()
2074 if head == rev:
2075 # We can't destroy the branch while we are sitting
2076 # on it. Switch to a detached HEAD.
2077 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002078 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002079
David Pursehouse8a68ff92012-09-24 12:15:13 +09002080 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002081 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05002082 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002083 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002084 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002085
2086 return GitCommand(self,
2087 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002088 capture_stdout=True,
2089 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002090
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002091 def PruneHeads(self):
2092 """Prune any topic branches already merged into upstream.
2093 """
2094 cb = self.CurrentBranch
2095 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002096 left = self._allrefs
2097 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098 if name.startswith(R_HEADS):
2099 name = name[len(R_HEADS):]
2100 if cb is None or name != cb:
2101 kill.append(name)
2102
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002103 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002104 if cb is not None \
2105 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002106 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002107 self.work_git.DetachHead(HEAD)
2108 kill.append(cb)
2109
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002110 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002111 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002112
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002113 try:
2114 self.bare_git.DetachHead(rev)
2115
2116 b = ['branch', '-d']
2117 b.extend(kill)
2118 b = GitCommand(self, b, bare=True,
2119 capture_stdout=True,
2120 capture_stderr=True)
2121 b.Wait()
2122 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002123 if ID_RE.match(old):
2124 self.bare_git.DetachHead(old)
2125 else:
2126 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002127 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002128
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002129 for branch in kill:
2130 if (R_HEADS + branch) not in left:
2131 self.CleanPublishedCache()
2132 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002133
2134 if cb and cb not in kill:
2135 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002136 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002137
2138 kept = []
2139 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002140 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002141 branch = self.GetBranch(branch)
2142 base = branch.LocalMerge
2143 if not base:
2144 base = rev
2145 kept.append(ReviewableBranch(self, branch, base))
2146 return kept
2147
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002148# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002149 def GetRegisteredSubprojects(self):
2150 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002151
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002152 def rec(subprojects):
2153 if not subprojects:
2154 return
2155 result.extend(subprojects)
2156 for p in subprojects:
2157 rec(p.subprojects)
2158 rec(self.subprojects)
2159 return result
2160
2161 def _GetSubmodules(self):
2162 # Unfortunately we cannot call `git submodule status --recursive` here
2163 # because the working tree might not exist yet, and it cannot be used
2164 # without a working tree in its current implementation.
2165
2166 def get_submodules(gitdir, rev):
2167 # Parse .gitmodules for submodule sub_paths and sub_urls
2168 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2169 if not sub_paths:
2170 return []
2171 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2172 # revision of submodule repository
2173 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2174 submodules = []
2175 for sub_path, sub_url in zip(sub_paths, sub_urls):
2176 try:
2177 sub_rev = sub_revs[sub_path]
2178 except KeyError:
2179 # Ignore non-exist submodules
2180 continue
2181 submodules.append((sub_rev, sub_path, sub_url))
2182 return submodules
2183
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002184 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2185 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002186
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002187 def parse_gitmodules(gitdir, rev):
2188 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2189 try:
Anthony King7bdac712014-07-16 12:56:40 +01002190 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2191 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002192 except GitError:
2193 return [], []
2194 if p.Wait() != 0:
2195 return [], []
2196
2197 gitmodules_lines = []
2198 fd, temp_gitmodules_path = tempfile.mkstemp()
2199 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002200 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002201 os.close(fd)
2202 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002203 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2204 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002205 if p.Wait() != 0:
2206 return [], []
2207 gitmodules_lines = p.stdout.split('\n')
2208 except GitError:
2209 return [], []
2210 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002211 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002212
2213 names = set()
2214 paths = {}
2215 urls = {}
2216 for line in gitmodules_lines:
2217 if not line:
2218 continue
2219 m = re_path.match(line)
2220 if m:
2221 names.add(m.group(1))
2222 paths[m.group(1)] = m.group(2)
2223 continue
2224 m = re_url.match(line)
2225 if m:
2226 names.add(m.group(1))
2227 urls[m.group(1)] = m.group(2)
2228 continue
2229 names = sorted(names)
2230 return ([paths.get(name, '') for name in names],
2231 [urls.get(name, '') for name in names])
2232
2233 def git_ls_tree(gitdir, rev, paths):
2234 cmd = ['ls-tree', rev, '--']
2235 cmd.extend(paths)
2236 try:
Anthony King7bdac712014-07-16 12:56:40 +01002237 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2238 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002239 except GitError:
2240 return []
2241 if p.Wait() != 0:
2242 return []
2243 objects = {}
2244 for line in p.stdout.split('\n'):
2245 if not line.strip():
2246 continue
2247 object_rev, object_path = line.split()[2:4]
2248 objects[object_path] = object_rev
2249 return objects
2250
2251 try:
2252 rev = self.GetRevisionId()
2253 except GitError:
2254 return []
2255 return get_submodules(self.gitdir, rev)
2256
2257 def GetDerivedSubprojects(self):
2258 result = []
2259 if not self.Exists:
2260 # If git repo does not exist yet, querying its submodules will
2261 # mess up its states; so return here.
2262 return result
2263 for rev, path, url in self._GetSubmodules():
2264 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002265 relpath, worktree, gitdir, objdir = \
2266 self.manifest.GetSubprojectPaths(self, name, path)
2267 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002268 if project:
2269 result.extend(project.GetDerivedSubprojects())
2270 continue
David James8d201162013-10-11 17:03:19 -07002271
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002272 if url.startswith('..'):
2273 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002274 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002275 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002276 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002277 review=self.remote.review,
2278 revision=self.remote.revision)
2279 subproject = Project(manifest=self.manifest,
2280 name=name,
2281 remote=remote,
2282 gitdir=gitdir,
2283 objdir=objdir,
2284 worktree=worktree,
2285 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002286 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002287 revisionId=rev,
2288 rebase=self.rebase,
2289 groups=self.groups,
2290 sync_c=self.sync_c,
2291 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002292 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002293 parent=self,
2294 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002295 result.append(subproject)
2296 result.extend(subproject.GetDerivedSubprojects())
2297 return result
2298
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002299# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002300 def EnableRepositoryExtension(self, key, value='true', version=1):
2301 """Enable git repository extension |key| with |value|.
2302
2303 Args:
2304 key: The extension to enabled. Omit the "extensions." prefix.
2305 value: The value to use for the extension.
2306 version: The minimum git repository version needed.
2307 """
2308 # Make sure the git repo version is new enough already.
2309 found_version = self.config.GetInt('core.repositoryFormatVersion')
2310 if found_version is None:
2311 found_version = 0
2312 if found_version < version:
2313 self.config.SetString('core.repositoryFormatVersion', str(version))
2314
2315 # Enable the extension!
2316 self.config.SetString('extensions.%s' % (key,), value)
2317
Zac Livingstone4332262017-06-16 08:56:09 -06002318 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002319 try:
2320 # if revision (sha or tag) is not present then following function
2321 # throws an error.
2322 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2323 return True
2324 except GitError:
2325 # There is no such persistent revision. We have to fetch it.
2326 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327
Julien Campergue335f5ef2013-10-16 11:02:35 +02002328 def _FetchArchive(self, tarpath, cwd=None):
2329 cmd = ['archive', '-v', '-o', tarpath]
2330 cmd.append('--remote=%s' % self.remote.url)
2331 cmd.append('--prefix=%s/' % self.relpath)
2332 cmd.append(self.revisionExpr)
2333
2334 command = GitCommand(self, cmd, cwd=cwd,
2335 capture_stdout=True,
2336 capture_stderr=True)
2337
2338 if command.Wait() != 0:
2339 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2340
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002341 def _RemoteFetch(self, name=None,
2342 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002343 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002344 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002345 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002346 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002347 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002348 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002349 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002350 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002351 force_sync=False,
2352 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002353
2354 is_sha1 = False
2355 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002356 # The depth should not be used when fetching to a mirror because
2357 # it will result in a shallow repository that cannot be cloned or
2358 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002359 # The repo project should also never be synced with partial depth.
2360 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2361 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002362
Shawn Pearce69e04d82014-01-29 12:48:54 -08002363 if depth:
2364 current_branch_only = True
2365
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002366 if ID_RE.match(self.revisionExpr) is not None:
2367 is_sha1 = True
2368
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002369 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002370 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002371 # this is a tag and its sha1 value should never change
2372 tag_name = self.revisionExpr[len(R_TAGS):]
2373
2374 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002375 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002376 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002377 print('Skipped fetching project %s (already have persistent ref)'
2378 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002379 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002380 if is_sha1 and not depth:
2381 # When syncing a specific commit and --depth is not set:
2382 # * if upstream is explicitly specified and is not a sha1, fetch only
2383 # upstream as users expect only upstream to be fetch.
2384 # Note: The commit might not be in upstream in which case the sync
2385 # will fail.
2386 # * otherwise, fetch all branches to make sure we end up with the
2387 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002388 if self.upstream:
2389 current_branch_only = not ID_RE.match(self.upstream)
2390 else:
2391 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002392
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002393 if not name:
2394 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002395
2396 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002397 remote = self.GetRemote(name)
2398 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002399 ssh_proxy = True
2400
Shawn O. Pearce88443382010-10-08 10:02:09 +02002401 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002402 if alt_dir and 'objects' == os.path.basename(alt_dir):
2403 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002404 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2405 remote = self.GetRemote(name)
2406
David Pursehouse8a68ff92012-09-24 12:15:13 +09002407 all_refs = self.bare_ref.all
2408 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002409 tmp = set()
2410
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302411 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002412 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002413 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002414 all_refs[r] = ref_id
2415 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002416 continue
2417
David Pursehouse8a68ff92012-09-24 12:15:13 +09002418 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002419 continue
2420
David Pursehouse8a68ff92012-09-24 12:15:13 +09002421 r = 'refs/_alt/%s' % ref_id
2422 all_refs[r] = ref_id
2423 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002424 tmp.add(r)
2425
heping3d7bbc92017-04-12 19:51:47 +08002426 tmp_packed_lines = []
2427 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002428
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302429 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002430 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002431 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002432 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002433 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002434
heping3d7bbc92017-04-12 19:51:47 +08002435 tmp_packed = ''.join(tmp_packed_lines)
2436 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002437 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002438 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002439 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002440
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002441 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002442
Xin Li745be2e2019-06-03 11:24:30 -07002443 if clone_filter:
2444 git_require((2, 19, 0), fail=True, msg='partial clones')
2445 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002446 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002447
Conley Owensf97e8382015-01-21 11:12:46 -08002448 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002449 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002450 else:
2451 # If this repo has shallow objects, then we don't know which refs have
2452 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2453 # do this with projects that don't have shallow objects, since it is less
2454 # efficient.
2455 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2456 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002457
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002458 if quiet:
2459 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002460 if not self.worktree:
2461 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002462 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002463
Mike Frysingere57f1142019-03-18 21:27:54 -04002464 if force_sync:
2465 cmd.append('--force')
2466
David Pursehouse74cfd272015-10-14 10:50:15 +09002467 if prune:
2468 cmd.append('--prune')
2469
Martin Kellye4e94d22017-03-21 16:05:12 -07002470 if submodules:
2471 cmd.append('--recurse-submodules=on-demand')
2472
Kuang-che Wu6856f982019-11-25 12:37:55 +08002473 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002474 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002475 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002476 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002477 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002478 spec.append('tag')
2479 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002480
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302481 if self.manifest.IsMirror and not current_branch_only:
2482 branch = None
2483 else:
2484 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002485 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002486 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002487 # Shallow checkout of a specific commit, fetch from that commit and not
2488 # the heads only as the commit might be deeper in the history.
2489 spec.append(branch)
2490 else:
2491 if is_sha1:
2492 branch = self.upstream
2493 if branch is not None and branch.strip():
2494 if not branch.startswith('refs/'):
2495 branch = R_HEADS + branch
2496 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2497
2498 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2499 # whole repo.
2500 if self.manifest.IsMirror and not spec:
2501 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2502
2503 # If using depth then we should not get all the tags since they may
2504 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002505 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002506 cmd.append('--no-tags')
2507 else:
2508 cmd.append('--tags')
2509 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2510
Conley Owens80b87fe2014-05-09 17:13:44 -07002511 cmd.extend(spec)
2512
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002513 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002514 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002515 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2516 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002517 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002518 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002519 ok = True
2520 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002521 # If needed, run the 'git remote prune' the first time through the loop
2522 elif (not _i and
2523 "error:" in gitcmd.stderr and
2524 "git remote prune" in gitcmd.stderr):
2525 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002526 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002527 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002528 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002529 break
2530 continue
Brian Harring14a66742012-09-28 20:21:57 -07002531 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002532 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2533 # in sha1 mode, we just tried sync'ing from the upstream field; it
2534 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002535 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002536 elif ret < 0:
2537 # Git died with a signal, exit immediately
2538 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002539 if not verbose:
2540 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002541 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002542
2543 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002544 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002545 if old_packed != '':
2546 _lwrite(packed_refs, old_packed)
2547 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002548 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002549 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002550
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002551 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002552 # We just synced the upstream given branch; verify we
2553 # got what we wanted, else trigger a second run of all
2554 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002555 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002556 # Sync the current branch only with depth set to None.
2557 # We always pass depth=None down to avoid infinite recursion.
2558 return self._RemoteFetch(
2559 name=name, quiet=quiet, verbose=verbose,
2560 current_branch_only=current_branch_only and depth,
2561 initial=False, alt_dir=alt_dir,
2562 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002563
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002564 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002565
Mike Frysingere50b6a72020-02-19 01:45:48 -05002566 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002567 if initial and \
2568 (self.manifest.manifestProject.config.GetString('repo.depth') or
2569 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002570 return False
2571
2572 remote = self.GetRemote(self.remote.name)
2573 bundle_url = remote.url + '/clone.bundle'
2574 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002575 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2576 'persistent-http',
2577 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002578 return False
2579
2580 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2581 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2582
2583 exist_dst = os.path.exists(bundle_dst)
2584 exist_tmp = os.path.exists(bundle_tmp)
2585
2586 if not initial and not exist_dst and not exist_tmp:
2587 return False
2588
2589 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002590 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2591 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002592 if not exist_dst:
2593 return False
2594
2595 cmd = ['fetch']
2596 if quiet:
2597 cmd.append('--quiet')
2598 if not self.worktree:
2599 cmd.append('--update-head-ok')
2600 cmd.append(bundle_dst)
2601 for f in remote.fetch:
2602 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002603 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002604
2605 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002606 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002607 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002608 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002609 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002610 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611
Mike Frysingere50b6a72020-02-19 01:45:48 -05002612 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002613 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002614 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002615
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002616 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002617 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002618 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002619 if os.path.exists(tmpPath):
2620 size = os.stat(tmpPath).st_size
2621 if size >= 1024:
2622 cmd += ['--continue-at', '%d' % (size,)]
2623 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002624 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002625 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002626 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002627 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002628 if proxy:
2629 cmd += ['--proxy', proxy]
2630 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2631 cmd += ['--proxy', os.environ['http_proxy']]
2632 if srcUrl.startswith('persistent-https'):
2633 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2634 elif srcUrl.startswith('persistent-http'):
2635 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002636 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002637
Dave Borowitz137d0132015-01-02 11:12:54 -08002638 if IsTrace():
2639 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002640 if verbose:
2641 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2642 stdout = None if verbose else subprocess.PIPE
2643 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002644 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002645 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002646 except OSError:
2647 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002648
Mike Frysingere50b6a72020-02-19 01:45:48 -05002649 (output, _) = proc.communicate()
2650 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002651
Dave Borowitz137d0132015-01-02 11:12:54 -08002652 if curlret == 22:
2653 # From curl man page:
2654 # 22: HTTP page not retrieved. The requested url was not found or
2655 # returned another error with the HTTP error code being 400 or above.
2656 # This return code only appears if -f, --fail is used.
2657 if not quiet:
2658 print("Server does not provide clone.bundle; ignoring.",
2659 file=sys.stderr)
2660 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002661 elif curlret and not verbose and output:
2662 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002663
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002664 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002665 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002666 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002667 return True
2668 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002669 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002670 return False
2671 else:
2672 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002673
Kris Giesingc8d882a2014-12-23 13:02:32 -08002674 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002675 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002676 with open(path, 'rb') as f:
2677 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002678 return True
2679 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002680 if not quiet:
2681 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002682 return False
2683 except OSError:
2684 return False
2685
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002686 def _Checkout(self, rev, quiet=False):
2687 cmd = ['checkout']
2688 if quiet:
2689 cmd.append('-q')
2690 cmd.append(rev)
2691 cmd.append('--')
2692 if GitCommand(self, cmd).Wait() != 0:
2693 if self._allrefs:
2694 raise GitError('%s checkout %s ' % (self.name, rev))
2695
Anthony King7bdac712014-07-16 12:56:40 +01002696 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002697 cmd = ['cherry-pick']
2698 cmd.append(rev)
2699 cmd.append('--')
2700 if GitCommand(self, cmd).Wait() != 0:
2701 if self._allrefs:
2702 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2703
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302704 def _LsRemote(self, refs):
2705 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302706 p = GitCommand(self, cmd, capture_stdout=True)
2707 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002708 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302709 return None
2710
Anthony King7bdac712014-07-16 12:56:40 +01002711 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002712 cmd = ['revert']
2713 cmd.append('--no-edit')
2714 cmd.append(rev)
2715 cmd.append('--')
2716 if GitCommand(self, cmd).Wait() != 0:
2717 if self._allrefs:
2718 raise GitError('%s revert %s ' % (self.name, rev))
2719
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002720 def _ResetHard(self, rev, quiet=True):
2721 cmd = ['reset', '--hard']
2722 if quiet:
2723 cmd.append('-q')
2724 cmd.append(rev)
2725 if GitCommand(self, cmd).Wait() != 0:
2726 raise GitError('%s reset --hard %s ' % (self.name, rev))
2727
Martin Kellye4e94d22017-03-21 16:05:12 -07002728 def _SyncSubmodules(self, quiet=True):
2729 cmd = ['submodule', 'update', '--init', '--recursive']
2730 if quiet:
2731 cmd.append('-q')
2732 if GitCommand(self, cmd).Wait() != 0:
2733 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2734
Anthony King7bdac712014-07-16 12:56:40 +01002735 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002736 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002737 if onto is not None:
2738 cmd.extend(['--onto', onto])
2739 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002740 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002741 raise GitError('%s rebase %s ' % (self.name, upstream))
2742
Pierre Tardy3d125942012-05-04 12:18:12 +02002743 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002744 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002745 if ffonly:
2746 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002747 if GitCommand(self, cmd).Wait() != 0:
2748 raise GitError('%s merge %s ' % (self.name, head))
2749
David Pursehousee8ace262020-02-13 12:41:15 +09002750 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002751 init_git_dir = not os.path.exists(self.gitdir)
2752 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002753 try:
2754 # Initialize the bare repository, which contains all of the objects.
2755 if init_obj_dir:
2756 os.makedirs(self.objdir)
2757 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002758
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002759 # Enable per-worktree config file support if possible. This is more a
2760 # nice-to-have feature for users rather than a hard requirement.
2761 if self.use_git_worktrees and git_require((2, 19, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002762 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002763
Kevin Degib1a07b82015-07-27 13:33:43 -06002764 # If we have a separate directory to hold refs, initialize it as well.
2765 if self.objdir != self.gitdir:
2766 if init_git_dir:
2767 os.makedirs(self.gitdir)
2768
2769 if init_obj_dir or init_git_dir:
2770 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2771 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002772 try:
2773 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2774 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002775 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002776 print("Retrying clone after deleting %s" %
2777 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002778 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002779 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2780 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002781 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002782 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002783 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2784 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002785 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002786 raise e
2787 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002788
Kevin Degi384b3c52014-10-16 16:02:58 -06002789 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002790 mp = self.manifest.manifestProject
2791 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002792
Kevin Degib1a07b82015-07-27 13:33:43 -06002793 if ref_dir or mirror_git:
2794 if not mirror_git:
2795 mirror_git = os.path.join(ref_dir, self.name + '.git')
2796 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2797 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002798 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2799 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002800
Kevin Degib1a07b82015-07-27 13:33:43 -06002801 if os.path.exists(mirror_git):
2802 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002803 elif os.path.exists(repo_git):
2804 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002805 elif os.path.exists(worktrees_git):
2806 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002807 else:
2808 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002809
Kevin Degib1a07b82015-07-27 13:33:43 -06002810 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002811 if not os.path.isabs(ref_dir):
2812 # The alternate directory is relative to the object database.
2813 ref_dir = os.path.relpath(ref_dir,
2814 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002815 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2816 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002817
David Pursehousee8ace262020-02-13 12:41:15 +09002818 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002819
2820 m = self.manifest.manifestProject.config
2821 for key in ['user.name', 'user.email']:
2822 if m.Has(key, include_defaults=False):
2823 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002824 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002825 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002826 if self.manifest.IsMirror:
2827 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002828 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002829 self.config.SetString('core.bare', None)
2830 except Exception:
2831 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002832 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002833 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002834 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002835 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002836
David Pursehousee8ace262020-02-13 12:41:15 +09002837 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002838 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002839 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002840
David Pursehousee8ace262020-02-13 12:41:15 +09002841 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002842 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002843 if not os.path.exists(hooks):
2844 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002845 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002846 name = os.path.basename(stock_hook)
2847
Victor Boivie65e0f352011-04-18 11:23:29 +02002848 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002849 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002850 # Don't install a Gerrit Code Review hook if this
2851 # project does not appear to use it for reviews.
2852 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002853 # Since the manifest project is one of those, but also
2854 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002855 continue
2856
2857 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002858 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002859 continue
2860 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002861 # If the files are the same, we'll leave it alone. We create symlinks
2862 # below by default but fallback to hardlinks if the OS blocks them.
2863 # So if we're here, it's probably because we made a hardlink below.
2864 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002865 if not quiet:
2866 _warn("%s: Not replacing locally modified %s hook",
2867 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002868 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002869 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002870 platform_utils.symlink(
2871 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002872 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002873 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002874 try:
2875 os.link(stock_hook, dst)
2876 except OSError:
2877 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002878 else:
2879 raise
2880
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002882 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002883 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002884 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002885 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002886 remote.review = self.remote.review
2887 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002888
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002889 if self.worktree:
2890 remote.ResetFetch(mirror=False)
2891 else:
2892 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002893 remote.Save()
2894
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002895 def _InitMRef(self):
2896 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002897 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002898
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002899 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002900 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002901
2902 def _InitAnyMRef(self, ref):
2903 cur = self.bare_ref.symref(ref)
2904
2905 if self.revisionId:
2906 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2907 msg = 'manifest set to %s' % self.revisionId
2908 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002909 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002910 else:
2911 remote = self.GetRemote(self.remote.name)
2912 dst = remote.ToLocal(self.revisionExpr)
2913 if cur != dst:
2914 msg = 'manifest set to %s' % self.revisionExpr
2915 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002916
Kevin Degi384b3c52014-10-16 16:02:58 -06002917 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002918 # Git worktrees don't use symlinks to share at all.
2919 if self.use_git_worktrees:
2920 return
2921
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002922 symlink_files = self.shareable_files[:]
2923 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002924 if share_refs:
2925 symlink_files += self.working_tree_files
2926 symlink_dirs += self.working_tree_dirs
2927 to_symlink = symlink_files + symlink_dirs
2928 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002929 # Try to self-heal a bit in simple cases.
2930 dst_path = os.path.join(destdir, name)
2931 src_path = os.path.join(srcdir, name)
2932
2933 if name in self.working_tree_dirs:
2934 # If the dir is missing under .repo/projects/, create it.
2935 if not os.path.exists(src_path):
2936 os.makedirs(src_path)
2937
2938 elif name in self.working_tree_files:
2939 # If it's a file under the checkout .git/ and the .repo/projects/ has
2940 # nothing, move the file under the .repo/projects/ tree.
2941 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2942 platform_utils.rename(dst_path, src_path)
2943
2944 # If the path exists under the .repo/projects/ and there's no symlink
2945 # under the checkout .git/, recreate the symlink.
2946 if name in self.working_tree_dirs or name in self.working_tree_files:
2947 if os.path.exists(src_path) and not os.path.exists(dst_path):
2948 platform_utils.symlink(
2949 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2950
2951 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002952 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002953 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002954 # Fail if the links are pointing to the wrong place
2955 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002956 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002957 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002958 'work tree. If you\'re comfortable with the '
2959 'possibility of losing the work tree\'s git metadata,'
2960 ' use `repo sync --force-sync {0}` to '
2961 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002962
David James8d201162013-10-11 17:03:19 -07002963 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2964 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2965
2966 Args:
2967 gitdir: The bare git repository. Must already be initialized.
2968 dotgit: The repository you would like to initialize.
2969 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2970 Only one work tree can store refs under a given |gitdir|.
2971 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2972 This saves you the effort of initializing |dotgit| yourself.
2973 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002974 symlink_files = self.shareable_files[:]
2975 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002976 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002977 symlink_files += self.working_tree_files
2978 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002979 to_symlink = symlink_files + symlink_dirs
2980
2981 to_copy = []
2982 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002983 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002984
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002985 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002986 for name in set(to_copy).union(to_symlink):
2987 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002988 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002989 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002990
Kevin Degi384b3c52014-10-16 16:02:58 -06002991 if os.path.lexists(dst):
2992 continue
David James8d201162013-10-11 17:03:19 -07002993
2994 # If the source dir doesn't exist, create an empty dir.
2995 if name in symlink_dirs and not os.path.lexists(src):
2996 os.makedirs(src)
2997
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002998 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002999 platform_utils.symlink(
3000 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003001 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07003002 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003003 shutil.copytree(src, dst)
3004 elif os.path.isfile(src):
3005 shutil.copy(src, dst)
3006
Conley Owens80b87fe2014-05-09 17:13:44 -07003007 # If the source file doesn't exist, ensure the destination
3008 # file doesn't either.
3009 if name in symlink_files and not os.path.lexists(src):
3010 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003011 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003012 except OSError:
3013 pass
3014
David James8d201162013-10-11 17:03:19 -07003015 except OSError as e:
3016 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003017 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003018 else:
3019 raise
3020
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003021 def _InitGitWorktree(self):
3022 """Init the project using git worktrees."""
3023 self.bare_git.worktree('prune')
3024 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3025 self.worktree, self.GetRevisionId())
3026
3027 # Rewrite the internal state files to use relative paths between the
3028 # checkouts & worktrees.
3029 dotgit = os.path.join(self.worktree, '.git')
3030 with open(dotgit, 'r') as fp:
3031 # Figure out the checkout->worktree path.
3032 setting = fp.read()
3033 assert setting.startswith('gitdir:')
3034 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05003035 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
3036 # of file permissions. Delete it and recreate it from scratch to avoid.
3037 platform_utils.remove(dotgit)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003038 # Use relative path from checkout->worktree.
3039 with open(dotgit, 'w') as fp:
3040 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3041 file=fp)
3042 # Use relative path from worktree->checkout.
3043 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3044 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3045
Martin Kellye4e94d22017-03-21 16:05:12 -07003046 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003047 realdotgit = os.path.join(self.worktree, '.git')
3048 tmpdotgit = realdotgit + '.tmp'
3049 init_dotgit = not os.path.exists(realdotgit)
3050 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003051 if self.use_git_worktrees:
3052 self._InitGitWorktree()
3053 self._CopyAndLinkFiles()
3054 return
3055
Mike Frysingerf4545122019-11-11 04:34:16 -05003056 dotgit = tmpdotgit
3057 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3058 os.makedirs(tmpdotgit)
3059 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3060 copy_all=False)
3061 else:
3062 dotgit = realdotgit
3063
Kevin Degib1a07b82015-07-27 13:33:43 -06003064 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003065 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3066 except GitError as e:
3067 if force_sync and not init_dotgit:
3068 try:
3069 platform_utils.rmtree(dotgit)
3070 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003071 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003072 raise e
3073 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003074
Mike Frysingerf4545122019-11-11 04:34:16 -05003075 if init_dotgit:
3076 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003077
Mike Frysingerf4545122019-11-11 04:34:16 -05003078 # Now that the .git dir is fully set up, move it to its final home.
3079 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003080
Mike Frysingerf4545122019-11-11 04:34:16 -05003081 # Finish checking out the worktree.
3082 cmd = ['read-tree', '--reset', '-u']
3083 cmd.append('-v')
3084 cmd.append(HEAD)
3085 if GitCommand(self, cmd).Wait() != 0:
3086 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003087
Mike Frysingerf4545122019-11-11 04:34:16 -05003088 if submodules:
3089 self._SyncSubmodules(quiet=True)
3090 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003091
Renaud Paquay788e9622017-01-27 11:41:12 -08003092 def _get_symlink_error_message(self):
3093 if platform_utils.isWindows():
3094 return ('Unable to create symbolic link. Please re-run the command as '
3095 'Administrator, or see '
3096 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3097 'for other options.')
3098 return 'filesystem must support symlinks'
3099
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003100 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003101 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003102
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003103 def _revlist(self, *args, **kw):
3104 a = []
3105 a.extend(args)
3106 a.append('--')
3107 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003108
3109 @property
3110 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003111 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003112
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003113 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003114 """Get logs between two revisions of this project."""
3115 comp = '..'
3116 if rev1:
3117 revs = [rev1]
3118 if rev2:
3119 revs.extend([comp, rev2])
3120 cmd = ['log', ''.join(revs)]
3121 out = DiffColoring(self.config)
3122 if out.is_on and color:
3123 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003124 if pretty_format is not None:
3125 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003126 if oneline:
3127 cmd.append('--oneline')
3128
3129 try:
3130 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3131 if log.Wait() == 0:
3132 return log.stdout
3133 except GitError:
3134 # worktree may not exist if groups changed for example. In that case,
3135 # try in gitdir instead.
3136 if not os.path.exists(self.worktree):
3137 return self.bare_git.log(*cmd[1:])
3138 else:
3139 raise
3140 return None
3141
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003142 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3143 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003144 """Get the list of logs from this revision to given revisionId"""
3145 logs = {}
3146 selfId = self.GetRevisionId(self._allrefs)
3147 toId = toProject.GetRevisionId(toProject._allrefs)
3148
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003149 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3150 pretty_format=pretty_format)
3151 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3152 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003153 return logs
3154
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003155 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003156
David James8d201162013-10-11 17:03:19 -07003157 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003158 self._project = project
3159 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003160 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003161
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003162 def LsOthers(self):
3163 p = GitCommand(self._project,
3164 ['ls-files',
3165 '-z',
3166 '--others',
3167 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003168 bare=False,
David James8d201162013-10-11 17:03:19 -07003169 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003170 capture_stdout=True,
3171 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003172 if p.Wait() == 0:
3173 out = p.stdout
3174 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003176 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003177 return []
3178
3179 def DiffZ(self, name, *args):
3180 cmd = [name]
3181 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003182 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003183 cmd.extend(args)
3184 p = GitCommand(self._project,
3185 cmd,
David James8d201162013-10-11 17:03:19 -07003186 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003187 bare=False,
3188 capture_stdout=True,
3189 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003190 try:
3191 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003192 if not hasattr(out, 'encode'):
3193 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003194 r = {}
3195 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003196 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003197 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003198 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003199 info = next(out)
3200 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003201 except StopIteration:
3202 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003203
3204 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003205
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003206 def __init__(self, path, omode, nmode, oid, nid, state):
3207 self.path = path
3208 self.src_path = None
3209 self.old_mode = omode
3210 self.new_mode = nmode
3211 self.old_id = oid
3212 self.new_id = nid
3213
3214 if len(state) == 1:
3215 self.status = state
3216 self.level = None
3217 else:
3218 self.status = state[:1]
3219 self.level = state[1:]
3220 while self.level.startswith('0'):
3221 self.level = self.level[1:]
3222
3223 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003224 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003225 if info.status in ('R', 'C'):
3226 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003227 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003228 r[info.path] = info
3229 return r
3230 finally:
3231 p.Wait()
3232
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003233 def GetDotgitPath(self, subpath=None):
3234 """Return the full path to the .git dir.
3235
3236 As a convenience, append |subpath| if provided.
3237 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003238 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003239 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003240 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003241 dotgit = os.path.join(self._project.worktree, '.git')
3242 if os.path.isfile(dotgit):
3243 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3244 with open(dotgit) as fp:
3245 setting = fp.read()
3246 assert setting.startswith('gitdir:')
3247 gitdir = setting.split(':', 1)[1].strip()
3248 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3249
3250 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3251
3252 def GetHead(self):
3253 """Return the ref that HEAD points to."""
3254 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003255 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003256 with open(path) as fd:
3257 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003258 except IOError as e:
3259 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003260 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303261 line = line.decode()
3262 except AttributeError:
3263 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003264 if line.startswith('ref: '):
3265 return line[5:-1]
3266 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003267
3268 def SetHead(self, ref, message=None):
3269 cmdv = []
3270 if message is not None:
3271 cmdv.extend(['-m', message])
3272 cmdv.append(HEAD)
3273 cmdv.append(ref)
3274 self.symbolic_ref(*cmdv)
3275
3276 def DetachHead(self, new, message=None):
3277 cmdv = ['--no-deref']
3278 if message is not None:
3279 cmdv.extend(['-m', message])
3280 cmdv.append(HEAD)
3281 cmdv.append(new)
3282 self.update_ref(*cmdv)
3283
3284 def UpdateRef(self, name, new, old=None,
3285 message=None,
3286 detach=False):
3287 cmdv = []
3288 if message is not None:
3289 cmdv.extend(['-m', message])
3290 if detach:
3291 cmdv.append('--no-deref')
3292 cmdv.append(name)
3293 cmdv.append(new)
3294 if old is not None:
3295 cmdv.append(old)
3296 self.update_ref(*cmdv)
3297
3298 def DeleteRef(self, name, old=None):
3299 if not old:
3300 old = self.rev_parse(name)
3301 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003302 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003303
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003304 def rev_list(self, *args, **kw):
3305 if 'format' in kw:
3306 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3307 else:
3308 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003309 cmdv.extend(args)
3310 p = GitCommand(self._project,
3311 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003312 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003313 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003314 capture_stdout=True,
3315 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003316 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003317 raise GitError('%s rev-list %s: %s' %
3318 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003319 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003320
3321 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003322 """Allow arbitrary git commands using pythonic syntax.
3323
3324 This allows you to do things like:
3325 git_obj.rev_parse('HEAD')
3326
3327 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3328 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003329 Any other positional arguments will be passed to the git command, and the
3330 following keyword arguments are supported:
3331 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003332
3333 Args:
3334 name: The name of the git command to call. Any '_' characters will
3335 be replaced with '-'.
3336
3337 Returns:
3338 A callable object that will try to call git with the named command.
3339 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003340 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003341
Dave Borowitz091f8932012-10-23 17:01:04 -07003342 def runner(*args, **kwargs):
3343 cmdv = []
3344 config = kwargs.pop('config', None)
3345 for k in kwargs:
3346 raise TypeError('%s() got an unexpected keyword argument %r'
3347 % (name, k))
3348 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303349 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003350 cmdv.append('-c')
3351 cmdv.append('%s=%s' % (k, v))
3352 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003353 cmdv.extend(args)
3354 p = GitCommand(self._project,
3355 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003356 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003357 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003358 capture_stdout=True,
3359 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003360 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003361 raise GitError('%s %s: %s' %
3362 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003363 r = p.stdout
3364 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3365 return r[:-1]
3366 return r
3367 return runner
3368
3369
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003370class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003371
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003372 def __str__(self):
3373 return 'prior sync failed; rebase still in progress'
3374
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003375
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003376class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003377
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003378 def __str__(self):
3379 return 'contains uncommitted changes'
3380
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003381
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003382class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003383
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003384 def __init__(self, project, text):
3385 self.project = project
3386 self.text = text
3387
3388 def Print(self, syncbuf):
3389 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3390 syncbuf.out.nl()
3391
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003392
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003393class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003394
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003395 def __init__(self, project, why):
3396 self.project = project
3397 self.why = why
3398
3399 def Print(self, syncbuf):
3400 syncbuf.out.fail('error: %s/: %s',
3401 self.project.relpath,
3402 str(self.why))
3403 syncbuf.out.nl()
3404
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003405
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003406class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003407
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003408 def __init__(self, project, action):
3409 self.project = project
3410 self.action = action
3411
3412 def Run(self, syncbuf):
3413 out = syncbuf.out
3414 out.project('project %s/', self.project.relpath)
3415 out.nl()
3416 try:
3417 self.action()
3418 out.nl()
3419 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003420 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003421 out.nl()
3422 return False
3423
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003424
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003425class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003426
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003427 def __init__(self, config):
3428 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003429 self.project = self.printer('header', attr='bold')
3430 self.info = self.printer('info')
3431 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003432
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003433
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003434class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003435
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003436 def __init__(self, config, detach_head=False):
3437 self._messages = []
3438 self._failures = []
3439 self._later_queue1 = []
3440 self._later_queue2 = []
3441
3442 self.out = _SyncColoring(config)
3443 self.out.redirect(sys.stderr)
3444
3445 self.detach_head = detach_head
3446 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003447 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003448
3449 def info(self, project, fmt, *args):
3450 self._messages.append(_InfoMessage(project, fmt % args))
3451
3452 def fail(self, project, err=None):
3453 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003454 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003455
3456 def later1(self, project, what):
3457 self._later_queue1.append(_Later(project, what))
3458
3459 def later2(self, project, what):
3460 self._later_queue2.append(_Later(project, what))
3461
3462 def Finish(self):
3463 self._PrintMessages()
3464 self._RunLater()
3465 self._PrintMessages()
3466 return self.clean
3467
David Rileye0684ad2017-04-05 00:02:59 -07003468 def Recently(self):
3469 recent_clean = self.recent_clean
3470 self.recent_clean = True
3471 return recent_clean
3472
3473 def _MarkUnclean(self):
3474 self.clean = False
3475 self.recent_clean = False
3476
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003477 def _RunLater(self):
3478 for q in ['_later_queue1', '_later_queue2']:
3479 if not self._RunQueue(q):
3480 return
3481
3482 def _RunQueue(self, queue):
3483 for m in getattr(self, queue):
3484 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003485 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003486 return False
3487 setattr(self, queue, [])
3488 return True
3489
3490 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003491 if self._messages or self._failures:
3492 if os.isatty(2):
3493 self.out.write(progress.CSI_ERASE_LINE)
3494 self.out.write('\r')
3495
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003496 for m in self._messages:
3497 m.Print(self)
3498 for m in self._failures:
3499 m.Print(self)
3500
3501 self._messages = []
3502 self._failures = []
3503
3504
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003505class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003506
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003507 """A special project housed under .repo.
3508 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003509
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003510 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003511 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003512 manifest=manifest,
3513 name=name,
3514 gitdir=gitdir,
3515 objdir=gitdir,
3516 worktree=worktree,
3517 remote=RemoteSpec('origin'),
3518 relpath='.repo/%s' % name,
3519 revisionExpr='refs/heads/master',
3520 revisionId=None,
3521 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003522
3523 def PreSync(self):
3524 if self.Exists:
3525 cb = self.CurrentBranch
3526 if cb:
3527 base = self.GetBranch(cb).merge
3528 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003529 self.revisionExpr = base
3530 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003531
Martin Kelly224a31a2017-07-10 14:46:25 -07003532 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003533 """ Prepare MetaProject for manifest branch switch
3534 """
3535
3536 # detach and delete manifest branch, allowing a new
3537 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003538 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003539 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003540 syncbuf.Finish()
3541
3542 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003543 ['update-ref', '-d', 'refs/heads/default'],
3544 capture_stdout=True,
3545 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003546
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003547 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003548 def LastFetch(self):
3549 try:
3550 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3551 return os.path.getmtime(fh)
3552 except OSError:
3553 return 0
3554
3555 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003556 def HasChanges(self):
3557 """Has the remote received new commits not yet checked out?
3558 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003559 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003560 return False
3561
David Pursehouse8a68ff92012-09-24 12:15:13 +09003562 all_refs = self.bare_ref.all
3563 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003564 head = self.work_git.GetHead()
3565 if head.startswith(R_HEADS):
3566 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003567 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003568 except KeyError:
3569 head = None
3570
3571 if revid == head:
3572 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003573 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003574 return True
3575 return False