blob: d83dd2d72ed1edd1dabc1ac9606b546e9a8c64f3 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Mike Frysingere6a202f2019-08-02 15:57:57 -040039from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
David Pursehousea46bf7d2020-02-15 12:45:53 +090055 input = raw_input # noqa: F821
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Mike Frysinger3164d402019-11-11 05:40:22 -050061 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063
64 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070065 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080067 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 raise
69
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070070
Shawn O. Pearce48244782009-04-16 08:25:57 -070071def _error(fmt, *args):
72 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070073 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070075
David Pursehousef33929d2015-08-24 14:39:14 +090076def _warn(fmt, *args):
77 msg = fmt % args
78 print('warn: %s' % msg, file=sys.stderr)
79
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081def not_rev(r):
82 return '^' + r
83
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070084
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080085def sq(r):
86 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080087
David Pursehouse819827a2020-02-12 15:20:19 +090088
Jonathan Nieder93719792015-03-17 11:29:58 -070089_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070090
91
Jonathan Nieder93719792015-03-17 11:29:58 -070092def _ProjectHooks():
93 """List the hooks present in the 'hooks' directory.
94
95 These hooks are project hooks and are copied to the '.git/hooks' directory
96 of all subprojects.
97
98 This function caches the list of hooks (based on the contents of the
99 'repo/hooks' directory) on the first call.
100
101 Returns:
102 A list of absolute paths to all of the files in the hooks directory.
103 """
104 global _project_hook_list
105 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700107 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700108 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 return _project_hook_list
110
111
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700112class DownloadedChange(object):
113 _commit_cache = None
114
115 def __init__(self, project, base, change_id, ps_id, commit):
116 self.project = project
117 self.base = base
118 self.change_id = change_id
119 self.ps_id = ps_id
120 self.commit = commit
121
122 @property
123 def commits(self):
124 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700125 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
126 '--abbrev-commit',
127 '--pretty=oneline',
128 '--reverse',
129 '--date-order',
130 not_rev(self.base),
131 self.commit,
132 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700133 return self._commit_cache
134
135
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136class ReviewableBranch(object):
137 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400138 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400152 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
153 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
154 try:
155 self._commit_cache = self.project.bare_git.rev_list(*args)
156 except GitError:
157 # We weren't able to probe the commits for this branch. Was it tracking
158 # a branch that no longer exists? If so, return no commits. Otherwise,
159 # rethrow the error as we don't know what's going on.
160 if self.base_exists:
161 raise
162
163 self._commit_cache = []
164
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 return self._commit_cache
166
167 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 def unabbrev_commits(self):
169 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700170 for commit in self.project.bare_git.rev_list(not_rev(self.base),
171 R_HEADS + self.name,
172 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800173 r[commit[0:8]] = commit
174 return r
175
176 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 return self.project.bare_git.log('--pretty=format:%cd',
179 '-n', '1',
180 R_HEADS + self.name,
181 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182
Mike Frysinger6da17752019-09-11 18:43:17 -0400183 @property
184 def base_exists(self):
185 """Whether the branch we're tracking exists.
186
187 Normally it should, but sometimes branches we track can get deleted.
188 """
189 if self._base_exists is None:
190 try:
191 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
192 # If we're still here, the base branch exists.
193 self._base_exists = True
194 except GitError:
195 # If we failed to verify, the base branch doesn't exist.
196 self._base_exists = False
197
198 return self._base_exists
199
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700200 def UploadForReview(self, people,
Mike Frysinger819cc812020-02-19 02:27:22 -0500201 dryrun=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500203 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000204 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200205 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700206 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200207 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200208 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800209 validate_certs=True,
210 push_options=None):
Mike Frysinger819cc812020-02-19 02:27:22 -0500211 self.project.UploadForReview(branch=self.name,
212 people=people,
213 dryrun=dryrun,
Brian Harring435370c2012-07-28 15:37:04 -0700214 auto_topic=auto_topic,
Mike Frysinger84685ba2020-02-19 02:22:22 -0500215 hashtags=hashtags,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000216 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200217 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700218 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200219 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200220 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800221 validate_certs=validate_certs,
222 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 def GetPublishedRefs(self):
225 refs = {}
226 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700227 self.branch.remote.SshReviewUrl(self.project.UserEmail),
228 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700229 for line in output.split('\n'):
230 try:
231 (sha, ref) = line.split()
232 refs[sha] = ref
233 except ValueError:
234 pass
235
236 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 def __init__(self, config):
242 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.project = self.printer('header', attr='bold')
244 self.branch = self.printer('header', attr='bold')
245 self.nobranch = self.printer('nobranch', fg='red')
246 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247
Anthony King7bdac712014-07-16 12:56:40 +0100248 self.added = self.printer('added', fg='green')
249 self.changed = self.printer('changed', fg='red')
250 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252
253class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 def __init__(self, config):
256 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100257 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400258 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700260
Anthony King7bdac712014-07-16 12:56:40 +0100261class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700262
James W. Mills24c13082012-04-12 15:04:13 -0500263 def __init__(self, name, value, keep):
264 self.name = name
265 self.value = value
266 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Mike Frysingere6a202f2019-08-02 15:57:57 -0400269def _SafeExpandPath(base, subpath, skipfinal=False):
270 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700271
Mike Frysingere6a202f2019-08-02 15:57:57 -0400272 We make sure no intermediate symlinks are traversed, and that the final path
273 is not a special file (e.g. not a socket or fifo).
274
275 NB: We rely on a number of paths already being filtered out while parsing the
276 manifest. See the validation logic in manifest_xml.py for more details.
277 """
Mike Frysingerd9254592020-02-19 22:36:26 -0500278 # Split up the path by its components. We can't use os.path.sep exclusively
279 # as some platforms (like Windows) will convert / to \ and that bypasses all
280 # our constructed logic here. Especially since manifest authors only use
281 # / in their paths.
282 resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
283 components = resep.split(subpath)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400284 if skipfinal:
285 # Whether the caller handles the final component itself.
286 finalpart = components.pop()
287
288 path = base
289 for part in components:
290 if part in {'.', '..'}:
291 raise ManifestInvalidPathError(
292 '%s: "%s" not allowed in paths' % (subpath, part))
293
294 path = os.path.join(path, part)
295 if platform_utils.islink(path):
296 raise ManifestInvalidPathError(
297 '%s: traversing symlinks not allow' % (path,))
298
299 if os.path.exists(path):
300 if not os.path.isfile(path) and not platform_utils.isdir(path):
301 raise ManifestInvalidPathError(
302 '%s: only regular files & directories allowed' % (path,))
303
304 if skipfinal:
305 path = os.path.join(path, finalpart)
306
307 return path
308
309
310class _CopyFile(object):
311 """Container for <copyfile> manifest element."""
312
313 def __init__(self, git_worktree, src, topdir, dest):
314 """Register a <copyfile> request.
315
316 Args:
317 git_worktree: Absolute path to the git project checkout.
318 src: Relative path under |git_worktree| of file to read.
319 topdir: Absolute path to the top of the repo client checkout.
320 dest: Relative path under |topdir| of file to write.
321 """
322 self.git_worktree = git_worktree
323 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700324 self.src = src
325 self.dest = dest
326
327 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400328 src = _SafeExpandPath(self.git_worktree, self.src)
329 dest = _SafeExpandPath(self.topdir, self.dest)
330
331 if platform_utils.isdir(src):
332 raise ManifestInvalidPathError(
333 '%s: copying from directory not supported' % (self.src,))
334 if platform_utils.isdir(dest):
335 raise ManifestInvalidPathError(
336 '%s: copying to directory not allowed' % (self.dest,))
337
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700338 # copy file if it does not exist or is out of date
339 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
340 try:
341 # remove existing file first, since it might be read-only
342 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800343 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400344 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200345 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700346 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200347 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 shutil.copy(src, dest)
349 # make the file read-only
350 mode = os.stat(dest)[stat.ST_MODE]
351 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
352 os.chmod(dest, mode)
353 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700354 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700355
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700356
Anthony King7bdac712014-07-16 12:56:40 +0100357class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400358 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700359
Mike Frysingere6a202f2019-08-02 15:57:57 -0400360 def __init__(self, git_worktree, src, topdir, dest):
361 """Register a <linkfile> request.
362
363 Args:
364 git_worktree: Absolute path to the git project checkout.
365 src: Target of symlink relative to path under |git_worktree|.
366 topdir: Absolute path to the top of the repo client checkout.
367 dest: Relative path under |topdir| of symlink to create.
368 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700369 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400370 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500371 self.src = src
372 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500373
Wink Saville4c426ef2015-06-03 08:05:17 -0700374 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500375 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700376 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500377 try:
378 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800379 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800380 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500381 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700382 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700383 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500384 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700385 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500386 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700387 _error('Cannot link file %s to %s', relSrc, absDest)
388
389 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400390 """Link the self.src & self.dest paths.
391
392 Handles wild cards on the src linking all of the files in the source in to
393 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700394 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500395 # Some people use src="." to create stable links to projects. Lets allow
396 # that but reject all other uses of "." to keep things simple.
397 if self.src == '.':
398 src = self.git_worktree
399 else:
400 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400401
402 if os.path.exists(src):
403 # Entity exists so just a simple one to one link operation.
404 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
405 # dest & src are absolute paths at this point. Make sure the target of
406 # the symlink is relative in the context of the repo client checkout.
407 relpath = os.path.relpath(src, os.path.dirname(dest))
408 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700409 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400412 if os.path.exists(dest) and not platform_utils.isdir(dest):
413 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700414 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400415 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700416 # Create a releative path from source dir to destination dir
417 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400418 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700419
420 # Get the source file name
421 srcFile = os.path.basename(absSrcFile)
422
423 # Now form the final full paths to srcFile. They will be
424 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400425 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700426 relSrc = os.path.join(relSrcDir, srcFile)
427 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500428
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700429
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700430class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700431
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700432 def __init__(self,
433 name,
Anthony King7bdac712014-07-16 12:56:40 +0100434 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700435 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100436 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700437 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700438 orig_name=None,
439 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700440 self.name = name
441 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700442 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700443 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100444 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700445 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700446 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700447
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700448
Doug Anderson37282b42011-03-04 11:54:18 -0800449class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700450
Doug Anderson37282b42011-03-04 11:54:18 -0800451 """A RepoHook contains information about a script to run as a hook.
452
453 Hooks are used to run a python script before running an upload (for instance,
454 to run presubmit checks). Eventually, we may have hooks for other actions.
455
456 This shouldn't be confused with files in the 'repo/hooks' directory. Those
457 files are copied into each '.git/hooks' folder for each project. Repo-level
458 hooks are associated instead with repo actions.
459
460 Hooks are always python. When a hook is run, we will load the hook into the
461 interpreter and execute its main() function.
462 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700463
Doug Anderson37282b42011-03-04 11:54:18 -0800464 def __init__(self,
465 hook_type,
466 hooks_project,
467 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400468 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800469 abort_if_user_denies=False):
470 """RepoHook constructor.
471
472 Params:
473 hook_type: A string representing the type of hook. This is also used
474 to figure out the name of the file containing the hook. For
475 example: 'pre-upload'.
476 hooks_project: The project containing the repo hooks. If you have a
477 manifest, this is manifest.repo_hooks_project. OK if this is None,
478 which will make the hook a no-op.
479 topdir: Repo's top directory (the one containing the .repo directory).
480 Scripts will run with CWD as this directory. If you have a manifest,
481 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400482 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800483 abort_if_user_denies: If True, we'll throw a HookError() if the user
484 doesn't allow us to run the hook.
485 """
486 self._hook_type = hook_type
487 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400488 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800489 self._topdir = topdir
490 self._abort_if_user_denies = abort_if_user_denies
491
492 # Store the full path to the script for convenience.
493 if self._hooks_project:
494 self._script_fullpath = os.path.join(self._hooks_project.worktree,
495 self._hook_type + '.py')
496 else:
497 self._script_fullpath = None
498
499 def _GetHash(self):
500 """Return a hash of the contents of the hooks directory.
501
502 We'll just use git to do this. This hash has the property that if anything
503 changes in the directory we will return a different has.
504
505 SECURITY CONSIDERATION:
506 This hash only represents the contents of files in the hook directory, not
507 any other files imported or called by hooks. Changes to imported files
508 can change the script behavior without affecting the hash.
509
510 Returns:
511 A string representing the hash. This will always be ASCII so that it can
512 be printed to the user easily.
513 """
514 assert self._hooks_project, "Must have hooks to calculate their hash."
515
516 # We will use the work_git object rather than just calling GetRevisionId().
517 # That gives us a hash of the latest checked in version of the files that
518 # the user will actually be executing. Specifically, GetRevisionId()
519 # doesn't appear to change even if a user checks out a different version
520 # of the hooks repo (via git checkout) nor if a user commits their own revs.
521 #
522 # NOTE: Local (non-committed) changes will not be factored into this hash.
523 # I think this is OK, since we're really only worried about warning the user
524 # about upstream changes.
525 return self._hooks_project.work_git.rev_parse('HEAD')
526
527 def _GetMustVerb(self):
528 """Return 'must' if the hook is required; 'should' if not."""
529 if self._abort_if_user_denies:
530 return 'must'
531 else:
532 return 'should'
533
534 def _CheckForHookApproval(self):
535 """Check to see whether this hook has been approved.
536
Mike Frysinger40252c22016-08-15 21:23:44 -0400537 We'll accept approval of manifest URLs if they're using secure transports.
538 This way the user can say they trust the manifest hoster. For insecure
539 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800540
541 Note that we ask permission for each individual hook even though we use
542 the hash of all hooks when detecting changes. We'd like the user to be
543 able to approve / deny each hook individually. We only use the hash of all
544 hooks because there is no other easy way to detect changes to local imports.
545
546 Returns:
547 True if this hook is approved to run; False otherwise.
548
549 Raises:
550 HookError: Raised if the user doesn't approve and abort_if_user_denies
551 was passed to the consturctor.
552 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400553 if self._ManifestUrlHasSecureScheme():
554 return self._CheckForHookApprovalManifest()
555 else:
556 return self._CheckForHookApprovalHash()
557
558 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
559 changed_prompt):
560 """Check for approval for a particular attribute and hook.
561
562 Args:
563 subkey: The git config key under [repo.hooks.<hook_type>] to store the
564 last approved string.
565 new_val: The new value to compare against the last approved one.
566 main_prompt: Message to display to the user to ask for approval.
567 changed_prompt: Message explaining why we're re-asking for approval.
568
569 Returns:
570 True if this hook is approved to run; False otherwise.
571
572 Raises:
573 HookError: Raised if the user doesn't approve and abort_if_user_denies
574 was passed to the consturctor.
575 """
Doug Anderson37282b42011-03-04 11:54:18 -0800576 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400577 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800578
Mike Frysinger40252c22016-08-15 21:23:44 -0400579 # Get the last value that the user approved for this hook; may be None.
580 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800581
Mike Frysinger40252c22016-08-15 21:23:44 -0400582 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800583 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400584 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800585 # Approval matched. We're done.
586 return True
587 else:
588 # Give the user a reason why we're prompting, since they last told
589 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400590 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800591 else:
592 prompt = ''
593
594 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
595 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400596 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530597 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900598 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800599
600 # User is doing a one-time approval.
601 if response in ('y', 'yes'):
602 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400603 elif response == 'always':
604 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800605 return True
606
607 # For anything else, we'll assume no approval.
608 if self._abort_if_user_denies:
609 raise HookError('You must allow the %s hook or use --no-verify.' %
610 self._hook_type)
611
612 return False
613
Mike Frysinger40252c22016-08-15 21:23:44 -0400614 def _ManifestUrlHasSecureScheme(self):
615 """Check if the URI for the manifest is a secure transport."""
616 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
617 parse_results = urllib.parse.urlparse(self._manifest_url)
618 return parse_results.scheme in secure_schemes
619
620 def _CheckForHookApprovalManifest(self):
621 """Check whether the user has approved this manifest host.
622
623 Returns:
624 True if this hook is approved to run; False otherwise.
625 """
626 return self._CheckForHookApprovalHelper(
627 'approvedmanifest',
628 self._manifest_url,
629 'Run hook scripts from %s' % (self._manifest_url,),
630 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
631
632 def _CheckForHookApprovalHash(self):
633 """Check whether the user has approved the hooks repo.
634
635 Returns:
636 True if this hook is approved to run; False otherwise.
637 """
638 prompt = ('Repo %s run the script:\n'
639 ' %s\n'
640 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700641 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400642 return self._CheckForHookApprovalHelper(
643 'approvedhash',
644 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700645 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400646 'Scripts have changed since %s was allowed.' % (self._hook_type,))
647
Mike Frysingerf7c51602019-06-18 17:23:39 -0400648 @staticmethod
649 def _ExtractInterpFromShebang(data):
650 """Extract the interpreter used in the shebang.
651
652 Try to locate the interpreter the script is using (ignoring `env`).
653
654 Args:
655 data: The file content of the script.
656
657 Returns:
658 The basename of the main script interpreter, or None if a shebang is not
659 used or could not be parsed out.
660 """
661 firstline = data.splitlines()[:1]
662 if not firstline:
663 return None
664
665 # The format here can be tricky.
666 shebang = firstline[0].strip()
667 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
668 if not m:
669 return None
670
671 # If the using `env`, find the target program.
672 interp = m.group(1)
673 if os.path.basename(interp) == 'env':
674 interp = m.group(2)
675
676 return interp
677
678 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
679 """Execute the hook script through |interp|.
680
681 Note: Support for this feature should be dropped ~Jun 2021.
682
683 Args:
684 interp: The Python program to run.
685 context: Basic Python context to execute the hook inside.
686 kwargs: Arbitrary arguments to pass to the hook script.
687
688 Raises:
689 HookError: When the hooks failed for any reason.
690 """
691 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
692 script = """
693import json, os, sys
694path = '''%(path)s'''
695kwargs = json.loads('''%(kwargs)s''')
696context = json.loads('''%(context)s''')
697sys.path.insert(0, os.path.dirname(path))
698data = open(path).read()
699exec(compile(data, path, 'exec'), context)
700context['main'](**kwargs)
701""" % {
702 'path': self._script_fullpath,
703 'kwargs': json.dumps(kwargs),
704 'context': json.dumps(context),
705 }
706
707 # We pass the script via stdin to avoid OS argv limits. It also makes
708 # unhandled exception tracebacks less verbose/confusing for users.
709 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
710 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
711 proc.communicate(input=script.encode('utf-8'))
712 if proc.returncode:
713 raise HookError('Failed to run %s hook.' % (self._hook_type,))
714
715 def _ExecuteHookViaImport(self, data, context, **kwargs):
716 """Execute the hook code in |data| directly.
717
718 Args:
719 data: The code of the hook to execute.
720 context: Basic Python context to execute the hook inside.
721 kwargs: Arbitrary arguments to pass to the hook script.
722
723 Raises:
724 HookError: When the hooks failed for any reason.
725 """
726 # Exec, storing global context in the context dict. We catch exceptions
727 # and convert to a HookError w/ just the failing traceback.
728 try:
729 exec(compile(data, self._script_fullpath, 'exec'), context)
730 except Exception:
731 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
732 (traceback.format_exc(), self._hook_type))
733
734 # Running the script should have defined a main() function.
735 if 'main' not in context:
736 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
737
738 # Call the main function in the hook. If the hook should cause the
739 # build to fail, it will raise an Exception. We'll catch that convert
740 # to a HookError w/ just the failing traceback.
741 try:
742 context['main'](**kwargs)
743 except Exception:
744 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
745 'above.' % (traceback.format_exc(), self._hook_type))
746
Doug Anderson37282b42011-03-04 11:54:18 -0800747 def _ExecuteHook(self, **kwargs):
748 """Actually execute the given hook.
749
750 This will run the hook's 'main' function in our python interpreter.
751
752 Args:
753 kwargs: Keyword arguments to pass to the hook. These are often specific
754 to the hook type. For instance, pre-upload hooks will contain
755 a project_list.
756 """
757 # Keep sys.path and CWD stashed away so that we can always restore them
758 # upon function exit.
759 orig_path = os.getcwd()
760 orig_syspath = sys.path
761
762 try:
763 # Always run hooks with CWD as topdir.
764 os.chdir(self._topdir)
765
766 # Put the hook dir as the first item of sys.path so hooks can do
767 # relative imports. We want to replace the repo dir as [0] so
768 # hooks can't import repo files.
769 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
770
Mike Frysingerf7c51602019-06-18 17:23:39 -0400771 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500772 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800773
Doug Anderson37282b42011-03-04 11:54:18 -0800774 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
775 # We don't actually want hooks to define their main with this argument--
776 # it's there to remind them that their hook should always take **kwargs.
777 # For instance, a pre-upload hook should be defined like:
778 # def main(project_list, **kwargs):
779 #
780 # This allows us to later expand the API without breaking old hooks.
781 kwargs = kwargs.copy()
782 kwargs['hook_should_take_kwargs'] = True
783
Mike Frysingerf7c51602019-06-18 17:23:39 -0400784 # See what version of python the hook has been written against.
785 data = open(self._script_fullpath).read()
786 interp = self._ExtractInterpFromShebang(data)
787 reexec = False
788 if interp:
789 prog = os.path.basename(interp)
790 if prog.startswith('python2') and sys.version_info.major != 2:
791 reexec = True
792 elif prog.startswith('python3') and sys.version_info.major == 2:
793 reexec = True
794
795 # Attempt to execute the hooks through the requested version of Python.
796 if reexec:
797 try:
798 self._ExecuteHookViaReexec(interp, context, **kwargs)
799 except OSError as e:
800 if e.errno == errno.ENOENT:
801 # We couldn't find the interpreter, so fallback to importing.
802 reexec = False
803 else:
804 raise
805
806 # Run the hook by importing directly.
807 if not reexec:
808 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800809 finally:
810 # Restore sys.path and CWD.
811 sys.path = orig_syspath
812 os.chdir(orig_path)
813
814 def Run(self, user_allows_all_hooks, **kwargs):
815 """Run the hook.
816
817 If the hook doesn't exist (because there is no hooks project or because
818 this particular hook is not enabled), this is a no-op.
819
820 Args:
821 user_allows_all_hooks: If True, we will never prompt about running the
822 hook--we'll just assume it's OK to run it.
823 kwargs: Keyword arguments to pass to the hook. These are often specific
824 to the hook type. For instance, pre-upload hooks will contain
825 a project_list.
826
827 Raises:
828 HookError: If there was a problem finding the hook or the user declined
829 to run a required hook (from _CheckForHookApproval).
830 """
831 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700832 if ((not self._hooks_project) or (self._hook_type not in
833 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800834 return
835
836 # Bail with a nice error if we can't find the hook.
837 if not os.path.isfile(self._script_fullpath):
838 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
839
840 # Make sure the user is OK with running the hook.
841 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
842 return
843
844 # Run the hook with the same version of python we're using.
845 self._ExecuteHook(**kwargs)
846
847
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600849 # These objects can be shared between several working trees.
850 shareable_files = ['description', 'info']
851 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
852 # These objects can only be used by a single working tree.
853 working_tree_files = ['config', 'packed-refs', 'shallow']
854 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700855
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856 def __init__(self,
857 manifest,
858 name,
859 remote,
860 gitdir,
David James8d201162013-10-11 17:03:19 -0700861 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700862 worktree,
863 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700864 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800865 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100866 rebase=True,
867 groups=None,
868 sync_c=False,
869 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900870 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100871 clone_depth=None,
872 upstream=None,
873 parent=None,
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500874 use_git_worktrees=False,
Anthony King7bdac712014-07-16 12:56:40 +0100875 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900876 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700877 optimized_fetch=False,
878 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800879 """Init a Project object.
880
881 Args:
882 manifest: The XmlManifest object.
883 name: The `name` attribute of manifest.xml's project element.
884 remote: RemoteSpec object specifying its remote's properties.
885 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700886 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800887 worktree: Absolute path of git working tree.
888 relpath: Relative path of git working tree to repo's top directory.
889 revisionExpr: The `revision` attribute of manifest.xml's project element.
890 revisionId: git commit id for checking out.
891 rebase: The `rebase` attribute of manifest.xml's project element.
892 groups: The `groups` attribute of manifest.xml's project element.
893 sync_c: The `sync-c` attribute of manifest.xml's project element.
894 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900895 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800896 upstream: The `upstream` attribute of manifest.xml's project element.
897 parent: The parent Project object.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500898 use_git_worktrees: Whether to use `git worktree` for this project.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800899 is_derived: False if the project was explicitly defined in the manifest;
900 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400901 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900902 optimized_fetch: If True, when a project is set to a sha1 revision, only
903 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700904 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800905 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700906 self.manifest = manifest
907 self.name = name
908 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800909 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700910 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800911 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700912 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800913 else:
914 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700915 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700916 self.revisionExpr = revisionExpr
917
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700918 if revisionId is None \
919 and revisionExpr \
920 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700921 self.revisionId = revisionExpr
922 else:
923 self.revisionId = revisionId
924
Mike Pontillod3153822012-02-28 11:53:24 -0800925 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700926 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700927 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800928 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900929 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900930 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700931 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800932 self.parent = parent
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500933 # NB: Do not use this setting in __init__ to change behavior so that the
934 # manifest.git checkout can inspect & change it after instantiating. See
935 # the XmlManifest init code for more info.
936 self.use_git_worktrees = use_git_worktrees
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800937 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900938 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800939 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800940
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700942 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500943 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500944 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700945 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
946 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700947
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800948 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700949 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800950 else:
951 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700952 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700953 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700954 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400955 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700956 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700957
Doug Anderson37282b42011-03-04 11:54:18 -0800958 # This will be filled in if a project is later identified to be the
959 # project containing repo hooks.
960 self.enabled_repo_hooks = []
961
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700962 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800963 def Derived(self):
964 return self.is_derived
965
966 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700967 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700968 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700969
970 @property
971 def CurrentBranch(self):
972 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400973
974 The branch name omits the 'refs/heads/' prefix.
975 None is returned if the project is on a detached HEAD, or if the work_git is
976 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700977 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400978 try:
979 b = self.work_git.GetHead()
980 except NoManifestException:
981 # If the local checkout is in a bad state, don't barf. Let the callers
982 # process this like the head is unreadable.
983 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984 if b.startswith(R_HEADS):
985 return b[len(R_HEADS):]
986 return None
987
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700988 def IsRebaseInProgress(self):
989 w = self.worktree
990 g = os.path.join(w, '.git')
991 return os.path.exists(os.path.join(g, 'rebase-apply')) \
992 or os.path.exists(os.path.join(g, 'rebase-merge')) \
993 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200994
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700995 def IsDirty(self, consider_untracked=True):
996 """Is the working directory modified in some way?
997 """
998 self.work_git.update_index('-q',
999 '--unmerged',
1000 '--ignore-missing',
1001 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +09001002 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003 return True
1004 if self.work_git.DiffZ('diff-files'):
1005 return True
1006 if consider_untracked and self.work_git.LsOthers():
1007 return True
1008 return False
1009
1010 _userident_name = None
1011 _userident_email = None
1012
1013 @property
1014 def UserName(self):
1015 """Obtain the user's personal name.
1016 """
1017 if self._userident_name is None:
1018 self._LoadUserIdentity()
1019 return self._userident_name
1020
1021 @property
1022 def UserEmail(self):
1023 """Obtain the user's email address. This is very likely
1024 to be their Gerrit login.
1025 """
1026 if self._userident_email is None:
1027 self._LoadUserIdentity()
1028 return self._userident_email
1029
1030 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001031 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1032 m = re.compile("^(.*) <([^>]*)> ").match(u)
1033 if m:
1034 self._userident_name = m.group(1)
1035 self._userident_email = m.group(2)
1036 else:
1037 self._userident_name = ''
1038 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001039
1040 def GetRemote(self, name):
1041 """Get the configuration for a single remote.
1042 """
1043 return self.config.GetRemote(name)
1044
1045 def GetBranch(self, name):
1046 """Get the configuration for a single branch.
1047 """
1048 return self.config.GetBranch(name)
1049
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001050 def GetBranches(self):
1051 """Get all existing local branches.
1052 """
1053 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001054 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001055 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001056
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301057 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001058 if name.startswith(R_HEADS):
1059 name = name[len(R_HEADS):]
1060 b = self.GetBranch(name)
1061 b.current = name == current
1062 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001063 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001064 heads[name] = b
1065
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301066 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001067 if name.startswith(R_PUB):
1068 name = name[len(R_PUB):]
1069 b = heads.get(name)
1070 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001071 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001072
1073 return heads
1074
Colin Cross5acde752012-03-28 20:15:45 -07001075 def MatchesGroups(self, manifest_groups):
1076 """Returns true if the manifest groups specified at init should cause
1077 this project to be synced.
1078 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001079 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001080
1081 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001082 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001083 manifest_groups: "-group1,group2"
1084 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001085
1086 The special manifest group "default" will match any project that
1087 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001088 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001089 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001090 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001091 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001092 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001093
Conley Owens971de8e2012-04-16 10:36:08 -07001094 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001095 for group in expanded_manifest_groups:
1096 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001097 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001098 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001099 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001100
Conley Owens971de8e2012-04-16 10:36:08 -07001101 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001103# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001104 def UncommitedFiles(self, get_all=True):
1105 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001106
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001107 Args:
1108 get_all: a boolean, if True - get information about all different
1109 uncommitted files. If False - return as soon as any kind of
1110 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001111 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001112 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001113 self.work_git.update_index('-q',
1114 '--unmerged',
1115 '--ignore-missing',
1116 '--refresh')
1117 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001118 details.append("rebase in progress")
1119 if not get_all:
1120 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001121
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001122 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1123 if changes:
1124 details.extend(changes)
1125 if not get_all:
1126 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001127
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001128 changes = self.work_git.DiffZ('diff-files').keys()
1129 if changes:
1130 details.extend(changes)
1131 if not get_all:
1132 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001133
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001134 changes = self.work_git.LsOthers()
1135 if changes:
1136 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001137
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001138 return details
1139
1140 def HasChanges(self):
1141 """Returns true if there are uncommitted changes.
1142 """
1143 if self.UncommitedFiles(get_all=False):
1144 return True
1145 else:
1146 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001147
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001148 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001149 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001150
1151 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001152 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001153 quiet: If True then only print the project name. Do not print
1154 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001156 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001157 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001158 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001159 print(file=output_redir)
1160 print('project %s/' % self.relpath, file=output_redir)
1161 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001162 return
1163
1164 self.work_git.update_index('-q',
1165 '--unmerged',
1166 '--ignore-missing',
1167 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001168 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001169 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1170 df = self.work_git.DiffZ('diff-files')
1171 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001172 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001173 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174
1175 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001176 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001177 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001178 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001180 if quiet:
1181 out.nl()
1182 return 'DIRTY'
1183
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184 branch = self.CurrentBranch
1185 if branch is None:
1186 out.nobranch('(*** NO BRANCH ***)')
1187 else:
1188 out.branch('branch %s', branch)
1189 out.nl()
1190
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001191 if rb:
1192 out.important('prior sync failed; rebase still in progress')
1193 out.nl()
1194
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001195 paths = list()
1196 paths.extend(di.keys())
1197 paths.extend(df.keys())
1198 paths.extend(do)
1199
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301200 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001201 try:
1202 i = di[p]
1203 except KeyError:
1204 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001206 try:
1207 f = df[p]
1208 except KeyError:
1209 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001210
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001211 if i:
1212 i_status = i.status.upper()
1213 else:
1214 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001215
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001216 if f:
1217 f_status = f.status.lower()
1218 else:
1219 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220
1221 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001222 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001223 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 else:
1225 line = ' %s%s\t%s' % (i_status, f_status, p)
1226
1227 if i and not f:
1228 out.added('%s', line)
1229 elif (i and f) or (not i and f):
1230 out.changed('%s', line)
1231 elif not i and not f:
1232 out.untracked('%s', line)
1233 else:
1234 out.write('%s', line)
1235 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001236
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001237 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001238
pelyad67872d2012-03-28 14:49:58 +03001239 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001240 """Prints the status of the repository to stdout.
1241 """
1242 out = DiffColoring(self.config)
1243 cmd = ['diff']
1244 if out.is_on:
1245 cmd.append('--color')
1246 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001247 if absolute_paths:
1248 cmd.append('--src-prefix=a/%s/' % self.relpath)
1249 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001250 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001251 try:
1252 p = GitCommand(self,
1253 cmd,
1254 capture_stdout=True,
1255 capture_stderr=True)
1256 except GitError as e:
1257 out.nl()
1258 out.project('project %s/' % self.relpath)
1259 out.nl()
1260 out.fail('%s', str(e))
1261 out.nl()
1262 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263 has_diff = False
1264 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001265 if not hasattr(line, 'encode'):
1266 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001267 if not has_diff:
1268 out.nl()
1269 out.project('project %s/' % self.relpath)
1270 out.nl()
1271 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001272 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001273 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001274
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001275# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001276 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001277 """Was the branch published (uploaded) for code review?
1278 If so, returns the SHA-1 hash of the last published
1279 state for the branch.
1280 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001281 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001282 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001283 try:
1284 return self.bare_git.rev_parse(key)
1285 except GitError:
1286 return None
1287 else:
1288 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001289 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001290 except KeyError:
1291 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001292
David Pursehouse8a68ff92012-09-24 12:15:13 +09001293 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001294 """Prunes any stale published refs.
1295 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001296 if all_refs is None:
1297 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 heads = set()
1299 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301300 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 if name.startswith(R_HEADS):
1302 heads.add(name)
1303 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001304 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001305
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301306 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 n = name[len(R_PUB):]
1308 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001309 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001311 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 """List any branches which can be uploaded for review.
1313 """
1314 heads = {}
1315 pubed = {}
1316
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301317 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001318 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001319 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001320 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001321 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
1323 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301324 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001325 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001327 if selected_branch and branch != selected_branch:
1328 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001330 rb = self.GetUploadableBranch(branch)
1331 if rb:
1332 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001333 return ready
1334
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001335 def GetUploadableBranch(self, branch_name):
1336 """Get a single uploadable branch, or None.
1337 """
1338 branch = self.GetBranch(branch_name)
1339 base = branch.LocalMerge
1340 if branch.LocalMerge:
1341 rb = ReviewableBranch(self, branch, base)
1342 if rb.commits:
1343 return rb
1344 return None
1345
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001346 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001347 people=([], []),
Mike Frysinger819cc812020-02-19 02:27:22 -05001348 dryrun=False,
Brian Harring435370c2012-07-28 15:37:04 -07001349 auto_topic=False,
Mike Frysinger84685ba2020-02-19 02:22:22 -05001350 hashtags=(),
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001351 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001352 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001353 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001354 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001355 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001356 validate_certs=True,
1357 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001358 """Uploads the named branch for code review.
1359 """
1360 if branch is None:
1361 branch = self.CurrentBranch
1362 if branch is None:
1363 raise GitError('not currently on a branch')
1364
1365 branch = self.GetBranch(branch)
1366 if not branch.LocalMerge:
1367 raise GitError('branch %s does not track a remote' % branch.name)
1368 if not branch.remote.review:
1369 raise GitError('remote %s has no review url' % branch.remote.name)
1370
Bryan Jacobsf609f912013-05-06 13:36:24 -04001371 if dest_branch is None:
1372 dest_branch = self.dest_branch
1373 if dest_branch is None:
1374 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001375 if not dest_branch.startswith(R_HEADS):
1376 dest_branch = R_HEADS + dest_branch
1377
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001378 if not branch.remote.projectname:
1379 branch.remote.projectname = self.name
1380 branch.remote.Save()
1381
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001382 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001383 if url is None:
1384 raise UploadError('review not configured')
1385 cmd = ['push']
Mike Frysinger819cc812020-02-19 02:27:22 -05001386 if dryrun:
1387 cmd.append('-n')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001388
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001389 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001390 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001391
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001392 for push_option in (push_options or []):
1393 cmd.append('-o')
1394 cmd.append(push_option)
1395
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001396 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001397
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001398 if dest_branch.startswith(R_HEADS):
1399 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001400
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001401 upload_type = 'for'
1402 if draft:
1403 upload_type = 'drafts'
1404
1405 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1406 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001407 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001408 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001409 opts += ['topic=' + branch.name]
Mike Frysinger84685ba2020-02-19 02:22:22 -05001410 opts += ['t=%s' % p for p in hashtags]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001411
David Pursehousef25a3702018-11-14 19:01:22 -08001412 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001413 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001414 if notify:
1415 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001416 if private:
1417 opts += ['private']
1418 if wip:
1419 opts += ['wip']
1420 if opts:
1421 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001422 cmd.append(ref_spec)
1423
Anthony King7bdac712014-07-16 12:56:40 +01001424 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001425 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001426
1427 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1428 self.bare_git.UpdateRef(R_PUB + branch.name,
1429 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001430 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001431
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001432# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001433 def _ExtractArchive(self, tarpath, path=None):
1434 """Extract the given tar on its current location
1435
1436 Args:
1437 - tarpath: The path to the actual tar file
1438
1439 """
1440 try:
1441 with tarfile.open(tarpath, 'r') as tar:
1442 tar.extractall(path=path)
1443 return True
1444 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001445 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001446 return False
1447
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001448 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001449 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05001450 verbose=False,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001451 is_new=None,
1452 current_branch_only=False,
1453 force_sync=False,
1454 clone_bundle=True,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001455 tags=True,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001456 archive=False,
1457 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001458 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001459 submodules=False,
1460 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001461 """Perform only the network IO portion of the sync process.
1462 Local working directory/branch state is not affected.
1463 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001464 if archive and not isinstance(self, MetaProject):
1465 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001466 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001467 return False
1468
1469 name = self.relpath.replace('\\', '/')
1470 name = name.replace('/', '_')
1471 tarpath = '%s.tar' % name
1472 topdir = self.manifest.topdir
1473
1474 try:
1475 self._FetchArchive(tarpath, cwd=topdir)
1476 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001477 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001478 return False
1479
1480 # From now on, we only need absolute tarpath
1481 tarpath = os.path.join(topdir, tarpath)
1482
1483 if not self._ExtractArchive(tarpath, path=topdir):
1484 return False
1485 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001486 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001487 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001488 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001489 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001490 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001491 if is_new is None:
1492 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001493 if is_new:
David Pursehousee8ace262020-02-13 12:41:15 +09001494 self._InitGitDir(force_sync=force_sync, quiet=quiet)
Jimmie Westera0444582012-10-24 13:44:42 +02001495 else:
David Pursehousee8ace262020-02-13 12:41:15 +09001496 self._UpdateHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001497 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001498
1499 if is_new:
1500 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1501 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001502 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001503 # This works for both absolute and relative alternate directories.
1504 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001505 except IOError:
1506 alt_dir = None
1507 else:
1508 alt_dir = None
1509
Mike Frysingere50b6a72020-02-19 01:45:48 -05001510 if (clone_bundle
1511 and alt_dir is None
1512 and self._ApplyCloneBundle(initial=is_new, quiet=quiet, verbose=verbose)):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001513 is_new = False
1514
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001515 if not current_branch_only:
1516 if self.sync_c:
1517 current_branch_only = True
1518 elif not self.manifest._loaded:
1519 # Manifest cannot check defaults until it syncs.
1520 current_branch_only = False
1521 elif self.manifest.default.sync_c:
1522 current_branch_only = True
1523
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001524 if not self.sync_tags:
1525 tags = False
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001526
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001527 if self.clone_depth:
1528 depth = self.clone_depth
1529 else:
1530 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1531
Mike Frysinger521d01b2020-02-17 01:51:49 -05001532 # See if we can skip the network fetch entirely.
1533 if not (optimized_fetch and
1534 (ID_RE.match(self.revisionExpr) and
1535 self._CheckForImmutableRevision())):
1536 if not self._RemoteFetch(
David Pursehouse3cceda52020-02-18 14:11:39 +09001537 initial=is_new, quiet=quiet, verbose=verbose, alt_dir=alt_dir,
1538 current_branch_only=current_branch_only,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001539 tags=tags, prune=prune, depth=depth,
David Pursehouse3cceda52020-02-18 14:11:39 +09001540 submodules=submodules, force_sync=force_sync,
1541 clone_filter=clone_filter):
Mike Frysinger521d01b2020-02-17 01:51:49 -05001542 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001543
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001544 mp = self.manifest.manifestProject
1545 dissociate = mp.config.GetBoolean('repo.dissociate')
1546 if dissociate:
1547 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1548 if os.path.exists(alternates_file):
1549 cmd = ['repack', '-a', '-d']
1550 if GitCommand(self, cmd, bare=True).Wait() != 0:
1551 return False
1552 platform_utils.remove(alternates_file)
1553
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001554 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001555 self._InitMRef()
1556 else:
1557 self._InitMirrorHead()
1558 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001559 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001560 except OSError:
1561 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001562 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001563
1564 def PostRepoUpgrade(self):
1565 self._InitHooks()
1566
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001567 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001568 if self.manifest.isGitcClient:
1569 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001570 for copyfile in self.copyfiles:
1571 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001572 for linkfile in self.linkfiles:
1573 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574
Julien Camperguedd654222014-01-09 16:21:37 +01001575 def GetCommitRevisionId(self):
1576 """Get revisionId of a commit.
1577
1578 Use this method instead of GetRevisionId to get the id of the commit rather
1579 than the id of the current git object (for example, a tag)
1580
1581 """
1582 if not self.revisionExpr.startswith(R_TAGS):
1583 return self.GetRevisionId(self._allrefs)
1584
1585 try:
1586 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1587 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001588 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1589 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001590
David Pursehouse8a68ff92012-09-24 12:15:13 +09001591 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001592 if self.revisionId:
1593 return self.revisionId
1594
1595 rem = self.GetRemote(self.remote.name)
1596 rev = rem.ToLocal(self.revisionExpr)
1597
David Pursehouse8a68ff92012-09-24 12:15:13 +09001598 if all_refs is not None and rev in all_refs:
1599 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001600
1601 try:
1602 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1603 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001604 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1605 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001606
Martin Kellye4e94d22017-03-21 16:05:12 -07001607 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001608 """Perform only the local IO portion of the sync process.
1609 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001610 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001611 if not os.path.exists(self.gitdir):
1612 syncbuf.fail(self,
1613 'Cannot checkout %s due to missing network sync; Run '
1614 '`repo sync -n %s` first.' %
1615 (self.name, self.name))
1616 return
1617
Martin Kellye4e94d22017-03-21 16:05:12 -07001618 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001619 all_refs = self.bare_ref.all
1620 self.CleanPublishedCache(all_refs)
1621 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001622
David Pursehouse1d947b32012-10-25 12:23:11 +09001623 def _doff():
1624 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001625 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001626
Martin Kellye4e94d22017-03-21 16:05:12 -07001627 def _dosubmodules():
1628 self._SyncSubmodules(quiet=True)
1629
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001630 head = self.work_git.GetHead()
1631 if head.startswith(R_HEADS):
1632 branch = head[len(R_HEADS):]
1633 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001634 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001635 except KeyError:
1636 head = None
1637 else:
1638 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001640 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001641 # Currently on a detached HEAD. The user is assumed to
1642 # not have any local modifications worth worrying about.
1643 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001644 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001645 syncbuf.fail(self, _PriorSyncFailedError())
1646 return
1647
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001648 if head == revid:
1649 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001650 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001651 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001652 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001653 # The copy/linkfile config may have changed.
1654 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001655 return
1656 else:
1657 lost = self._revlist(not_rev(revid), HEAD)
1658 if lost:
1659 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001660
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001661 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001662 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001663 if submodules:
1664 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001665 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001666 syncbuf.fail(self, e)
1667 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001668 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001669 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001671 if head == revid:
1672 # No changes; don't do anything further.
1673 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001674 # The copy/linkfile config may have changed.
1675 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001676 return
1677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001679
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001680 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001682 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001684 syncbuf.info(self,
1685 "leaving %s; does not track upstream",
1686 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001688 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001689 if submodules:
1690 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001691 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001692 syncbuf.fail(self, e)
1693 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001694 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001695 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001696
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001697 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001698
1699 # See if we can perform a fast forward merge. This can happen if our
1700 # branch isn't in the exact same state as we last published.
1701 try:
1702 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1703 # Skip the published logic.
1704 pub = False
1705 except GitError:
1706 pub = self.WasPublished(branch.name, all_refs)
1707
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001709 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710 if not_merged:
1711 if upstream_gain:
1712 # The user has published this branch and some of those
1713 # commits are not yet merged upstream. We do not want
1714 # to rewrite the published commits so we punt.
1715 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001716 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001717 "branch %s is published (but not merged) and is now "
1718 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001719 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001720 elif pub == head:
1721 # All published commits are merged, and thus we are a
1722 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001723 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001724 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001725 if submodules:
1726 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001727 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001729 # Examine the local commits not in the remote. Find the
1730 # last one attributed to this user, if any.
1731 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001732 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001733 last_mine = None
1734 cnt_mine = 0
1735 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001736 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001737 if committer_email == self.UserEmail:
1738 last_mine = commit_id
1739 cnt_mine += 1
1740
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001741 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001742 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001743
1744 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001745 syncbuf.fail(self, _DirtyError())
1746 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001747
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001748 # If the upstream switched on us, warn the user.
1749 #
1750 if branch.merge != self.revisionExpr:
1751 if branch.merge and self.revisionExpr:
1752 syncbuf.info(self,
1753 'manifest switched %s...%s',
1754 branch.merge,
1755 self.revisionExpr)
1756 elif branch.merge:
1757 syncbuf.info(self,
1758 'manifest no longer tracks %s',
1759 branch.merge)
1760
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001761 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001762 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001763 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001764 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001765 syncbuf.info(self,
1766 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001767 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001769 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001770 if not ID_RE.match(self.revisionExpr):
1771 # in case of manifest sync the revisionExpr might be a SHA1
1772 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001773 if not branch.merge.startswith('refs/'):
1774 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001775 branch.Save()
1776
Mike Pontillod3153822012-02-28 11:53:24 -08001777 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001778 def _docopyandlink():
1779 self._CopyAndLinkFiles()
1780
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001781 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001782 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001783 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001784 if submodules:
1785 syncbuf.later2(self, _dosubmodules)
1786 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001787 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001788 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001789 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001790 if submodules:
1791 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001792 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001793 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001794 syncbuf.fail(self, e)
1795 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001796 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001797 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001798 if submodules:
1799 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001800
Mike Frysingere6a202f2019-08-02 15:57:57 -04001801 def AddCopyFile(self, src, dest, topdir):
1802 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001803
Mike Frysingere6a202f2019-08-02 15:57:57 -04001804 No filesystem changes occur here. Actual copying happens later on.
1805
1806 Paths should have basic validation run on them before being queued.
1807 Further checking will be handled when the actual copy happens.
1808 """
1809 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1810
1811 def AddLinkFile(self, src, dest, topdir):
1812 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1813
1814 No filesystem changes occur here. Actual linking happens later on.
1815
1816 Paths should have basic validation run on them before being queued.
1817 Further checking will be handled when the actual link happens.
1818 """
1819 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001820
James W. Mills24c13082012-04-12 15:04:13 -05001821 def AddAnnotation(self, name, value, keep):
1822 self.annotations.append(_Annotation(name, value, keep))
1823
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001824 def DownloadPatchSet(self, change_id, patch_id):
1825 """Download a single patch set of a single change to FETCH_HEAD.
1826 """
1827 remote = self.GetRemote(self.remote.name)
1828
1829 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001830 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001831 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001832 if GitCommand(self, cmd, bare=True).Wait() != 0:
1833 return None
1834 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001835 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001836 change_id,
1837 patch_id,
1838 self.bare_git.rev_parse('FETCH_HEAD'))
1839
Mike Frysingerc0d18662020-02-19 19:19:18 -05001840 def DeleteWorktree(self, quiet=False, force=False):
1841 """Delete the source checkout and any other housekeeping tasks.
1842
1843 This currently leaves behind the internal .repo/ cache state. This helps
1844 when switching branches or manifest changes get reverted as we don't have
1845 to redownload all the git objects. But we should do some GC at some point.
1846
1847 Args:
1848 quiet: Whether to hide normal messages.
1849 force: Always delete tree even if dirty.
1850
1851 Returns:
1852 True if the worktree was completely cleaned out.
1853 """
1854 if self.IsDirty():
1855 if force:
1856 print('warning: %s: Removing dirty project: uncommitted changes lost.' %
1857 (self.relpath,), file=sys.stderr)
1858 else:
1859 print('error: %s: Cannot remove project: uncommitted changes are '
1860 'present.\n' % (self.relpath,), file=sys.stderr)
1861 return False
1862
1863 if not quiet:
1864 print('%s: Deleting obsolete checkout.' % (self.relpath,))
1865
1866 # Unlock and delink from the main worktree. We don't use git's worktree
1867 # remove because it will recursively delete projects -- we handle that
1868 # ourselves below. https://crbug.com/git/48
1869 if self.use_git_worktrees:
1870 needle = platform_utils.realpath(self.gitdir)
1871 # Find the git worktree commondir under .repo/worktrees/.
1872 output = self.bare_git.worktree('list', '--porcelain').splitlines()[0]
1873 assert output.startswith('worktree '), output
1874 commondir = output[9:]
1875 # Walk each of the git worktrees to see where they point.
1876 configs = os.path.join(commondir, 'worktrees')
1877 for name in os.listdir(configs):
1878 gitdir = os.path.join(configs, name, 'gitdir')
1879 with open(gitdir) as fp:
1880 relpath = fp.read().strip()
1881 # Resolve the checkout path and see if it matches this project.
1882 fullpath = platform_utils.realpath(os.path.join(configs, name, relpath))
1883 if fullpath == needle:
1884 platform_utils.rmtree(os.path.join(configs, name))
1885
1886 # Delete the .git directory first, so we're less likely to have a partially
1887 # working git repository around. There shouldn't be any git projects here,
1888 # so rmtree works.
1889
1890 # Try to remove plain files first in case of git worktrees. If this fails
1891 # for any reason, we'll fall back to rmtree, and that'll display errors if
1892 # it can't remove things either.
1893 try:
1894 platform_utils.remove(self.gitdir)
1895 except OSError:
1896 pass
1897 try:
1898 platform_utils.rmtree(self.gitdir)
1899 except OSError as e:
1900 if e.errno != errno.ENOENT:
1901 print('error: %s: %s' % (self.gitdir, e), file=sys.stderr)
1902 print('error: %s: Failed to delete obsolete checkout; remove manually, '
1903 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr)
1904 return False
1905
1906 # Delete everything under the worktree, except for directories that contain
1907 # another git project.
1908 dirs_to_remove = []
1909 failed = False
1910 for root, dirs, files in platform_utils.walk(self.worktree):
1911 for f in files:
1912 path = os.path.join(root, f)
1913 try:
1914 platform_utils.remove(path)
1915 except OSError as e:
1916 if e.errno != errno.ENOENT:
1917 print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr)
1918 failed = True
1919 dirs[:] = [d for d in dirs
1920 if not os.path.lexists(os.path.join(root, d, '.git'))]
1921 dirs_to_remove += [os.path.join(root, d) for d in dirs
1922 if os.path.join(root, d) not in dirs_to_remove]
1923 for d in reversed(dirs_to_remove):
1924 if platform_utils.islink(d):
1925 try:
1926 platform_utils.remove(d)
1927 except OSError as e:
1928 if e.errno != errno.ENOENT:
1929 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1930 failed = True
1931 elif not platform_utils.listdir(d):
1932 try:
1933 platform_utils.rmdir(d)
1934 except OSError as e:
1935 if e.errno != errno.ENOENT:
1936 print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr)
1937 failed = True
1938 if failed:
1939 print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,),
1940 file=sys.stderr)
1941 print(' Remove manually, then run `repo sync -l`.', file=sys.stderr)
1942 return False
1943
1944 # Try deleting parent dirs if they are empty.
1945 path = self.worktree
1946 while path != self.manifest.topdir:
1947 try:
1948 platform_utils.rmdir(path)
1949 except OSError as e:
1950 if e.errno != errno.ENOENT:
1951 break
1952 path = os.path.dirname(path)
1953
1954 return True
1955
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001956# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001957 def GetHeadPath(self):
1958 """Return the full path to the HEAD ref."""
1959 dotgit = os.path.join(self.worktree, '.git')
1960 if os.path.isfile(dotgit):
1961 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1962 with open(dotgit) as fp:
1963 setting = fp.read()
1964 assert setting.startswith('gitdir:')
1965 gitdir = setting.split(':', 1)[1].strip()
1966 dotgit = os.path.join(self.worktree, gitdir)
1967 return os.path.join(dotgit, HEAD)
1968
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001969 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001970 """Create a new branch off the manifest's revision.
1971 """
Simran Basib9a1b732015-08-20 12:19:28 -07001972 if not branch_merge:
1973 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001974 head = self.work_git.GetHead()
1975 if head == (R_HEADS + name):
1976 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001977
David Pursehouse8a68ff92012-09-24 12:15:13 +09001978 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001979 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001980 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001981 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001982 capture_stdout=True,
1983 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001984
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001985 branch = self.GetBranch(name)
1986 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001987 branch.merge = branch_merge
1988 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1989 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001990
1991 if revision is None:
1992 revid = self.GetRevisionId(all_refs)
1993 else:
1994 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001995
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001996 if head.startswith(R_HEADS):
1997 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001998 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001999 except KeyError:
2000 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002001 if revid and head and revid == head:
Mike Frysinger746e7f62020-02-19 15:49:02 -05002002 ref = R_HEADS + name
2003 self.work_git.update_ref(ref, revid)
2004 self.work_git.symbolic_ref(HEAD, ref)
2005 branch.Save()
2006 return True
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002007
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002008 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002009 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01002010 capture_stdout=True,
2011 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07002012 branch.Save()
2013 return True
2014 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002015
Wink Saville02d79452009-04-10 13:01:24 -07002016 def CheckoutBranch(self, name):
2017 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07002018
2019 Args:
2020 name: The name of the branch to checkout.
2021
2022 Returns:
2023 True if the checkout succeeded; False if it didn't; None if the branch
2024 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07002025 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002026 rev = R_HEADS + name
2027 head = self.work_git.GetHead()
2028 if head == rev:
2029 # Already on the branch
2030 #
2031 return True
Wink Saville02d79452009-04-10 13:01:24 -07002032
David Pursehouse8a68ff92012-09-24 12:15:13 +09002033 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07002034 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002035 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002036 except KeyError:
2037 # Branch does not exist in this project
2038 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07002039 return None
Wink Saville02d79452009-04-10 13:01:24 -07002040
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002041 if head.startswith(R_HEADS):
2042 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09002043 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002044 except KeyError:
2045 head = None
2046
2047 if head == revid:
2048 # Same revision; just update HEAD to point to the new
2049 # target branch, but otherwise take no other action.
2050 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05002051 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07002052 return True
2053
2054 return GitCommand(self,
2055 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002056 capture_stdout=True,
2057 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002058
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002059 def AbandonBranch(self, name):
2060 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002061
2062 Args:
2063 name: The name of the branch to abandon.
2064
2065 Returns:
2066 True if the abandon succeeded; False if it didn't; None if the branch
2067 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002068 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002069 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002070 all_refs = self.bare_ref.all
2071 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002072 # Doesn't exist
2073 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002074
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002075 head = self.work_git.GetHead()
2076 if head == rev:
2077 # We can't destroy the branch while we are sitting
2078 # on it. Switch to a detached HEAD.
2079 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002080 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002081
David Pursehouse8a68ff92012-09-24 12:15:13 +09002082 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002083 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05002084 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002085 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002086 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002087
2088 return GitCommand(self,
2089 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002090 capture_stdout=True,
2091 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002092
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002093 def PruneHeads(self):
2094 """Prune any topic branches already merged into upstream.
2095 """
2096 cb = self.CurrentBranch
2097 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002098 left = self._allrefs
2099 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002100 if name.startswith(R_HEADS):
2101 name = name[len(R_HEADS):]
2102 if cb is None or name != cb:
2103 kill.append(name)
2104
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002105 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002106 if cb is not None \
2107 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002108 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002109 self.work_git.DetachHead(HEAD)
2110 kill.append(cb)
2111
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002112 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002113 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002114
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002115 try:
2116 self.bare_git.DetachHead(rev)
2117
2118 b = ['branch', '-d']
2119 b.extend(kill)
2120 b = GitCommand(self, b, bare=True,
2121 capture_stdout=True,
2122 capture_stderr=True)
2123 b.Wait()
2124 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002125 if ID_RE.match(old):
2126 self.bare_git.DetachHead(old)
2127 else:
2128 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002129 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002130
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002131 for branch in kill:
2132 if (R_HEADS + branch) not in left:
2133 self.CleanPublishedCache()
2134 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002135
2136 if cb and cb not in kill:
2137 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002138 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002139
2140 kept = []
2141 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002142 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002143 branch = self.GetBranch(branch)
2144 base = branch.LocalMerge
2145 if not base:
2146 base = rev
2147 kept.append(ReviewableBranch(self, branch, base))
2148 return kept
2149
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002150# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002151 def GetRegisteredSubprojects(self):
2152 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002153
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002154 def rec(subprojects):
2155 if not subprojects:
2156 return
2157 result.extend(subprojects)
2158 for p in subprojects:
2159 rec(p.subprojects)
2160 rec(self.subprojects)
2161 return result
2162
2163 def _GetSubmodules(self):
2164 # Unfortunately we cannot call `git submodule status --recursive` here
2165 # because the working tree might not exist yet, and it cannot be used
2166 # without a working tree in its current implementation.
2167
2168 def get_submodules(gitdir, rev):
2169 # Parse .gitmodules for submodule sub_paths and sub_urls
2170 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2171 if not sub_paths:
2172 return []
2173 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2174 # revision of submodule repository
2175 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2176 submodules = []
2177 for sub_path, sub_url in zip(sub_paths, sub_urls):
2178 try:
2179 sub_rev = sub_revs[sub_path]
2180 except KeyError:
2181 # Ignore non-exist submodules
2182 continue
2183 submodules.append((sub_rev, sub_path, sub_url))
2184 return submodules
2185
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002186 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2187 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002188
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002189 def parse_gitmodules(gitdir, rev):
2190 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2191 try:
Anthony King7bdac712014-07-16 12:56:40 +01002192 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2193 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002194 except GitError:
2195 return [], []
2196 if p.Wait() != 0:
2197 return [], []
2198
2199 gitmodules_lines = []
2200 fd, temp_gitmodules_path = tempfile.mkstemp()
2201 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002202 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002203 os.close(fd)
2204 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002205 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2206 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002207 if p.Wait() != 0:
2208 return [], []
2209 gitmodules_lines = p.stdout.split('\n')
2210 except GitError:
2211 return [], []
2212 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002213 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002214
2215 names = set()
2216 paths = {}
2217 urls = {}
2218 for line in gitmodules_lines:
2219 if not line:
2220 continue
2221 m = re_path.match(line)
2222 if m:
2223 names.add(m.group(1))
2224 paths[m.group(1)] = m.group(2)
2225 continue
2226 m = re_url.match(line)
2227 if m:
2228 names.add(m.group(1))
2229 urls[m.group(1)] = m.group(2)
2230 continue
2231 names = sorted(names)
2232 return ([paths.get(name, '') for name in names],
2233 [urls.get(name, '') for name in names])
2234
2235 def git_ls_tree(gitdir, rev, paths):
2236 cmd = ['ls-tree', rev, '--']
2237 cmd.extend(paths)
2238 try:
Anthony King7bdac712014-07-16 12:56:40 +01002239 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2240 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002241 except GitError:
2242 return []
2243 if p.Wait() != 0:
2244 return []
2245 objects = {}
2246 for line in p.stdout.split('\n'):
2247 if not line.strip():
2248 continue
2249 object_rev, object_path = line.split()[2:4]
2250 objects[object_path] = object_rev
2251 return objects
2252
2253 try:
2254 rev = self.GetRevisionId()
2255 except GitError:
2256 return []
2257 return get_submodules(self.gitdir, rev)
2258
2259 def GetDerivedSubprojects(self):
2260 result = []
2261 if not self.Exists:
2262 # If git repo does not exist yet, querying its submodules will
2263 # mess up its states; so return here.
2264 return result
2265 for rev, path, url in self._GetSubmodules():
2266 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002267 relpath, worktree, gitdir, objdir = \
2268 self.manifest.GetSubprojectPaths(self, name, path)
2269 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002270 if project:
2271 result.extend(project.GetDerivedSubprojects())
2272 continue
David James8d201162013-10-11 17:03:19 -07002273
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002274 if url.startswith('..'):
2275 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002276 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002277 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002278 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002279 review=self.remote.review,
2280 revision=self.remote.revision)
2281 subproject = Project(manifest=self.manifest,
2282 name=name,
2283 remote=remote,
2284 gitdir=gitdir,
2285 objdir=objdir,
2286 worktree=worktree,
2287 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002288 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002289 revisionId=rev,
2290 rebase=self.rebase,
2291 groups=self.groups,
2292 sync_c=self.sync_c,
2293 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002294 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002295 parent=self,
2296 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002297 result.append(subproject)
2298 result.extend(subproject.GetDerivedSubprojects())
2299 return result
2300
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002301# Direct Git Commands ##
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002302 def EnableRepositoryExtension(self, key, value='true', version=1):
2303 """Enable git repository extension |key| with |value|.
2304
2305 Args:
2306 key: The extension to enabled. Omit the "extensions." prefix.
2307 value: The value to use for the extension.
2308 version: The minimum git repository version needed.
2309 """
2310 # Make sure the git repo version is new enough already.
2311 found_version = self.config.GetInt('core.repositoryFormatVersion')
2312 if found_version is None:
2313 found_version = 0
2314 if found_version < version:
2315 self.config.SetString('core.repositoryFormatVersion', str(version))
2316
2317 # Enable the extension!
2318 self.config.SetString('extensions.%s' % (key,), value)
2319
Zac Livingstone4332262017-06-16 08:56:09 -06002320 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002321 try:
2322 # if revision (sha or tag) is not present then following function
2323 # throws an error.
2324 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2325 return True
2326 except GitError:
2327 # There is no such persistent revision. We have to fetch it.
2328 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002329
Julien Campergue335f5ef2013-10-16 11:02:35 +02002330 def _FetchArchive(self, tarpath, cwd=None):
2331 cmd = ['archive', '-v', '-o', tarpath]
2332 cmd.append('--remote=%s' % self.remote.url)
2333 cmd.append('--prefix=%s/' % self.relpath)
2334 cmd.append(self.revisionExpr)
2335
2336 command = GitCommand(self, cmd, cwd=cwd,
2337 capture_stdout=True,
2338 capture_stderr=True)
2339
2340 if command.Wait() != 0:
2341 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2342
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002343 def _RemoteFetch(self, name=None,
2344 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002345 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002346 quiet=False,
Mike Frysinger521d01b2020-02-17 01:51:49 -05002347 verbose=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002348 alt_dir=None,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002349 tags=True,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002350 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002351 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002352 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002353 force_sync=False,
2354 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002355
2356 is_sha1 = False
2357 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002358 # The depth should not be used when fetching to a mirror because
2359 # it will result in a shallow repository that cannot be cloned or
2360 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002361 # The repo project should also never be synced with partial depth.
2362 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2363 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002364
Shawn Pearce69e04d82014-01-29 12:48:54 -08002365 if depth:
2366 current_branch_only = True
2367
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002368 if ID_RE.match(self.revisionExpr) is not None:
2369 is_sha1 = True
2370
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002371 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002372 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002373 # this is a tag and its sha1 value should never change
2374 tag_name = self.revisionExpr[len(R_TAGS):]
2375
2376 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002377 if self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002378 if verbose:
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002379 print('Skipped fetching project %s (already have persistent ref)'
2380 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002381 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002382 if is_sha1 and not depth:
2383 # When syncing a specific commit and --depth is not set:
2384 # * if upstream is explicitly specified and is not a sha1, fetch only
2385 # upstream as users expect only upstream to be fetch.
2386 # Note: The commit might not be in upstream in which case the sync
2387 # will fail.
2388 # * otherwise, fetch all branches to make sure we end up with the
2389 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002390 if self.upstream:
2391 current_branch_only = not ID_RE.match(self.upstream)
2392 else:
2393 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002394
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002395 if not name:
2396 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002397
2398 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002399 remote = self.GetRemote(name)
2400 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002401 ssh_proxy = True
2402
Shawn O. Pearce88443382010-10-08 10:02:09 +02002403 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002404 if alt_dir and 'objects' == os.path.basename(alt_dir):
2405 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002406 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2407 remote = self.GetRemote(name)
2408
David Pursehouse8a68ff92012-09-24 12:15:13 +09002409 all_refs = self.bare_ref.all
2410 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002411 tmp = set()
2412
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302413 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002414 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002415 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002416 all_refs[r] = ref_id
2417 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002418 continue
2419
David Pursehouse8a68ff92012-09-24 12:15:13 +09002420 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002421 continue
2422
David Pursehouse8a68ff92012-09-24 12:15:13 +09002423 r = 'refs/_alt/%s' % ref_id
2424 all_refs[r] = ref_id
2425 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002426 tmp.add(r)
2427
heping3d7bbc92017-04-12 19:51:47 +08002428 tmp_packed_lines = []
2429 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002430
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302431 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002432 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002433 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002434 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002435 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002436
heping3d7bbc92017-04-12 19:51:47 +08002437 tmp_packed = ''.join(tmp_packed_lines)
2438 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002439 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002440 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002441 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002442
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002443 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002444
Xin Li745be2e2019-06-03 11:24:30 -07002445 if clone_filter:
2446 git_require((2, 19, 0), fail=True, msg='partial clones')
2447 cmd.append('--filter=%s' % clone_filter)
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002448 self.EnableRepositoryExtension('partialclone', self.remote.name)
Xin Li745be2e2019-06-03 11:24:30 -07002449
Conley Owensf97e8382015-01-21 11:12:46 -08002450 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002451 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002452 else:
2453 # If this repo has shallow objects, then we don't know which refs have
2454 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2455 # do this with projects that don't have shallow objects, since it is less
2456 # efficient.
2457 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2458 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002459
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002460 if quiet:
2461 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002462 if not self.worktree:
2463 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002464 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002465
Mike Frysingere57f1142019-03-18 21:27:54 -04002466 if force_sync:
2467 cmd.append('--force')
2468
David Pursehouse74cfd272015-10-14 10:50:15 +09002469 if prune:
2470 cmd.append('--prune')
2471
Martin Kellye4e94d22017-03-21 16:05:12 -07002472 if submodules:
2473 cmd.append('--recurse-submodules=on-demand')
2474
Kuang-che Wu6856f982019-11-25 12:37:55 +08002475 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002476 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002477 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002478 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002479 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002480 spec.append('tag')
2481 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002482
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302483 if self.manifest.IsMirror and not current_branch_only:
2484 branch = None
2485 else:
2486 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002487 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002488 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002489 # Shallow checkout of a specific commit, fetch from that commit and not
2490 # the heads only as the commit might be deeper in the history.
2491 spec.append(branch)
2492 else:
2493 if is_sha1:
2494 branch = self.upstream
2495 if branch is not None and branch.strip():
2496 if not branch.startswith('refs/'):
2497 branch = R_HEADS + branch
2498 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2499
2500 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2501 # whole repo.
2502 if self.manifest.IsMirror and not spec:
2503 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2504
2505 # If using depth then we should not get all the tags since they may
2506 # be outside of the depth.
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05002507 if not tags or depth:
Kuang-che Wu6856f982019-11-25 12:37:55 +08002508 cmd.append('--no-tags')
2509 else:
2510 cmd.append('--tags')
2511 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2512
Conley Owens80b87fe2014-05-09 17:13:44 -07002513 cmd.extend(spec)
2514
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002515 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002516 for _i in range(2):
Mike Frysinger31990f02020-02-17 01:35:18 -05002517 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy,
2518 merge_output=True, capture_stdout=not verbose)
John L. Villalovos126e2982015-01-29 21:58:12 -08002519 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002520 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002521 ok = True
2522 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002523 # If needed, run the 'git remote prune' the first time through the loop
2524 elif (not _i and
2525 "error:" in gitcmd.stderr and
2526 "git remote prune" in gitcmd.stderr):
2527 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002528 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002529 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002530 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002531 break
2532 continue
Brian Harring14a66742012-09-28 20:21:57 -07002533 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002534 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2535 # in sha1 mode, we just tried sync'ing from the upstream field; it
2536 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002537 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002538 elif ret < 0:
2539 # Git died with a signal, exit immediately
2540 break
Mike Frysinger31990f02020-02-17 01:35:18 -05002541 if not verbose:
2542 print('%s:\n%s' % (self.name, gitcmd.stdout), file=sys.stderr)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002543 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002544
2545 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002546 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002547 if old_packed != '':
2548 _lwrite(packed_refs, old_packed)
2549 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002550 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002551 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002552
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002553 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002554 # We just synced the upstream given branch; verify we
2555 # got what we wanted, else trigger a second run of all
2556 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002557 if not self._CheckForImmutableRevision():
Mike Frysinger521d01b2020-02-17 01:51:49 -05002558 # Sync the current branch only with depth set to None.
2559 # We always pass depth=None down to avoid infinite recursion.
2560 return self._RemoteFetch(
2561 name=name, quiet=quiet, verbose=verbose,
2562 current_branch_only=current_branch_only and depth,
2563 initial=False, alt_dir=alt_dir,
2564 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002565
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002566 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002567
Mike Frysingere50b6a72020-02-19 01:45:48 -05002568 def _ApplyCloneBundle(self, initial=False, quiet=False, verbose=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002569 if initial and \
2570 (self.manifest.manifestProject.config.GetString('repo.depth') or
2571 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002572 return False
2573
2574 remote = self.GetRemote(self.remote.name)
2575 bundle_url = remote.url + '/clone.bundle'
2576 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002577 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2578 'persistent-http',
2579 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002580 return False
2581
2582 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2583 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2584
2585 exist_dst = os.path.exists(bundle_dst)
2586 exist_tmp = os.path.exists(bundle_tmp)
2587
2588 if not initial and not exist_dst and not exist_tmp:
2589 return False
2590
2591 if not exist_dst:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002592 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet,
2593 verbose)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002594 if not exist_dst:
2595 return False
2596
2597 cmd = ['fetch']
2598 if quiet:
2599 cmd.append('--quiet')
2600 if not self.worktree:
2601 cmd.append('--update-head-ok')
2602 cmd.append(bundle_dst)
2603 for f in remote.fetch:
2604 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002605 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002606
2607 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002608 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002609 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002610 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002611 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002612 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002613
Mike Frysingere50b6a72020-02-19 01:45:48 -05002614 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002615 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002616 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002617
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002618 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002619 if quiet:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002620 cmd += ['--silent', '--show-error']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002621 if os.path.exists(tmpPath):
2622 size = os.stat(tmpPath).st_size
2623 if size >= 1024:
2624 cmd += ['--continue-at', '%d' % (size,)]
2625 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002626 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002627 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002628 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002629 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002630 if proxy:
2631 cmd += ['--proxy', proxy]
2632 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2633 cmd += ['--proxy', os.environ['http_proxy']]
2634 if srcUrl.startswith('persistent-https'):
2635 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2636 elif srcUrl.startswith('persistent-http'):
2637 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002638 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002639
Dave Borowitz137d0132015-01-02 11:12:54 -08002640 if IsTrace():
2641 Trace('%s', ' '.join(cmd))
Mike Frysingere50b6a72020-02-19 01:45:48 -05002642 if verbose:
2643 print('%s: Downloading bundle: %s' % (self.name, srcUrl))
2644 stdout = None if verbose else subprocess.PIPE
2645 stderr = None if verbose else subprocess.STDOUT
Dave Borowitz137d0132015-01-02 11:12:54 -08002646 try:
Mike Frysingere50b6a72020-02-19 01:45:48 -05002647 proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
Dave Borowitz137d0132015-01-02 11:12:54 -08002648 except OSError:
2649 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002650
Mike Frysingere50b6a72020-02-19 01:45:48 -05002651 (output, _) = proc.communicate()
2652 curlret = proc.returncode
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002653
Dave Borowitz137d0132015-01-02 11:12:54 -08002654 if curlret == 22:
2655 # From curl man page:
2656 # 22: HTTP page not retrieved. The requested url was not found or
2657 # returned another error with the HTTP error code being 400 or above.
2658 # This return code only appears if -f, --fail is used.
2659 if not quiet:
2660 print("Server does not provide clone.bundle; ignoring.",
2661 file=sys.stderr)
2662 return False
Mike Frysingere50b6a72020-02-19 01:45:48 -05002663 elif curlret and not verbose and output:
2664 print('%s' % output, file=sys.stderr)
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002665
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002666 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002667 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002668 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002669 return True
2670 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002671 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002672 return False
2673 else:
2674 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002675
Kris Giesingc8d882a2014-12-23 13:02:32 -08002676 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002677 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002678 with open(path, 'rb') as f:
2679 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002680 return True
2681 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002682 if not quiet:
2683 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002684 return False
2685 except OSError:
2686 return False
2687
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002688 def _Checkout(self, rev, quiet=False):
2689 cmd = ['checkout']
2690 if quiet:
2691 cmd.append('-q')
2692 cmd.append(rev)
2693 cmd.append('--')
2694 if GitCommand(self, cmd).Wait() != 0:
2695 if self._allrefs:
2696 raise GitError('%s checkout %s ' % (self.name, rev))
2697
Anthony King7bdac712014-07-16 12:56:40 +01002698 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002699 cmd = ['cherry-pick']
2700 cmd.append(rev)
2701 cmd.append('--')
2702 if GitCommand(self, cmd).Wait() != 0:
2703 if self._allrefs:
2704 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2705
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302706 def _LsRemote(self, refs):
2707 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302708 p = GitCommand(self, cmd, capture_stdout=True)
2709 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002710 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302711 return None
2712
Anthony King7bdac712014-07-16 12:56:40 +01002713 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002714 cmd = ['revert']
2715 cmd.append('--no-edit')
2716 cmd.append(rev)
2717 cmd.append('--')
2718 if GitCommand(self, cmd).Wait() != 0:
2719 if self._allrefs:
2720 raise GitError('%s revert %s ' % (self.name, rev))
2721
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002722 def _ResetHard(self, rev, quiet=True):
2723 cmd = ['reset', '--hard']
2724 if quiet:
2725 cmd.append('-q')
2726 cmd.append(rev)
2727 if GitCommand(self, cmd).Wait() != 0:
2728 raise GitError('%s reset --hard %s ' % (self.name, rev))
2729
Martin Kellye4e94d22017-03-21 16:05:12 -07002730 def _SyncSubmodules(self, quiet=True):
2731 cmd = ['submodule', 'update', '--init', '--recursive']
2732 if quiet:
2733 cmd.append('-q')
2734 if GitCommand(self, cmd).Wait() != 0:
2735 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2736
Anthony King7bdac712014-07-16 12:56:40 +01002737 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002738 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002739 if onto is not None:
2740 cmd.extend(['--onto', onto])
2741 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002742 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002743 raise GitError('%s rebase %s ' % (self.name, upstream))
2744
Pierre Tardy3d125942012-05-04 12:18:12 +02002745 def _FastForward(self, head, ffonly=False):
Mike Frysinger2b1345b2020-02-17 01:07:11 -05002746 cmd = ['merge', '--no-stat', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002747 if ffonly:
2748 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002749 if GitCommand(self, cmd).Wait() != 0:
2750 raise GitError('%s merge %s ' % (self.name, head))
2751
David Pursehousee8ace262020-02-13 12:41:15 +09002752 def _InitGitDir(self, mirror_git=None, force_sync=False, quiet=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002753 init_git_dir = not os.path.exists(self.gitdir)
2754 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002755 try:
2756 # Initialize the bare repository, which contains all of the objects.
2757 if init_obj_dir:
2758 os.makedirs(self.objdir)
2759 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002760
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002761 # Enable per-worktree config file support if possible. This is more a
2762 # nice-to-have feature for users rather than a hard requirement.
2763 if self.use_git_worktrees and git_require((2, 19, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -05002764 self.EnableRepositoryExtension('worktreeConfig')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002765
Kevin Degib1a07b82015-07-27 13:33:43 -06002766 # If we have a separate directory to hold refs, initialize it as well.
2767 if self.objdir != self.gitdir:
2768 if init_git_dir:
2769 os.makedirs(self.gitdir)
2770
2771 if init_obj_dir or init_git_dir:
2772 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2773 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002774 try:
2775 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2776 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002777 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002778 print("Retrying clone after deleting %s" %
2779 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002780 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002781 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2782 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002783 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002784 platform_utils.rmtree(platform_utils.realpath(self.worktree))
David Pursehousee8ace262020-02-13 12:41:15 +09002785 return self._InitGitDir(mirror_git=mirror_git, force_sync=False,
2786 quiet=quiet)
David Pursehouse145e35b2020-02-12 15:40:47 +09002787 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002788 raise e
2789 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002790
Kevin Degi384b3c52014-10-16 16:02:58 -06002791 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002792 mp = self.manifest.manifestProject
2793 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002794
Kevin Degib1a07b82015-07-27 13:33:43 -06002795 if ref_dir or mirror_git:
2796 if not mirror_git:
2797 mirror_git = os.path.join(ref_dir, self.name + '.git')
2798 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2799 self.relpath + '.git')
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002800 worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees',
2801 self.name + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002802
Kevin Degib1a07b82015-07-27 13:33:43 -06002803 if os.path.exists(mirror_git):
2804 ref_dir = mirror_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002805 elif os.path.exists(repo_git):
2806 ref_dir = repo_git
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002807 elif os.path.exists(worktrees_git):
2808 ref_dir = worktrees_git
Kevin Degib1a07b82015-07-27 13:33:43 -06002809 else:
2810 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002811
Kevin Degib1a07b82015-07-27 13:33:43 -06002812 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002813 if not os.path.isabs(ref_dir):
2814 # The alternate directory is relative to the object database.
2815 ref_dir = os.path.relpath(ref_dir,
2816 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002817 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2818 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002819
David Pursehousee8ace262020-02-13 12:41:15 +09002820 self._UpdateHooks(quiet=quiet)
Kevin Degib1a07b82015-07-27 13:33:43 -06002821
2822 m = self.manifest.manifestProject.config
2823 for key in ['user.name', 'user.email']:
2824 if m.Has(key, include_defaults=False):
2825 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002826 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002827 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002828 if self.manifest.IsMirror:
2829 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002830 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002831 self.config.SetString('core.bare', None)
2832 except Exception:
2833 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002834 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002835 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002836 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002837 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002838
David Pursehousee8ace262020-02-13 12:41:15 +09002839 def _UpdateHooks(self, quiet=False):
Jimmie Westera0444582012-10-24 13:44:42 +02002840 if os.path.exists(self.gitdir):
David Pursehousee8ace262020-02-13 12:41:15 +09002841 self._InitHooks(quiet=quiet)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002842
David Pursehousee8ace262020-02-13 12:41:15 +09002843 def _InitHooks(self, quiet=False):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002844 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002845 if not os.path.exists(hooks):
2846 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002847 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002848 name = os.path.basename(stock_hook)
2849
Victor Boivie65e0f352011-04-18 11:23:29 +02002850 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002851 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002852 # Don't install a Gerrit Code Review hook if this
2853 # project does not appear to use it for reviews.
2854 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002855 # Since the manifest project is one of those, but also
2856 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002857 continue
2858
2859 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002860 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002861 continue
2862 if os.path.exists(dst):
Mike Frysinger7951e142020-02-21 00:04:15 -05002863 # If the files are the same, we'll leave it alone. We create symlinks
2864 # below by default but fallback to hardlinks if the OS blocks them.
2865 # So if we're here, it's probably because we made a hardlink below.
2866 if not filecmp.cmp(stock_hook, dst, shallow=False):
David Pursehousee8ace262020-02-13 12:41:15 +09002867 if not quiet:
2868 _warn("%s: Not replacing locally modified %s hook",
2869 self.relpath, name)
Mike Frysinger7951e142020-02-21 00:04:15 -05002870 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002871 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002872 platform_utils.symlink(
2873 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002874 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002875 if e.errno == errno.EPERM:
Mike Frysinger7951e142020-02-21 00:04:15 -05002876 try:
2877 os.link(stock_hook, dst)
2878 except OSError:
2879 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002880 else:
2881 raise
2882
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002883 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002884 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002885 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002886 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002887 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002888 remote.review = self.remote.review
2889 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002891 if self.worktree:
2892 remote.ResetFetch(mirror=False)
2893 else:
2894 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002895 remote.Save()
2896
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002897 def _InitMRef(self):
2898 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002899 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002900
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002901 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002902 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002903
2904 def _InitAnyMRef(self, ref):
2905 cur = self.bare_ref.symref(ref)
2906
2907 if self.revisionId:
2908 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2909 msg = 'manifest set to %s' % self.revisionId
2910 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002911 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002912 else:
2913 remote = self.GetRemote(self.remote.name)
2914 dst = remote.ToLocal(self.revisionExpr)
2915 if cur != dst:
2916 msg = 'manifest set to %s' % self.revisionExpr
2917 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002918
Kevin Degi384b3c52014-10-16 16:02:58 -06002919 def _CheckDirReference(self, srcdir, destdir, share_refs):
Mike Frysinger979d5bd2020-02-09 02:28:34 -05002920 # Git worktrees don't use symlinks to share at all.
2921 if self.use_git_worktrees:
2922 return
2923
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002924 symlink_files = self.shareable_files[:]
2925 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002926 if share_refs:
2927 symlink_files += self.working_tree_files
2928 symlink_dirs += self.working_tree_dirs
2929 to_symlink = symlink_files + symlink_dirs
2930 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002931 # Try to self-heal a bit in simple cases.
2932 dst_path = os.path.join(destdir, name)
2933 src_path = os.path.join(srcdir, name)
2934
2935 if name in self.working_tree_dirs:
2936 # If the dir is missing under .repo/projects/, create it.
2937 if not os.path.exists(src_path):
2938 os.makedirs(src_path)
2939
2940 elif name in self.working_tree_files:
2941 # If it's a file under the checkout .git/ and the .repo/projects/ has
2942 # nothing, move the file under the .repo/projects/ tree.
2943 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2944 platform_utils.rename(dst_path, src_path)
2945
2946 # If the path exists under the .repo/projects/ and there's no symlink
2947 # under the checkout .git/, recreate the symlink.
2948 if name in self.working_tree_dirs or name in self.working_tree_files:
2949 if os.path.exists(src_path) and not os.path.exists(dst_path):
2950 platform_utils.symlink(
2951 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2952
2953 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002954 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002955 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002956 # Fail if the links are pointing to the wrong place
2957 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002958 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002959 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002960 'work tree. If you\'re comfortable with the '
2961 'possibility of losing the work tree\'s git metadata,'
2962 ' use `repo sync --force-sync {0}` to '
2963 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002964
David James8d201162013-10-11 17:03:19 -07002965 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2966 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2967
2968 Args:
2969 gitdir: The bare git repository. Must already be initialized.
2970 dotgit: The repository you would like to initialize.
2971 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2972 Only one work tree can store refs under a given |gitdir|.
2973 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2974 This saves you the effort of initializing |dotgit| yourself.
2975 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002976 symlink_files = self.shareable_files[:]
2977 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002978 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002979 symlink_files += self.working_tree_files
2980 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002981 to_symlink = symlink_files + symlink_dirs
2982
2983 to_copy = []
2984 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002985 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002986
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002987 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002988 for name in set(to_copy).union(to_symlink):
2989 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002990 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002991 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002992
Kevin Degi384b3c52014-10-16 16:02:58 -06002993 if os.path.lexists(dst):
2994 continue
David James8d201162013-10-11 17:03:19 -07002995
2996 # If the source dir doesn't exist, create an empty dir.
2997 if name in symlink_dirs and not os.path.lexists(src):
2998 os.makedirs(src)
2999
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003000 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07003001 platform_utils.symlink(
3002 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003003 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07003004 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02003005 shutil.copytree(src, dst)
3006 elif os.path.isfile(src):
3007 shutil.copy(src, dst)
3008
Conley Owens80b87fe2014-05-09 17:13:44 -07003009 # If the source file doesn't exist, ensure the destination
3010 # file doesn't either.
3011 if name in symlink_files and not os.path.lexists(src):
3012 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08003013 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07003014 except OSError:
3015 pass
3016
David James8d201162013-10-11 17:03:19 -07003017 except OSError as e:
3018 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08003019 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07003020 else:
3021 raise
3022
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003023 def _InitGitWorktree(self):
3024 """Init the project using git worktrees."""
3025 self.bare_git.worktree('prune')
3026 self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock',
3027 self.worktree, self.GetRevisionId())
3028
3029 # Rewrite the internal state files to use relative paths between the
3030 # checkouts & worktrees.
3031 dotgit = os.path.join(self.worktree, '.git')
3032 with open(dotgit, 'r') as fp:
3033 # Figure out the checkout->worktree path.
3034 setting = fp.read()
3035 assert setting.startswith('gitdir:')
3036 git_worktree_path = setting.split(':', 1)[1].strip()
3037 # Use relative path from checkout->worktree.
3038 with open(dotgit, 'w') as fp:
3039 print('gitdir:', os.path.relpath(git_worktree_path, self.worktree),
3040 file=fp)
3041 # Use relative path from worktree->checkout.
3042 with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
3043 print(os.path.relpath(dotgit, git_worktree_path), file=fp)
3044
Martin Kellye4e94d22017-03-21 16:05:12 -07003045 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05003046 realdotgit = os.path.join(self.worktree, '.git')
3047 tmpdotgit = realdotgit + '.tmp'
3048 init_dotgit = not os.path.exists(realdotgit)
3049 if init_dotgit:
Mike Frysinger979d5bd2020-02-09 02:28:34 -05003050 if self.use_git_worktrees:
3051 self._InitGitWorktree()
3052 self._CopyAndLinkFiles()
3053 return
3054
Mike Frysingerf4545122019-11-11 04:34:16 -05003055 dotgit = tmpdotgit
3056 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
3057 os.makedirs(tmpdotgit)
3058 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
3059 copy_all=False)
3060 else:
3061 dotgit = realdotgit
3062
Kevin Degib1a07b82015-07-27 13:33:43 -06003063 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05003064 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
3065 except GitError as e:
3066 if force_sync and not init_dotgit:
3067 try:
3068 platform_utils.rmtree(dotgit)
3069 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09003070 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05003071 raise e
3072 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003073
Mike Frysingerf4545122019-11-11 04:34:16 -05003074 if init_dotgit:
3075 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06003076
Mike Frysingerf4545122019-11-11 04:34:16 -05003077 # Now that the .git dir is fully set up, move it to its final home.
3078 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003079
Mike Frysingerf4545122019-11-11 04:34:16 -05003080 # Finish checking out the worktree.
3081 cmd = ['read-tree', '--reset', '-u']
3082 cmd.append('-v')
3083 cmd.append(HEAD)
3084 if GitCommand(self, cmd).Wait() != 0:
3085 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01003086
Mike Frysingerf4545122019-11-11 04:34:16 -05003087 if submodules:
3088 self._SyncSubmodules(quiet=True)
3089 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003090
Renaud Paquay788e9622017-01-27 11:41:12 -08003091 def _get_symlink_error_message(self):
3092 if platform_utils.isWindows():
3093 return ('Unable to create symbolic link. Please re-run the command as '
3094 'Administrator, or see '
3095 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
3096 'for other options.')
3097 return 'filesystem must support symlinks'
3098
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003099 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07003100 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003101
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003102 def _revlist(self, *args, **kw):
3103 a = []
3104 a.extend(args)
3105 a.append('--')
3106 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003107
3108 @property
3109 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07003110 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003111
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003112 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003113 """Get logs between two revisions of this project."""
3114 comp = '..'
3115 if rev1:
3116 revs = [rev1]
3117 if rev2:
3118 revs.extend([comp, rev2])
3119 cmd = ['log', ''.join(revs)]
3120 out = DiffColoring(self.config)
3121 if out.is_on and color:
3122 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003123 if pretty_format is not None:
3124 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003125 if oneline:
3126 cmd.append('--oneline')
3127
3128 try:
3129 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3130 if log.Wait() == 0:
3131 return log.stdout
3132 except GitError:
3133 # worktree may not exist if groups changed for example. In that case,
3134 # try in gitdir instead.
3135 if not os.path.exists(self.worktree):
3136 return self.bare_git.log(*cmd[1:])
3137 else:
3138 raise
3139 return None
3140
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003141 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3142 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003143 """Get the list of logs from this revision to given revisionId"""
3144 logs = {}
3145 selfId = self.GetRevisionId(self._allrefs)
3146 toId = toProject.GetRevisionId(toProject._allrefs)
3147
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003148 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3149 pretty_format=pretty_format)
3150 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3151 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003152 return logs
3153
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003154 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003155
David James8d201162013-10-11 17:03:19 -07003156 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003157 self._project = project
3158 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003159 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003160
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003161 def LsOthers(self):
3162 p = GitCommand(self._project,
3163 ['ls-files',
3164 '-z',
3165 '--others',
3166 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003167 bare=False,
David James8d201162013-10-11 17:03:19 -07003168 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003169 capture_stdout=True,
3170 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003171 if p.Wait() == 0:
3172 out = p.stdout
3173 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003174 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003175 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003176 return []
3177
3178 def DiffZ(self, name, *args):
3179 cmd = [name]
3180 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07003181 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003182 cmd.extend(args)
3183 p = GitCommand(self._project,
3184 cmd,
David James8d201162013-10-11 17:03:19 -07003185 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003186 bare=False,
3187 capture_stdout=True,
3188 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003189 try:
3190 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003191 if not hasattr(out, 'encode'):
3192 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003193 r = {}
3194 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003195 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003196 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003197 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003198 info = next(out)
3199 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003200 except StopIteration:
3201 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003202
3203 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003204
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003205 def __init__(self, path, omode, nmode, oid, nid, state):
3206 self.path = path
3207 self.src_path = None
3208 self.old_mode = omode
3209 self.new_mode = nmode
3210 self.old_id = oid
3211 self.new_id = nid
3212
3213 if len(state) == 1:
3214 self.status = state
3215 self.level = None
3216 else:
3217 self.status = state[:1]
3218 self.level = state[1:]
3219 while self.level.startswith('0'):
3220 self.level = self.level[1:]
3221
3222 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003223 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003224 if info.status in ('R', 'C'):
3225 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003226 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003227 r[info.path] = info
3228 return r
3229 finally:
3230 p.Wait()
3231
3232 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003233 if self._bare:
3234 path = os.path.join(self._project.gitdir, HEAD)
3235 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003236 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003237 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003238 with open(path) as fd:
3239 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003240 except IOError as e:
3241 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003242 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303243 line = line.decode()
3244 except AttributeError:
3245 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003246 if line.startswith('ref: '):
3247 return line[5:-1]
3248 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003249
3250 def SetHead(self, ref, message=None):
3251 cmdv = []
3252 if message is not None:
3253 cmdv.extend(['-m', message])
3254 cmdv.append(HEAD)
3255 cmdv.append(ref)
3256 self.symbolic_ref(*cmdv)
3257
3258 def DetachHead(self, new, message=None):
3259 cmdv = ['--no-deref']
3260 if message is not None:
3261 cmdv.extend(['-m', message])
3262 cmdv.append(HEAD)
3263 cmdv.append(new)
3264 self.update_ref(*cmdv)
3265
3266 def UpdateRef(self, name, new, old=None,
3267 message=None,
3268 detach=False):
3269 cmdv = []
3270 if message is not None:
3271 cmdv.extend(['-m', message])
3272 if detach:
3273 cmdv.append('--no-deref')
3274 cmdv.append(name)
3275 cmdv.append(new)
3276 if old is not None:
3277 cmdv.append(old)
3278 self.update_ref(*cmdv)
3279
3280 def DeleteRef(self, name, old=None):
3281 if not old:
3282 old = self.rev_parse(name)
3283 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003284 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003285
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003286 def rev_list(self, *args, **kw):
3287 if 'format' in kw:
3288 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3289 else:
3290 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003291 cmdv.extend(args)
3292 p = GitCommand(self._project,
3293 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003294 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003295 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003296 capture_stdout=True,
3297 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003298 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003299 raise GitError('%s rev-list %s: %s' %
3300 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003301 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003302
3303 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003304 """Allow arbitrary git commands using pythonic syntax.
3305
3306 This allows you to do things like:
3307 git_obj.rev_parse('HEAD')
3308
3309 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3310 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003311 Any other positional arguments will be passed to the git command, and the
3312 following keyword arguments are supported:
3313 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003314
3315 Args:
3316 name: The name of the git command to call. Any '_' characters will
3317 be replaced with '-'.
3318
3319 Returns:
3320 A callable object that will try to call git with the named command.
3321 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003322 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003323
Dave Borowitz091f8932012-10-23 17:01:04 -07003324 def runner(*args, **kwargs):
3325 cmdv = []
3326 config = kwargs.pop('config', None)
3327 for k in kwargs:
3328 raise TypeError('%s() got an unexpected keyword argument %r'
3329 % (name, k))
3330 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303331 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003332 cmdv.append('-c')
3333 cmdv.append('%s=%s' % (k, v))
3334 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003335 cmdv.extend(args)
3336 p = GitCommand(self._project,
3337 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003338 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003339 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003340 capture_stdout=True,
3341 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003342 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003343 raise GitError('%s %s: %s' %
3344 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003345 r = p.stdout
3346 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3347 return r[:-1]
3348 return r
3349 return runner
3350
3351
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003352class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003353
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003354 def __str__(self):
3355 return 'prior sync failed; rebase still in progress'
3356
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003357
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003358class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003359
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003360 def __str__(self):
3361 return 'contains uncommitted changes'
3362
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003363
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003364class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003365
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003366 def __init__(self, project, text):
3367 self.project = project
3368 self.text = text
3369
3370 def Print(self, syncbuf):
3371 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3372 syncbuf.out.nl()
3373
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003374
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003375class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003376
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003377 def __init__(self, project, why):
3378 self.project = project
3379 self.why = why
3380
3381 def Print(self, syncbuf):
3382 syncbuf.out.fail('error: %s/: %s',
3383 self.project.relpath,
3384 str(self.why))
3385 syncbuf.out.nl()
3386
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003387
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003388class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003389
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003390 def __init__(self, project, action):
3391 self.project = project
3392 self.action = action
3393
3394 def Run(self, syncbuf):
3395 out = syncbuf.out
3396 out.project('project %s/', self.project.relpath)
3397 out.nl()
3398 try:
3399 self.action()
3400 out.nl()
3401 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003402 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003403 out.nl()
3404 return False
3405
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003406
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003407class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003408
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003409 def __init__(self, config):
3410 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003411 self.project = self.printer('header', attr='bold')
3412 self.info = self.printer('info')
3413 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003414
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003415
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003416class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003417
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003418 def __init__(self, config, detach_head=False):
3419 self._messages = []
3420 self._failures = []
3421 self._later_queue1 = []
3422 self._later_queue2 = []
3423
3424 self.out = _SyncColoring(config)
3425 self.out.redirect(sys.stderr)
3426
3427 self.detach_head = detach_head
3428 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003429 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003430
3431 def info(self, project, fmt, *args):
3432 self._messages.append(_InfoMessage(project, fmt % args))
3433
3434 def fail(self, project, err=None):
3435 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003436 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003437
3438 def later1(self, project, what):
3439 self._later_queue1.append(_Later(project, what))
3440
3441 def later2(self, project, what):
3442 self._later_queue2.append(_Later(project, what))
3443
3444 def Finish(self):
3445 self._PrintMessages()
3446 self._RunLater()
3447 self._PrintMessages()
3448 return self.clean
3449
David Rileye0684ad2017-04-05 00:02:59 -07003450 def Recently(self):
3451 recent_clean = self.recent_clean
3452 self.recent_clean = True
3453 return recent_clean
3454
3455 def _MarkUnclean(self):
3456 self.clean = False
3457 self.recent_clean = False
3458
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003459 def _RunLater(self):
3460 for q in ['_later_queue1', '_later_queue2']:
3461 if not self._RunQueue(q):
3462 return
3463
3464 def _RunQueue(self, queue):
3465 for m in getattr(self, queue):
3466 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003467 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003468 return False
3469 setattr(self, queue, [])
3470 return True
3471
3472 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003473 if self._messages or self._failures:
3474 if os.isatty(2):
3475 self.out.write(progress.CSI_ERASE_LINE)
3476 self.out.write('\r')
3477
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003478 for m in self._messages:
3479 m.Print(self)
3480 for m in self._failures:
3481 m.Print(self)
3482
3483 self._messages = []
3484 self._failures = []
3485
3486
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003487class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003488
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003489 """A special project housed under .repo.
3490 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003491
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003492 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003493 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003494 manifest=manifest,
3495 name=name,
3496 gitdir=gitdir,
3497 objdir=gitdir,
3498 worktree=worktree,
3499 remote=RemoteSpec('origin'),
3500 relpath='.repo/%s' % name,
3501 revisionExpr='refs/heads/master',
3502 revisionId=None,
3503 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003504
3505 def PreSync(self):
3506 if self.Exists:
3507 cb = self.CurrentBranch
3508 if cb:
3509 base = self.GetBranch(cb).merge
3510 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003511 self.revisionExpr = base
3512 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003513
Martin Kelly224a31a2017-07-10 14:46:25 -07003514 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003515 """ Prepare MetaProject for manifest branch switch
3516 """
3517
3518 # detach and delete manifest branch, allowing a new
3519 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003520 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003521 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003522 syncbuf.Finish()
3523
3524 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003525 ['update-ref', '-d', 'refs/heads/default'],
3526 capture_stdout=True,
3527 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003528
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003529 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003530 def LastFetch(self):
3531 try:
3532 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3533 return os.path.getmtime(fh)
3534 except OSError:
3535 return 0
3536
3537 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003538 def HasChanges(self):
3539 """Has the remote received new commits not yet checked out?
3540 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003541 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003542 return False
3543
David Pursehouse8a68ff92012-09-24 12:15:13 +09003544 all_refs = self.bare_ref.all
3545 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003546 head = self.work_git.GetHead()
3547 if head.startswith(R_HEADS):
3548 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003549 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003550 except KeyError:
3551 head = None
3552
3553 if revid == head:
3554 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003555 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003556 return True
3557 return False