blob: a58af4ffa2fd3ee1afeaf9b9bfa0e39802ef3ae5 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Mike Frysinger21b7fbe2020-02-26 23:53:36 -050045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Mike Frysinger3164d402019-11-11 05:40:22 -050061 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500204 labels=(),
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,
Mike Frysingerfc1b18a2020-02-24 15:38:07 -0500216 labels=labels,
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=(),
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001349 labels=(),
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
Mike Frysingerb0fbc7f2020-02-25 02:58:04 -05001399 ref_spec = '%s:refs/for/%s' % (R_HEADS + branch.name, dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001400 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001401 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001402 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001403 opts += ['t=%s' % p for p in hashtags]
Mike Frysingerfc1b18a2020-02-24 15:38:07 -05001404 opts += ['l=%s' % p for p in labels]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001405
David Pursehousef25a3702018-11-14 19:01:22 -08001406 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001407 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001408 if notify:
1409 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001410 if private:
1411 opts += ['private']
1412 if wip:
1413 opts += ['wip']
1414 if opts:
1415 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001416 cmd.append(ref_spec)
1417
Anthony King7bdac712014-07-16 12:56:40 +01001418 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001419 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001420
1421 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1422 self.bare_git.UpdateRef(R_PUB + branch.name,
1423 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001424 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001425
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001426# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001427 def _ExtractArchive(self, tarpath, path=None):
1428 """Extract the given tar on its current location
1429
1430 Args:
1431 - tarpath: The path to the actual tar file
1432
1433 """
1434 try:
1435 with tarfile.open(tarpath, 'r') as tar:
1436 tar.extractall(path=path)
1437 return True
1438 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001439 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001440 return False
1441
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001442 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001443 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001444 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001445 is_new=None,
1446 current_branch_only=False,
1447 force_sync=False,
1448 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001449 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001450 archive=False,
1451 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001452 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001453 submodules=False,
1454 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001455 """Perform only the network IO portion of the sync process.
1456 Local working directory/branch state is not affected.
1457 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001458 if archive and not isinstance(self, MetaProject):
1459 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001460 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001461 return False
1462
1463 name = self.relpath.replace('\\', '/')
1464 name = name.replace('/', '_')
1465 tarpath = '%s.tar' % name
1466 topdir = self.manifest.topdir
1467
1468 try:
1469 self._FetchArchive(tarpath, cwd=topdir)
1470 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001471 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001472 return False
1473
1474 # From now on, we only need absolute tarpath
1475 tarpath = os.path.join(topdir, tarpath)
1476
1477 if not self._ExtractArchive(tarpath, path=topdir):
1478 return False
1479 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001480 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001481 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001482 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001483 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001484 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001485 if is_new is None:
1486 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001487 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001488 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001489 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001490 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001491 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001492
1493 if is_new:
1494 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1495 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001496 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001497 # This works for both absolute and relative alternate directories.
1498 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001499 except IOError:
1500 alt_dir = None
1501 else:
1502 alt_dir = None
1503
Mike Frysingere50b6a72020-02-19 01:45:48 -05001504 if (clone_bundle
1505 and alt_dir is None
1506 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001507 is_new = False
1508
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001509 if not current_branch_only:
1510 if self.sync_c:
1511 current_branch_only = True
1512 elif not self.manifest._loaded:
1513 # Manifest cannot check defaults until it syncs.
1514 current_branch_only = False
1515 elif self.manifest.default.sync_c:
1516 current_branch_only = True
1517
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001518 if not self.sync_tags:
1519 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001520
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001521 if self.clone_depth:
1522 depth = self.clone_depth
1523 else:
1524 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1525
Mike Frysinger521d01b2020-02-17 01:51:49 -05001526 # See if we can skip the network fetch entirely.
1527 if not (optimized_fetch and
1528 (ID_RE.match(self.revisionExpr) and
1529 self._CheckForImmutableRevision())):
1530 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001531 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1532 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001533 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001534 submodules=submodules, force_sync=force_sync,
1535 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001536 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001537
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001538 mp = self.manifest.manifestProject
1539 dissociate = mp.config.GetBoolean('repo.dissociate')
1540 if dissociate:
1541 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1542 if os.path.exists(alternates_file):
1543 cmd = ['repack', '-a', '-d']
1544 if GitCommand(self, cmd, bare=True).Wait() != 0:
1545 return False
1546 platform_utils.remove(alternates_file)
1547
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001548 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001549 self._InitMRef()
1550 else:
1551 self._InitMirrorHead()
1552 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001553 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001554 except OSError:
1555 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001557
1558 def PostRepoUpgrade(self):
1559 self._InitHooks()
1560
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001561 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001562 if self.manifest.isGitcClient:
1563 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001564 for copyfile in self.copyfiles:
1565 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001566 for linkfile in self.linkfiles:
1567 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001568
Julien Camperguedd654222014-01-09 16:21:37 +01001569 def GetCommitRevisionId(self):
1570 """Get revisionId of a commit.
1571
1572 Use this method instead of GetRevisionId to get the id of the commit rather
1573 than the id of the current git object (for example, a tag)
1574
1575 """
1576 if not self.revisionExpr.startswith(R_TAGS):
1577 return self.GetRevisionId(self._allrefs)
1578
1579 try:
1580 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1581 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001582 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1583 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001584
David Pursehouse8a68ff92012-09-24 12:15:13 +09001585 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001586 if self.revisionId:
1587 return self.revisionId
1588
1589 rem = self.GetRemote(self.remote.name)
1590 rev = rem.ToLocal(self.revisionExpr)
1591
David Pursehouse8a68ff92012-09-24 12:15:13 +09001592 if all_refs is not None and rev in all_refs:
1593 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001594
1595 try:
1596 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1597 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001598 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1599 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001600
Martin Kellye4e94d22017-03-21 16:05:12 -07001601 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001602 """Perform only the local IO portion of the sync process.
1603 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001604 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001605 if not os.path.exists(self.gitdir):
1606 syncbuf.fail(self,
1607 'Cannot checkout %s due to missing network sync; Run '
1608 '`repo sync -n %s` first.' %
1609 (self.name, self.name))
1610 return
1611
Martin Kellye4e94d22017-03-21 16:05:12 -07001612 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001613 all_refs = self.bare_ref.all
1614 self.CleanPublishedCache(all_refs)
1615 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001616
David Pursehouse1d947b32012-10-25 12:23:11 +09001617 def _doff():
1618 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001619 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001620
Martin Kellye4e94d22017-03-21 16:05:12 -07001621 def _dosubmodules():
1622 self._SyncSubmodules(quiet=True)
1623
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001624 head = self.work_git.GetHead()
1625 if head.startswith(R_HEADS):
1626 branch = head[len(R_HEADS):]
1627 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001628 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001629 except KeyError:
1630 head = None
1631 else:
1632 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001633
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001634 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001635 # Currently on a detached HEAD. The user is assumed to
1636 # not have any local modifications worth worrying about.
1637 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001638 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001639 syncbuf.fail(self, _PriorSyncFailedError())
1640 return
1641
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001642 if head == revid:
1643 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001644 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001645 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001646 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001647 # The copy/linkfile config may have changed.
1648 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001649 return
1650 else:
1651 lost = self._revlist(not_rev(revid), HEAD)
1652 if lost:
1653 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001654
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001655 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001656 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001657 if submodules:
1658 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001659 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001660 syncbuf.fail(self, e)
1661 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001662 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001663 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001665 if head == revid:
1666 # No changes; don't do anything further.
1667 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001668 # The copy/linkfile config may have changed.
1669 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001670 return
1671
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001672 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001673
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001674 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001675 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001676 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001677 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001678 syncbuf.info(self,
1679 "leaving %s; does not track upstream",
1680 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001682 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001683 if submodules:
1684 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001685 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001686 syncbuf.fail(self, e)
1687 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001688 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001689 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001690
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001691 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001692
1693 # See if we can perform a fast forward merge. This can happen if our
1694 # branch isn't in the exact same state as we last published.
1695 try:
1696 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1697 # Skip the published logic.
1698 pub = False
1699 except GitError:
1700 pub = self.WasPublished(branch.name, all_refs)
1701
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001702 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001703 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001704 if not_merged:
1705 if upstream_gain:
1706 # The user has published this branch and some of those
1707 # commits are not yet merged upstream. We do not want
1708 # to rewrite the published commits so we punt.
1709 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001710 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001711 "branch %s is published (but not merged) and is now "
1712 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001713 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001714 elif pub == head:
1715 # All published commits are merged, and thus we are a
1716 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001717 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001718 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001719 if submodules:
1720 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001721 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001722
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001723 # Examine the local commits not in the remote. Find the
1724 # last one attributed to this user, if any.
1725 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001726 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001727 last_mine = None
1728 cnt_mine = 0
1729 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001730 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001731 if committer_email == self.UserEmail:
1732 last_mine = commit_id
1733 cnt_mine += 1
1734
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001735 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001736 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737
1738 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001739 syncbuf.fail(self, _DirtyError())
1740 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001742 # If the upstream switched on us, warn the user.
1743 #
1744 if branch.merge != self.revisionExpr:
1745 if branch.merge and self.revisionExpr:
1746 syncbuf.info(self,
1747 'manifest switched %s...%s',
1748 branch.merge,
1749 self.revisionExpr)
1750 elif branch.merge:
1751 syncbuf.info(self,
1752 'manifest no longer tracks %s',
1753 branch.merge)
1754
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001755 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001756 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001757 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001758 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001759 syncbuf.info(self,
1760 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001761 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001763 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001764 if not ID_RE.match(self.revisionExpr):
1765 # in case of manifest sync the revisionExpr might be a SHA1
1766 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001767 if not branch.merge.startswith('refs/'):
1768 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001769 branch.Save()
1770
Mike Pontillod3153822012-02-28 11:53:24 -08001771 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001772 def _docopyandlink():
1773 self._CopyAndLinkFiles()
1774
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001775 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001776 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001777 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001778 if submodules:
1779 syncbuf.later2(self, _dosubmodules)
1780 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001781 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001783 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001784 if submodules:
1785 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001786 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001787 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001788 syncbuf.fail(self, e)
1789 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001790 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001791 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001792 if submodules:
1793 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001794
Mike Frysingere6a202f2019-08-02 15:57:57 -04001795 def AddCopyFile(self, src, dest, topdir):
1796 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001797
Mike Frysingere6a202f2019-08-02 15:57:57 -04001798 No filesystem changes occur here. Actual copying happens later on.
1799
1800 Paths should have basic validation run on them before being queued.
1801 Further checking will be handled when the actual copy happens.
1802 """
1803 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1804
1805 def AddLinkFile(self, src, dest, topdir):
1806 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1807
1808 No filesystem changes occur here. Actual linking happens later on.
1809
1810 Paths should have basic validation run on them before being queued.
1811 Further checking will be handled when the actual link happens.
1812 """
1813 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001814
James W. Mills24c13082012-04-12 15:04:13 -05001815 def AddAnnotation(self, name, value, keep):
1816 self.annotations.append(_Annotation(name, value, keep))
1817
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001818 def DownloadPatchSet(self, change_id, patch_id):
1819 """Download a single patch set of a single change to FETCH_HEAD.
1820 """
1821 remote = self.GetRemote(self.remote.name)
1822
1823 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001824 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001825 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001826 if GitCommand(self, cmd, bare=True).Wait() != 0:
1827 return None
1828 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001829 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001830 change_id,
1831 patch_id,
1832 self.bare_git.rev_parse('FETCH_HEAD'))
1833
Mike Frysingerc0d18662020-02-19 19:19:18 -05001834 def DeleteWorktree(self, quiet=False, force=False):
1835 """Delete the source checkout and any other housekeeping tasks.
1836
1837 This currently leaves behind the internal .repo/ cache state. This helps
1838 when switching branches or manifest changes get reverted as we don't have
1839 to redownload all the git objects. But we should do some GC at some point.
1840
1841 Args:
1842 quiet: Whether to hide normal messages.
1843 force: Always delete tree even if dirty.
1844
1845 Returns:
1846 True if the worktree was completely cleaned out.
1847 """
1848 if self.IsDirty():
1849 if force:
1850 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1851 (self.relpath,), file=sys.stderr)
1852 else:
1853 print('error: %s: Cannot remove project: uncommitted changes are '
1854 'present.\n' % (self.relpath,), file=sys.stderr)
1855 return False
1856
1857 if not quiet:
1858 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1859
1860 # Unlock and delink from the main worktree. We don't use git's worktree
1861 # remove because it will recursively delete projects -- we handle that
1862 # ourselves below. https://crbug.com/git/48
1863 if self.use_git_worktrees:
1864 needle = platform_utils.realpath(self.gitdir)
1865 # Find the git worktree commondir under .repo/worktrees/.
1866 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1867 assert output.startswith('worktree '), output
1868 commondir = output[9:]
1869 # Walk each of the git worktrees to see where they point.
1870 configs = os.path.join(commondir, 'worktrees')
1871 for name in os.listdir(configs):
1872 gitdir = os.path.join(configs, name, 'gitdir')
1873 with open(gitdir) as fp:
1874 relpath = fp.read().strip()
1875 # Resolve the checkout path and see if it matches this project.
1876 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1877 if fullpath == needle:
1878 platform_utils.rmtree(os.path.join(configs, name))
1879
1880 # Delete the .git directory first, so we're less likely to have a partially
1881 # working git repository around. There shouldn't be any git projects here,
1882 # so rmtree works.
1883
1884 # Try to remove plain files first in case of git worktrees. If this fails
1885 # for any reason, we'll fall back to rmtree, and that'll display errors if
1886 # it can't remove things either.
1887 try:
1888 platform_utils.remove(self.gitdir)
1889 except OSError:
1890 pass
1891 try:
1892 platform_utils.rmtree(self.gitdir)
1893 except OSError as e:
1894 if e.errno != errno.ENOENT:
1895 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1896 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1897 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1898 return False
1899
1900 # Delete everything under the worktree, except for directories that contain
1901 # another git project.
1902 dirs_to_remove = []
1903 failed = False
1904 for root, dirs, files in platform_utils.walk(self.worktree):
1905 for f in files:
1906 path = os.path.join(root, f)
1907 try:
1908 platform_utils.remove(path)
1909 except OSError as e:
1910 if e.errno != errno.ENOENT:
1911 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1912 failed = True
1913 dirs[:] = [d for d in dirs
1914 if not os.path.lexists(os.path.join(root, d, '.git'))]
1915 dirs_to_remove += [os.path.join(root, d) for d in dirs
1916 if os.path.join(root, d) not in dirs_to_remove]
1917 for d in reversed(dirs_to_remove):
1918 if platform_utils.islink(d):
1919 try:
1920 platform_utils.remove(d)
1921 except OSError as e:
1922 if e.errno != errno.ENOENT:
1923 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1924 failed = True
1925 elif not platform_utils.listdir(d):
1926 try:
1927 platform_utils.rmdir(d)
1928 except OSError as e:
1929 if e.errno != errno.ENOENT:
1930 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1931 failed = True
1932 if failed:
1933 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1934 file=sys.stderr)
1935 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1936 return False
1937
1938 # Try deleting parent dirs if they are empty.
1939 path = self.worktree
1940 while path != self.manifest.topdir:
1941 try:
1942 platform_utils.rmdir(path)
1943 except OSError as e:
1944 if e.errno != errno.ENOENT:
1945 break
1946 path = os.path.dirname(path)
1947
1948 return True
1949
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001950# Branch Management ##
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001951 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001952 """Create a new branch off the manifest's revision.
1953 """
Simran Basib9a1b732015-08-20 12:19:28 -07001954 if not branch_merge:
1955 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001956 head = self.work_git.GetHead()
1957 if head == (R_HEADS + name):
1958 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001959
David Pursehouse8a68ff92012-09-24 12:15:13 +09001960 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001961 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001962 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001963 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001964 capture_stdout=True,
1965 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001966
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001967 branch = self.GetBranch(name)
1968 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001969 branch.merge = branch_merge
1970 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1971 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001972
1973 if revision is None:
1974 revid = self.GetRevisionId(all_refs)
1975 else:
1976 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001977
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001978 if head.startswith(R_HEADS):
1979 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001980 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001981 except KeyError:
1982 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001983 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05001984 ref = R_HEADS + name
1985 self.work_git.update_ref(ref, revid)
1986 self.work_git.symbolic_ref(HEAD, ref)
1987 branch.Save()
1988 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001989
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001990 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001991 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001992 capture_stdout=True,
1993 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001994 branch.Save()
1995 return True
1996 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001997
Wink Saville02d79452009-04-10 13:01:24 -07001998 def CheckoutBranch(self, name):
1999 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002000
2001 Args:
2002 name: The name of the branch to checkout.
2003
2004 Returns:
2005 True if the checkout succeeded; False if it didn't; None if the branch
2006 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002007 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002008 rev = R_HEADS + name
2009 head = self.work_git.GetHead()
2010 if head == rev:
2011 # Already on the branch
2012 #
2013 return True
Wink Saville02d79452009-04-10 13:01:24 -07002014
David Pursehouse8a68ff92012-09-24 12:15:13 +09002015 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002016 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002017 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002018 except KeyError:
2019 # Branch does not exist in this project
2020 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002021 return None
Wink Saville02d79452009-04-10 13:01:24 -07002022
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002023 if head.startswith(R_HEADS):
2024 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002025 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002026 except KeyError:
2027 head = None
2028
2029 if head == revid:
2030 # Same revision; just update HEAD to point to the new
2031 # target branch, but otherwise take no other action.
2032 #
Mike Frysinger9f91c432020-02-23 23:24:40 -05002033 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD),
2034 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002035 return True
2036
2037 return GitCommand(self,
2038 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002039 capture_stdout=True,
2040 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002041
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002042 def AbandonBranch(self, name):
2043 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002044
2045 Args:
2046 name: The name of the branch to abandon.
2047
2048 Returns:
2049 True if the abandon succeeded; False if it didn't; None if the branch
2050 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002051 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002052 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002053 all_refs = self.bare_ref.all
2054 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002055 # Doesn't exist
2056 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002057
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002058 head = self.work_git.GetHead()
2059 if head == rev:
2060 # We can't destroy the branch while we are sitting
2061 # on it. Switch to a detached HEAD.
2062 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002063 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002064
David Pursehouse8a68ff92012-09-24 12:15:13 +09002065 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002066 if head == revid:
Mike Frysinger9f91c432020-02-23 23:24:40 -05002067 _lwrite(self.work_git.GetDotgitPath(subpath=HEAD), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002068 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002069 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002070
2071 return GitCommand(self,
2072 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002073 capture_stdout=True,
2074 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002075
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002076 def PruneHeads(self):
2077 """Prune any topic branches already merged into upstream.
2078 """
2079 cb = self.CurrentBranch
2080 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002081 left = self._allrefs
2082 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002083 if name.startswith(R_HEADS):
2084 name = name[len(R_HEADS):]
2085 if cb is None or name != cb:
2086 kill.append(name)
2087
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002088 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002089 if cb is not None \
2090 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002091 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002092 self.work_git.DetachHead(HEAD)
2093 kill.append(cb)
2094
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002095 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002096 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002097
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002098 try:
2099 self.bare_git.DetachHead(rev)
2100
2101 b = ['branch', '-d']
2102 b.extend(kill)
2103 b = GitCommand(self, b, bare=True,
2104 capture_stdout=True,
2105 capture_stderr=True)
2106 b.Wait()
2107 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002108 if ID_RE.match(old):
2109 self.bare_git.DetachHead(old)
2110 else:
2111 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002112 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002113
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002114 for branch in kill:
2115 if (R_HEADS + branch) not in left:
2116 self.CleanPublishedCache()
2117 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002118
2119 if cb and cb not in kill:
2120 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002121 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002122
2123 kept = []
2124 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002125 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002126 branch = self.GetBranch(branch)
2127 base = branch.LocalMerge
2128 if not base:
2129 base = rev
2130 kept.append(ReviewableBranch(self, branch, base))
2131 return kept
2132
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002133# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002134 def GetRegisteredSubprojects(self):
2135 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002136
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002137 def rec(subprojects):
2138 if not subprojects:
2139 return
2140 result.extend(subprojects)
2141 for p in subprojects:
2142 rec(p.subprojects)
2143 rec(self.subprojects)
2144 return result
2145
2146 def _GetSubmodules(self):
2147 # Unfortunately we cannot call `git submodule status --recursive` here
2148 # because the working tree might not exist yet, and it cannot be used
2149 # without a working tree in its current implementation.
2150
2151 def get_submodules(gitdir, rev):
2152 # Parse .gitmodules for submodule sub_paths and sub_urls
2153 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2154 if not sub_paths:
2155 return []
2156 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2157 # revision of submodule repository
2158 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2159 submodules = []
2160 for sub_path, sub_url in zip(sub_paths, sub_urls):
2161 try:
2162 sub_rev = sub_revs[sub_path]
2163 except KeyError:
2164 # Ignore non-exist submodules
2165 continue
2166 submodules.append((sub_rev, sub_path, sub_url))
2167 return submodules
2168
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002169 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2170 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002171
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002172 def parse_gitmodules(gitdir, rev):
2173 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2174 try:
Anthony King7bdac712014-07-16 12:56:40 +01002175 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2176 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002177 except GitError:
2178 return [], []
2179 if p.Wait() != 0:
2180 return [], []
2181
2182 gitmodules_lines = []
2183 fd, temp_gitmodules_path = tempfile.mkstemp()
2184 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002185 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002186 os.close(fd)
2187 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002188 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2189 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002190 if p.Wait() != 0:
2191 return [], []
2192 gitmodules_lines = p.stdout.split('\n')
2193 except GitError:
2194 return [], []
2195 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002196 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002197
2198 names = set()
2199 paths = {}
2200 urls = {}
2201 for line in gitmodules_lines:
2202 if not line:
2203 continue
2204 m = re_path.match(line)
2205 if m:
2206 names.add(m.group(1))
2207 paths[m.group(1)] = m.group(2)
2208 continue
2209 m = re_url.match(line)
2210 if m:
2211 names.add(m.group(1))
2212 urls[m.group(1)] = m.group(2)
2213 continue
2214 names = sorted(names)
2215 return ([paths.get(name, '') for name in names],
2216 [urls.get(name, '') for name in names])
2217
2218 def git_ls_tree(gitdir, rev, paths):
2219 cmd = ['ls-tree', rev, '--']
2220 cmd.extend(paths)
2221 try:
Anthony King7bdac712014-07-16 12:56:40 +01002222 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2223 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002224 except GitError:
2225 return []
2226 if p.Wait() != 0:
2227 return []
2228 objects = {}
2229 for line in p.stdout.split('\n'):
2230 if not line.strip():
2231 continue
2232 object_rev, object_path = line.split()[2:4]
2233 objects[object_path] = object_rev
2234 return objects
2235
2236 try:
2237 rev = self.GetRevisionId()
2238 except GitError:
2239 return []
2240 return get_submodules(self.gitdir, rev)
2241
2242 def GetDerivedSubprojects(self):
2243 result = []
2244 if not self.Exists:
2245 # If git repo does not exist yet, querying its submodules will
2246 # mess up its states; so return here.
2247 return result
2248 for rev, path, url in self._GetSubmodules():
2249 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002250 relpath, worktree, gitdir, objdir = \
2251 self.manifest.GetSubprojectPaths(self, name, path)
2252 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002253 if project:
2254 result.extend(project.GetDerivedSubprojects())
2255 continue
David James8d201162013-10-11 17:03:19 -07002256
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002257 if url.startswith('..'):
2258 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002259 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002260 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002261 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002262 review=self.remote.review,
2263 revision=self.remote.revision)
2264 subproject = Project(manifest=self.manifest,
2265 name=name,
2266 remote=remote,
2267 gitdir=gitdir,
2268 objdir=objdir,
2269 worktree=worktree,
2270 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002271 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002272 revisionId=rev,
2273 rebase=self.rebase,
2274 groups=self.groups,
2275 sync_c=self.sync_c,
2276 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002277 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002278 parent=self,
2279 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002280 result.append(subproject)
2281 result.extend(subproject.GetDerivedSubprojects())
2282 return result
2283
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002284# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002285 def EnableRepositoryExtension(self, key, value='true', version=1):
2286 """Enable git repository extension |key| with |value|.
2287
2288 Args:
2289 key: The extension to enabled. Omit the "extensions." prefix.
2290 value: The value to use for the extension.
2291 version: The minimum git repository version needed.
2292 """
2293 # Make sure the git repo version is new enough already.
2294 found_version = self.config.GetInt('core.repositoryFormatVersion')
2295 if found_version is None:
2296 found_version = 0
2297 if found_version < version:
2298 self.config.SetString('core.repositoryFormatVersion', str(version))
2299
2300 # Enable the extension!
2301 self.config.SetString('extensions.%s' % (key,), value)
2302
Zac Livingstone4332262017-06-16 08:56:09 -06002303 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002304 try:
2305 # if revision (sha or tag) is not present then following function
2306 # throws an error.
2307 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2308 return True
2309 except GitError:
2310 # There is no such persistent revision. We have to fetch it.
2311 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002312
Julien Campergue335f5ef2013-10-16 11:02:35 +02002313 def _FetchArchive(self, tarpath, cwd=None):
2314 cmd = ['archive', '-v', '-o', tarpath]
2315 cmd.append('--remote=%s' % self.remote.url)
2316 cmd.append('--prefix=%s/' % self.relpath)
2317 cmd.append(self.revisionExpr)
2318
2319 command = GitCommand(self, cmd, cwd=cwd,
2320 capture_stdout=True,
2321 capture_stderr=True)
2322
2323 if command.Wait() != 0:
2324 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2325
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002326 def _RemoteFetch(self, name=None,
2327 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002328 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002329 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002330 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002331 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002332 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002333 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002334 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002335 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002336 force_sync=False,
2337 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002338
2339 is_sha1 = False
2340 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002341 # The depth should not be used when fetching to a mirror because
2342 # it will result in a shallow repository that cannot be cloned or
2343 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002344 # The repo project should also never be synced with partial depth.
2345 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2346 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002347
Shawn Pearce69e04d82014-01-29 12:48:54 -08002348 if depth:
2349 current_branch_only = True
2350
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002351 if ID_RE.match(self.revisionExpr) is not None:
2352 is_sha1 = True
2353
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002354 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002355 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002356 # this is a tag and its sha1 value should never change
2357 tag_name = self.revisionExpr[len(R_TAGS):]
2358
2359 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002360 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002361 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002362 print('Skipped fetching project %s (already have persistent ref)'
2363 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002364 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002365 if is_sha1 and not depth:
2366 # When syncing a specific commit and --depth is not set:
2367 # * if upstream is explicitly specified and is not a sha1, fetch only
2368 # upstream as users expect only upstream to be fetch.
2369 # Note: The commit might not be in upstream in which case the sync
2370 # will fail.
2371 # * otherwise, fetch all branches to make sure we end up with the
2372 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002373 if self.upstream:
2374 current_branch_only = not ID_RE.match(self.upstream)
2375 else:
2376 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002377
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002378 if not name:
2379 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002380
2381 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002382 remote = self.GetRemote(name)
2383 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002384 ssh_proxy = True
2385
Shawn O. Pearce88443382010-10-08 10:02:09 +02002386 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002387 if alt_dir and 'objects' == os.path.basename(alt_dir):
2388 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002389 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2390 remote = self.GetRemote(name)
2391
David Pursehouse8a68ff92012-09-24 12:15:13 +09002392 all_refs = self.bare_ref.all
2393 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002394 tmp = set()
2395
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302396 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002397 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002398 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002399 all_refs[r] = ref_id
2400 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002401 continue
2402
David Pursehouse8a68ff92012-09-24 12:15:13 +09002403 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002404 continue
2405
David Pursehouse8a68ff92012-09-24 12:15:13 +09002406 r = 'refs/_alt/%s' % ref_id
2407 all_refs[r] = ref_id
2408 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002409 tmp.add(r)
2410
heping3d7bbc92017-04-12 19:51:47 +08002411 tmp_packed_lines = []
2412 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002413
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302414 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002415 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002416 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002417 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002418 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002419
heping3d7bbc92017-04-12 19:51:47 +08002420 tmp_packed = ''.join(tmp_packed_lines)
2421 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002422 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002423 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002424 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002425
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002426 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002427
Xin Li745be2e2019-06-03 11:24:30 -07002428 if clone_filter:
2429 git_require((2, 19, 0), fail=True, msg='partial clones')
2430 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002431 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002432
Conley Owensf97e8382015-01-21 11:12:46 -08002433 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002434 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002435 else:
2436 # If this repo has shallow objects, then we don't know which refs have
2437 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2438 # do this with projects that don't have shallow objects, since it is less
2439 # efficient.
2440 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2441 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002442
Mike Frysinger4847e052020-02-22 00:07:35 -05002443 if not verbose:
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002444 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002445 if not quiet and sys.stdout.isatty():
2446 cmd.append('--progress')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002447 if not self.worktree:
2448 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002449 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002450
Mike Frysingere57f1142019-03-18 21:27:54 -04002451 if force_sync:
2452 cmd.append('--force')
2453
David Pursehouse74cfd272015-10-14 10:50:15 +09002454 if prune:
2455 cmd.append('--prune')
2456
Martin Kellye4e94d22017-03-21 16:05:12 -07002457 if submodules:
2458 cmd.append('--recurse-submodules=on-demand')
2459
Kuang-che Wu6856f982019-11-25 12:37:55 +08002460 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002461 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002462 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002463 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002464 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002465 spec.append('tag')
2466 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002467
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302468 if self.manifest.IsMirror and not current_branch_only:
2469 branch = None
2470 else:
2471 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002472 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002473 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002474 # Shallow checkout of a specific commit, fetch from that commit and not
2475 # the heads only as the commit might be deeper in the history.
2476 spec.append(branch)
2477 else:
2478 if is_sha1:
2479 branch = self.upstream
2480 if branch is not None and branch.strip():
2481 if not branch.startswith('refs/'):
2482 branch = R_HEADS + branch
2483 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2484
2485 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2486 # whole repo.
2487 if self.manifest.IsMirror and not spec:
2488 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2489
2490 # If using depth then we should not get all the tags since they may
2491 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002492 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002493 cmd.append('--no-tags')
2494 else:
2495 cmd.append('--tags')
2496 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2497
Conley Owens80b87fe2014-05-09 17:13:44 -07002498 cmd.extend(spec)
2499
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002500 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002501 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002502 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
Mike Frysinger4847e052020-02-22 00:07:35 -05002503 merge_output=True, capture_stdout=quiet)
John L. Villalovos126e2982015-01-29 21:58:12 -08002504 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002505 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002506 ok = True
2507 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002508 # If needed, run the 'git remote prune' the first time through the loop
2509 elif (not _i and
2510 "error:" in gitcmd.stderr and
2511 "git remote prune" in gitcmd.stderr):
2512 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002513 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002514 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002515 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002516 break
2517 continue
Brian Harring14a66742012-09-28 20:21:57 -07002518 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002519 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2520 # in sha1 mode, we just tried sync'ing from the upstream field; it
2521 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002522 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002523 elif ret < 0:
2524 # Git died with a signal, exit immediately
2525 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002526 if not verbose:
2527 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002528 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002529
2530 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002531 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002532 if old_packed != '':
2533 _lwrite(packed_refs, old_packed)
2534 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002535 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002536 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002537
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002538 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002539 # We just synced the upstream given branch; verify we
2540 # got what we wanted, else trigger a second run of all
2541 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002542 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002543 # Sync the current branch only with depth set to None.
2544 # We always pass depth=None down to avoid infinite recursion.
2545 return self._RemoteFetch(
2546 name=name, quiet=quiet, verbose=verbose,
2547 current_branch_only=current_branch_only and depth,
2548 initial=False, alt_dir=alt_dir,
2549 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002550
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002551 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002552
Mike Frysingere50b6a72020-02-19 01:45:48 -05002553 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002554 if initial and \
2555 (self.manifest.manifestProject.config.GetString('repo.depth') or
2556 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002557 return False
2558
2559 remote = self.GetRemote(self.remote.name)
2560 bundle_url = remote.url + '/clone.bundle'
2561 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002562 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2563 'persistent-http',
2564 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002565 return False
2566
2567 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2568 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2569
2570 exist_dst = os.path.exists(bundle_dst)
2571 exist_tmp = os.path.exists(bundle_tmp)
2572
2573 if not initial and not exist_dst and not exist_tmp:
2574 return False
2575
2576 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002577 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2578 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002579 if not exist_dst:
2580 return False
2581
2582 cmd = ['fetch']
Mike Frysinger4847e052020-02-22 00:07:35 -05002583 if not verbose:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002584 cmd.append('--quiet')
Mike Frysinger4847e052020-02-22 00:07:35 -05002585 if not quiet and sys.stdout.isatty():
2586 cmd.append('--progress')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002587 if not self.worktree:
2588 cmd.append('--update-head-ok')
2589 cmd.append(bundle_dst)
2590 for f in remote.fetch:
2591 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002592 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002593
2594 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002595 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002596 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002597 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002598 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002599 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002600
Mike Frysingere50b6a72020-02-19 01:45:48 -05002601 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002602 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002603 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002604
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002605 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002606 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002607 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002608 if os.path.exists(tmpPath):
2609 size = os.stat(tmpPath).st_size
2610 if size >= 1024:
2611 cmd += ['--continue-at', '%d' % (size,)]
2612 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002613 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002614 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002615 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002616 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002617 if proxy:
2618 cmd += ['--proxy', proxy]
2619 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2620 cmd += ['--proxy', os.environ['http_proxy']]
2621 if srcUrl.startswith('persistent-https'):
2622 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2623 elif srcUrl.startswith('persistent-http'):
2624 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002625 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002626
Dave Borowitz137d0132015-01-02 11:12:54 -08002627 if IsTrace():
2628 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002629 if verbose:
2630 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2631 stdout = None if verbose else subprocess.PIPE
2632 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002633 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002634 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002635 except OSError:
2636 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002637
Mike Frysingere50b6a72020-02-19 01:45:48 -05002638 (output, _) = proc.communicate()
2639 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002640
Dave Borowitz137d0132015-01-02 11:12:54 -08002641 if curlret == 22:
2642 # From curl man page:
2643 # 22: HTTP page not retrieved. The requested url was not found or
2644 # returned another error with the HTTP error code being 400 or above.
2645 # This return code only appears if -f, --fail is used.
Mike Frysinger4847e052020-02-22 00:07:35 -05002646 if verbose:
2647 print('Server does not provide clone.bundle; ignoring.')
Dave Borowitz137d0132015-01-02 11:12:54 -08002648 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002649 elif curlret and not verbose and output:
2650 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002651
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002652 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002653 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002654 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002655 return True
2656 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002657 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002658 return False
2659 else:
2660 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002661
Kris Giesingc8d882a2014-12-23 13:02:32 -08002662 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002663 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002664 with open(path, 'rb') as f:
2665 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002666 return True
2667 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002668 if not quiet:
2669 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002670 return False
2671 except OSError:
2672 return False
2673
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002674 def _Checkout(self, rev, quiet=False):
2675 cmd = ['checkout']
2676 if quiet:
2677 cmd.append('-q')
2678 cmd.append(rev)
2679 cmd.append('--')
2680 if GitCommand(self, cmd).Wait() != 0:
2681 if self._allrefs:
2682 raise GitError('%s checkout %s ' % (self.name, rev))
2683
Mike Frysingerea431762020-03-22 12:14:01 -04002684 def _CherryPick(self, rev, ffonly=False):
Pierre Tardye5a21222011-03-24 16:28:18 +01002685 cmd = ['cherry-pick']
Mike Frysingerea431762020-03-22 12:14:01 -04002686 if ffonly:
2687 cmd.append('--ff')
Pierre Tardye5a21222011-03-24 16:28:18 +01002688 cmd.append(rev)
2689 cmd.append('--')
2690 if GitCommand(self, cmd).Wait() != 0:
2691 if self._allrefs:
2692 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2693
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302694 def _LsRemote(self, refs):
2695 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302696 p = GitCommand(self, cmd, capture_stdout=True)
2697 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002698 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302699 return None
2700
Anthony King7bdac712014-07-16 12:56:40 +01002701 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002702 cmd = ['revert']
2703 cmd.append('--no-edit')
2704 cmd.append(rev)
2705 cmd.append('--')
2706 if GitCommand(self, cmd).Wait() != 0:
2707 if self._allrefs:
2708 raise GitError('%s revert %s ' % (self.name, rev))
2709
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002710 def _ResetHard(self, rev, quiet=True):
2711 cmd = ['reset', '--hard']
2712 if quiet:
2713 cmd.append('-q')
2714 cmd.append(rev)
2715 if GitCommand(self, cmd).Wait() != 0:
2716 raise GitError('%s reset --hard %s ' % (self.name, rev))
2717
Martin Kellye4e94d22017-03-21 16:05:12 -07002718 def _SyncSubmodules(self, quiet=True):
2719 cmd = ['submodule', 'update', '--init', '--recursive']
2720 if quiet:
2721 cmd.append('-q')
2722 if GitCommand(self, cmd).Wait() != 0:
2723 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2724
Anthony King7bdac712014-07-16 12:56:40 +01002725 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002726 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002727 if onto is not None:
2728 cmd.extend(['--onto', onto])
2729 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002730 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731 raise GitError('%s rebase %s ' % (self.name, upstream))
2732
Pierre Tardy3d125942012-05-04 12:18:12 +02002733 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002734 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002735 if ffonly:
2736 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002737 if GitCommand(self, cmd).Wait() != 0:
2738 raise GitError('%s merge %s ' % (self.name, head))
2739
David Pursehousee8ace262020-02-13 12:41:15 +09002740 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002741 init_git_dir = not os.path.exists(self.gitdir)
2742 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002743 try:
2744 # Initialize the bare repository, which contains all of the objects.
2745 if init_obj_dir:
2746 os.makedirs(self.objdir)
2747 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002748
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002749 if self.use_git_worktrees:
2750 # Set up the m/ space to point to the worktree-specific ref space.
2751 # We'll update the worktree-specific ref space on each checkout.
2752 if self.manifest.branch:
2753 self.bare_git.symbolic_ref(
2754 '-m', 'redirecting to worktree scope',
2755 R_M + self.manifest.branch,
2756 R_WORKTREE_M + self.manifest.branch)
2757
2758 # Enable per-worktree config file support if possible. This is more a
2759 # nice-to-have feature for users rather than a hard requirement.
2760 if git_require((2, 19, 0)):
2761 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002762
Kevin Degib1a07b82015-07-27 13:33:43 -06002763 # If we have a separate directory to hold refs, initialize it as well.
2764 if self.objdir != self.gitdir:
2765 if init_git_dir:
2766 os.makedirs(self.gitdir)
2767
2768 if init_obj_dir or init_git_dir:
2769 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2770 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002771 try:
2772 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2773 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002774 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002775 print("Retrying clone after deleting %s" %
2776 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002777 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002778 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2779 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002780 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002781 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002782 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2783 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002784 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002785 raise e
2786 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002787
Kevin Degi384b3c52014-10-16 16:02:58 -06002788 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002789 mp = self.manifest.manifestProject
2790 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002791
Kevin Degib1a07b82015-07-27 13:33:43 -06002792 if ref_dir or mirror_git:
2793 if not mirror_git:
2794 mirror_git = os.path.join(ref_dir, self.name + '.git')
2795 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2796 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002797 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2798 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002799
Kevin Degib1a07b82015-07-27 13:33:43 -06002800 if os.path.exists(mirror_git):
2801 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002802 elif os.path.exists(repo_git):
2803 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002804 elif os.path.exists(worktrees_git):
2805 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002806 else:
2807 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002808
Kevin Degib1a07b82015-07-27 13:33:43 -06002809 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002810 if not os.path.isabs(ref_dir):
2811 # The alternate directory is relative to the object database.
2812 ref_dir = os.path.relpath(ref_dir,
2813 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002814 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2815 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002816
David Pursehousee8ace262020-02-13 12:41:15 +09002817 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002818
2819 m = self.manifest.manifestProject.config
2820 for key in ['user.name', 'user.email']:
2821 if m.Has(key, include_defaults=False):
2822 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002823 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002824 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002825 if self.manifest.IsMirror:
2826 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002827 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002828 self.config.SetString('core.bare', None)
2829 except Exception:
2830 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002831 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002832 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002833 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002834 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002835
David Pursehousee8ace262020-02-13 12:41:15 +09002836 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002837 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002838 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002839
David Pursehousee8ace262020-02-13 12:41:15 +09002840 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002841 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002842 if not os.path.exists(hooks):
2843 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002844 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002845 name = os.path.basename(stock_hook)
2846
Victor Boivie65e0f352011-04-18 11:23:29 +02002847 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002848 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002849 # Don't install a Gerrit Code Review hook if this
2850 # project does not appear to use it for reviews.
2851 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002852 # Since the manifest project is one of those, but also
2853 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002854 continue
2855
2856 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002857 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002858 continue
2859 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002860 # If the files are the same, we'll leave it alone. We create symlinks
2861 # below by default but fallback to hardlinks if the OS blocks them.
2862 # So if we're here, it's probably because we made a hardlink below.
2863 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002864 if not quiet:
2865 _warn("%s: Not replacing locally modified %s hook",
2866 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002867 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002868 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002869 platform_utils.symlink(
2870 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002871 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002872 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002873 try:
2874 os.link(stock_hook, dst)
2875 except OSError:
2876 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002877 else:
2878 raise
2879
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002880 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002881 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002882 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002883 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002884 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002885 remote.review = self.remote.review
2886 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002887
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002888 if self.worktree:
2889 remote.ResetFetch(mirror=False)
2890 else:
2891 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002892 remote.Save()
2893
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002894 def _InitMRef(self):
2895 if self.manifest.branch:
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002896 if self.use_git_worktrees:
2897 # We can't update this ref with git worktrees until it exists.
2898 # We'll wait until the initial checkout to set it.
2899 if not os.path.exists(self.worktree):
2900 return
2901
2902 base = R_WORKTREE_M
2903 active_git = self.work_git
2904 else:
2905 base = R_M
2906 active_git = self.bare_git
2907
2908 self._InitAnyMRef(base + self.manifest.branch, active_git)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002909
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002910 def _InitMirrorHead(self):
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002911 self._InitAnyMRef(HEAD, self.bare_git)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002912
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002913 def _InitAnyMRef(self, ref, active_git):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002914 cur = self.bare_ref.symref(ref)
2915
2916 if self.revisionId:
2917 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2918 msg = 'manifest set to %s' % self.revisionId
2919 dst = self.revisionId + '^0'
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002920 active_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002921 else:
2922 remote = self.GetRemote(self.remote.name)
2923 dst = remote.ToLocal(self.revisionExpr)
2924 if cur != dst:
2925 msg = 'manifest set to %s' % self.revisionExpr
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05002926 active_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002927
Kevin Degi384b3c52014-10-16 16:02:58 -06002928 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002929 # Git worktrees don't use symlinks to share at all.
2930 if self.use_git_worktrees:
2931 return
2932
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002933 symlink_files = self.shareable_files[:]
2934 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002935 if share_refs:
2936 symlink_files += self.working_tree_files
2937 symlink_dirs += self.working_tree_dirs
2938 to_symlink = symlink_files + symlink_dirs
2939 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002940 # Try to self-heal a bit in simple cases.
2941 dst_path = os.path.join(destdir, name)
2942 src_path = os.path.join(srcdir, name)
2943
2944 if name in self.working_tree_dirs:
2945 # If the dir is missing under .repo/projects/, create it.
2946 if not os.path.exists(src_path):
2947 os.makedirs(src_path)
2948
2949 elif name in self.working_tree_files:
2950 # If it's a file under the checkout .git/ and the .repo/projects/ has
2951 # nothing, move the file under the .repo/projects/ tree.
2952 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2953 platform_utils.rename(dst_path, src_path)
2954
2955 # If the path exists under the .repo/projects/ and there's no symlink
2956 # under the checkout .git/, recreate the symlink.
2957 if name in self.working_tree_dirs or name in self.working_tree_files:
2958 if os.path.exists(src_path) and not os.path.exists(dst_path):
2959 platform_utils.symlink(
2960 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2961
2962 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002963 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002964 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002965 # Fail if the links are pointing to the wrong place
2966 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002967 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002968 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002969 'work tree. If you\'re comfortable with the '
2970 'possibility of losing the work tree\'s git metadata,'
2971 ' use `repo sync --force-sync {0}` to '
2972 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002973
David James8d201162013-10-11 17:03:19 -07002974 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2975 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2976
2977 Args:
2978 gitdir: The bare git repository. Must already be initialized.
2979 dotgit: The repository you would like to initialize.
2980 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2981 Only one work tree can store refs under a given |gitdir|.
2982 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2983 This saves you the effort of initializing |dotgit| yourself.
2984 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002985 symlink_files = self.shareable_files[:]
2986 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002987 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002988 symlink_files += self.working_tree_files
2989 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002990 to_symlink = symlink_files + symlink_dirs
2991
2992 to_copy = []
2993 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002994 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002995
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002996 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002997 for name in set(to_copy).union(to_symlink):
2998 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002999 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07003000 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07003001
Kevin Degi384b3c52014-10-16 16:02:58 -06003002 if os.path.lexists(dst):
3003 continue
David James8d201162013-10-11 17:03:19 -07003004
3005 # If the source dir doesn't exist, create an empty dir.
3006 if name in symlink_dirs and not os.path.lexists(src):
3007 os.makedirs(src)
3008
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003009 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07003010 platform_utils.symlink(
3011 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003012 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07003013 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003014 shutil.copytree(src, dst)
3015 elif os.path.isfile(src):
3016 shutil.copy(src, dst)
3017
Conley Owens80b87fe2014-05-09 17:13:44 -07003018 # If the source file doesn't exist, ensure the destination
3019 # file doesn't either.
3020 if name in symlink_files and not os.path.lexists(src):
3021 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003022 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003023 except OSError:
3024 pass
3025
David James8d201162013-10-11 17:03:19 -07003026 except OSError as e:
3027 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003028 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003029 else:
3030 raise
3031
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003032 def _InitGitWorktree(self):
3033 """Init the project using git worktrees."""
3034 self.bare_git.worktree('prune')
3035 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3036 self.worktree, self.GetRevisionId())
3037
3038 # Rewrite the internal state files to use relative paths between the
3039 # checkouts & worktrees.
3040 dotgit = os.path.join(self.worktree, '.git')
3041 with open(dotgit, 'r') as fp:
3042 # Figure out the checkout->worktree path.
3043 setting = fp.read()
3044 assert setting.startswith('gitdir:')
3045 git_worktree_path = setting.split(':', 1)[1].strip()
Mike Frysinger75264782020-02-21 18:55:07 -05003046 # Some platforms (e.g. Windows) won't let us update dotgit in situ because
3047 # of file permissions. Delete it and recreate it from scratch to avoid.
3048 platform_utils.remove(dotgit)
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003049 # Use relative path from checkout->worktree.
3050 with open(dotgit, 'w') as fp:
3051 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3052 file=fp)
3053 # Use relative path from worktree->checkout.
3054 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3055 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3056
Mike Frysinger21b7fbe2020-02-26 23:53:36 -05003057 self._InitMRef()
3058
Martin Kellye4e94d22017-03-21 16:05:12 -07003059 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003060 realdotgit = os.path.join(self.worktree, '.git')
3061 tmpdotgit = realdotgit + '.tmp'
3062 init_dotgit = not os.path.exists(realdotgit)
3063 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003064 if self.use_git_worktrees:
3065 self._InitGitWorktree()
3066 self._CopyAndLinkFiles()
3067 return
3068
Mike Frysingerf4545122019-11-11 04:34:16 -05003069 dotgit = tmpdotgit
3070 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3071 os.makedirs(tmpdotgit)
3072 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3073 copy_all=False)
3074 else:
3075 dotgit = realdotgit
3076
Kevin Degib1a07b82015-07-27 13:33:43 -06003077 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003078 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3079 except GitError as e:
3080 if force_sync and not init_dotgit:
3081 try:
3082 platform_utils.rmtree(dotgit)
3083 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003084 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003085 raise e
3086 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003087
Mike Frysingerf4545122019-11-11 04:34:16 -05003088 if init_dotgit:
3089 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003090
Mike Frysingerf4545122019-11-11 04:34:16 -05003091 # Now that the .git dir is fully set up, move it to its final home.
3092 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003093
Mike Frysingerf4545122019-11-11 04:34:16 -05003094 # Finish checking out the worktree.
3095 cmd = ['read-tree', '--reset', '-u']
3096 cmd.append('-v')
3097 cmd.append(HEAD)
3098 if GitCommand(self, cmd).Wait() != 0:
3099 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003100
Mike Frysingerf4545122019-11-11 04:34:16 -05003101 if submodules:
3102 self._SyncSubmodules(quiet=True)
3103 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003104
Renaud Paquay788e9622017-01-27 11:41:12 -08003105 def _get_symlink_error_message(self):
3106 if platform_utils.isWindows():
3107 return ('Unable to create symbolic link. Please re-run the command as '
3108 'Administrator, or see '
3109 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3110 'for other options.')
3111 return 'filesystem must support symlinks'
3112
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003113 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003114 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003115
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003116 def _revlist(self, *args, **kw):
3117 a = []
3118 a.extend(args)
3119 a.append('--')
3120 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003121
3122 @property
3123 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003124 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003125
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003126 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003127 """Get logs between two revisions of this project."""
3128 comp = '..'
3129 if rev1:
3130 revs = [rev1]
3131 if rev2:
3132 revs.extend([comp, rev2])
3133 cmd = ['log', ''.join(revs)]
3134 out = DiffColoring(self.config)
3135 if out.is_on and color:
3136 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003137 if pretty_format is not None:
3138 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003139 if oneline:
3140 cmd.append('--oneline')
3141
3142 try:
3143 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3144 if log.Wait() == 0:
3145 return log.stdout
3146 except GitError:
3147 # worktree may not exist if groups changed for example. In that case,
3148 # try in gitdir instead.
3149 if not os.path.exists(self.worktree):
3150 return self.bare_git.log(*cmd[1:])
3151 else:
3152 raise
3153 return None
3154
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003155 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3156 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003157 """Get the list of logs from this revision to given revisionId"""
3158 logs = {}
3159 selfId = self.GetRevisionId(self._allrefs)
3160 toId = toProject.GetRevisionId(toProject._allrefs)
3161
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003162 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3163 pretty_format=pretty_format)
3164 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3165 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003166 return logs
3167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003168 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003169
David James8d201162013-10-11 17:03:19 -07003170 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003171 self._project = project
3172 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003173 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003174
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003175 def LsOthers(self):
3176 p = GitCommand(self._project,
3177 ['ls-files',
3178 '-z',
3179 '--others',
3180 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003181 bare=False,
David James8d201162013-10-11 17:03:19 -07003182 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003183 capture_stdout=True,
3184 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003185 if p.Wait() == 0:
3186 out = p.stdout
3187 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003188 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003189 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003190 return []
3191
3192 def DiffZ(self, name, *args):
3193 cmd = [name]
3194 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003195 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003196 cmd.extend(args)
3197 p = GitCommand(self._project,
3198 cmd,
David James8d201162013-10-11 17:03:19 -07003199 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003200 bare=False,
3201 capture_stdout=True,
3202 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003203 try:
3204 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003205 if not hasattr(out, 'encode'):
3206 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003207 r = {}
3208 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003209 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003210 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003211 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003212 info = next(out)
3213 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003214 except StopIteration:
3215 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003216
3217 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003218
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003219 def __init__(self, path, omode, nmode, oid, nid, state):
3220 self.path = path
3221 self.src_path = None
3222 self.old_mode = omode
3223 self.new_mode = nmode
3224 self.old_id = oid
3225 self.new_id = nid
3226
3227 if len(state) == 1:
3228 self.status = state
3229 self.level = None
3230 else:
3231 self.status = state[:1]
3232 self.level = state[1:]
3233 while self.level.startswith('0'):
3234 self.level = self.level[1:]
3235
3236 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003237 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003238 if info.status in ('R', 'C'):
3239 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003240 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003241 r[info.path] = info
3242 return r
3243 finally:
3244 p.Wait()
3245
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003246 def GetDotgitPath(self, subpath=None):
3247 """Return the full path to the .git dir.
3248
3249 As a convenience, append |subpath| if provided.
3250 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003251 if self._bare:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003252 dotgit = self._gitdir
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003253 else:
Mike Frysinger4b0eb5a2020-02-23 23:22:34 -05003254 dotgit = os.path.join(self._project.worktree, '.git')
3255 if os.path.isfile(dotgit):
3256 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
3257 with open(dotgit) as fp:
3258 setting = fp.read()
3259 assert setting.startswith('gitdir:')
3260 gitdir = setting.split(':', 1)[1].strip()
3261 dotgit = os.path.normpath(os.path.join(self._project.worktree, gitdir))
3262
3263 return dotgit if subpath is None else os.path.join(dotgit, subpath)
3264
3265 def GetHead(self):
3266 """Return the ref that HEAD points to."""
3267 path = self.GetDotgitPath(subpath=HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08003268 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003269 with open(path) as fd:
3270 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003271 except IOError as e:
3272 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003273 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303274 line = line.decode()
3275 except AttributeError:
3276 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003277 if line.startswith('ref: '):
3278 return line[5:-1]
3279 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003280
3281 def SetHead(self, ref, message=None):
3282 cmdv = []
3283 if message is not None:
3284 cmdv.extend(['-m', message])
3285 cmdv.append(HEAD)
3286 cmdv.append(ref)
3287 self.symbolic_ref(*cmdv)
3288
3289 def DetachHead(self, new, message=None):
3290 cmdv = ['--no-deref']
3291 if message is not None:
3292 cmdv.extend(['-m', message])
3293 cmdv.append(HEAD)
3294 cmdv.append(new)
3295 self.update_ref(*cmdv)
3296
3297 def UpdateRef(self, name, new, old=None,
3298 message=None,
3299 detach=False):
3300 cmdv = []
3301 if message is not None:
3302 cmdv.extend(['-m', message])
3303 if detach:
3304 cmdv.append('--no-deref')
3305 cmdv.append(name)
3306 cmdv.append(new)
3307 if old is not None:
3308 cmdv.append(old)
3309 self.update_ref(*cmdv)
3310
3311 def DeleteRef(self, name, old=None):
3312 if not old:
3313 old = self.rev_parse(name)
3314 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003315 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003316
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003317 def rev_list(self, *args, **kw):
3318 if 'format' in kw:
3319 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3320 else:
3321 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003322 cmdv.extend(args)
3323 p = GitCommand(self._project,
3324 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003325 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003326 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003327 capture_stdout=True,
3328 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003329 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003330 raise GitError('%s rev-list %s: %s' %
3331 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003332 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003333
3334 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003335 """Allow arbitrary git commands using pythonic syntax.
3336
3337 This allows you to do things like:
3338 git_obj.rev_parse('HEAD')
3339
3340 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3341 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003342 Any other positional arguments will be passed to the git command, and the
3343 following keyword arguments are supported:
3344 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003345
3346 Args:
3347 name: The name of the git command to call. Any '_' characters will
3348 be replaced with '-'.
3349
3350 Returns:
3351 A callable object that will try to call git with the named command.
3352 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003353 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003354
Dave Borowitz091f8932012-10-23 17:01:04 -07003355 def runner(*args, **kwargs):
3356 cmdv = []
3357 config = kwargs.pop('config', None)
3358 for k in kwargs:
3359 raise TypeError('%s() got an unexpected keyword argument %r'
3360 % (name, k))
3361 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303362 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003363 cmdv.append('-c')
3364 cmdv.append('%s=%s' % (k, v))
3365 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003366 cmdv.extend(args)
3367 p = GitCommand(self._project,
3368 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003369 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003370 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003371 capture_stdout=True,
3372 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003373 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003374 raise GitError('%s %s: %s' %
3375 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003376 r = p.stdout
3377 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3378 return r[:-1]
3379 return r
3380 return runner
3381
3382
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003383class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003384
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003385 def __str__(self):
3386 return 'prior sync failed; rebase still in progress'
3387
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003388
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003389class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003390
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003391 def __str__(self):
3392 return 'contains uncommitted changes'
3393
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003394
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003395class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003396
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003397 def __init__(self, project, text):
3398 self.project = project
3399 self.text = text
3400
3401 def Print(self, syncbuf):
3402 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3403 syncbuf.out.nl()
3404
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003405
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003406class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003407
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003408 def __init__(self, project, why):
3409 self.project = project
3410 self.why = why
3411
3412 def Print(self, syncbuf):
3413 syncbuf.out.fail('error: %s/: %s',
3414 self.project.relpath,
3415 str(self.why))
3416 syncbuf.out.nl()
3417
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003418
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003419class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003420
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003421 def __init__(self, project, action):
3422 self.project = project
3423 self.action = action
3424
3425 def Run(self, syncbuf):
3426 out = syncbuf.out
3427 out.project('project %s/', self.project.relpath)
3428 out.nl()
3429 try:
3430 self.action()
3431 out.nl()
3432 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003433 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003434 out.nl()
3435 return False
3436
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003437
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003438class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003439
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003440 def __init__(self, config):
3441 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003442 self.project = self.printer('header', attr='bold')
3443 self.info = self.printer('info')
3444 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003445
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003446
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003447class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003448
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003449 def __init__(self, config, detach_head=False):
3450 self._messages = []
3451 self._failures = []
3452 self._later_queue1 = []
3453 self._later_queue2 = []
3454
3455 self.out = _SyncColoring(config)
3456 self.out.redirect(sys.stderr)
3457
3458 self.detach_head = detach_head
3459 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003460 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003461
3462 def info(self, project, fmt, *args):
3463 self._messages.append(_InfoMessage(project, fmt % args))
3464
3465 def fail(self, project, err=None):
3466 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003467 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003468
3469 def later1(self, project, what):
3470 self._later_queue1.append(_Later(project, what))
3471
3472 def later2(self, project, what):
3473 self._later_queue2.append(_Later(project, what))
3474
3475 def Finish(self):
3476 self._PrintMessages()
3477 self._RunLater()
3478 self._PrintMessages()
3479 return self.clean
3480
David Rileye0684ad2017-04-05 00:02:59 -07003481 def Recently(self):
3482 recent_clean = self.recent_clean
3483 self.recent_clean = True
3484 return recent_clean
3485
3486 def _MarkUnclean(self):
3487 self.clean = False
3488 self.recent_clean = False
3489
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003490 def _RunLater(self):
3491 for q in ['_later_queue1', '_later_queue2']:
3492 if not self._RunQueue(q):
3493 return
3494
3495 def _RunQueue(self, queue):
3496 for m in getattr(self, queue):
3497 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003498 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003499 return False
3500 setattr(self, queue, [])
3501 return True
3502
3503 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003504 if self._messages or self._failures:
3505 if os.isatty(2):
3506 self.out.write(progress.CSI_ERASE_LINE)
3507 self.out.write('\r')
3508
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003509 for m in self._messages:
3510 m.Print(self)
3511 for m in self._failures:
3512 m.Print(self)
3513
3514 self._messages = []
3515 self._failures = []
3516
3517
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003518class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003519
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003520 """A special project housed under .repo.
3521 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003522
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003523 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003524 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003525 manifest=manifest,
3526 name=name,
3527 gitdir=gitdir,
3528 objdir=gitdir,
3529 worktree=worktree,
3530 remote=RemoteSpec('origin'),
3531 relpath='.repo/%s' % name,
3532 revisionExpr='refs/heads/master',
3533 revisionId=None,
3534 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003535
3536 def PreSync(self):
3537 if self.Exists:
3538 cb = self.CurrentBranch
3539 if cb:
3540 base = self.GetBranch(cb).merge
3541 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003542 self.revisionExpr = base
3543 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003544
Martin Kelly224a31a2017-07-10 14:46:25 -07003545 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003546 """ Prepare MetaProject for manifest branch switch
3547 """
3548
3549 # detach and delete manifest branch, allowing a new
3550 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003551 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003552 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003553 syncbuf.Finish()
3554
3555 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003556 ['update-ref', '-d', 'refs/heads/default'],
3557 capture_stdout=True,
3558 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003559
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003560 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003561 def LastFetch(self):
3562 try:
3563 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3564 return os.path.getmtime(fh)
3565 except OSError:
3566 return 0
3567
3568 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003569 def HasChanges(self):
3570 """Has the remote received new commits not yet checked out?
3571 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003572 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003573 return False
3574
David Pursehouse8a68ff92012-09-24 12:15:13 +09003575 all_refs = self.bare_ref.all
3576 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003577 head = self.work_git.GetHead()
3578 if head.startswith(R_HEADS):
3579 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003580 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003581 except KeyError:
3582 head = None
3583
3584 if revid == head:
3585 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003586 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003587 return True
3588 return False