blob: a305d72006acdcba14a1b638d200729deecf5ec8 [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
Chirayu Desai217ea7d2013-03-01 19:14:38 +053055 input = raw_input
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
Jonathan Nieder93719792015-03-17 11:29:58 -070088_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070089
90
Jonathan Nieder93719792015-03-17 11:29:58 -070091def _ProjectHooks():
92 """List the hooks present in the 'hooks' directory.
93
94 These hooks are project hooks and are copied to the '.git/hooks' directory
95 of all subprojects.
96
97 This function caches the list of hooks (based on the contents of the
98 'repo/hooks' directory) on the first call.
99
100 Returns:
101 A list of absolute paths to all of the files in the hooks directory.
102 """
103 global _project_hook_list
104 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700105 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700106 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700107 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700108 return _project_hook_list
109
110
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700111class DownloadedChange(object):
112 _commit_cache = None
113
114 def __init__(self, project, base, change_id, ps_id, commit):
115 self.project = project
116 self.base = base
117 self.change_id = change_id
118 self.ps_id = ps_id
119 self.commit = commit
120
121 @property
122 def commits(self):
123 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700124 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
125 '--abbrev-commit',
126 '--pretty=oneline',
127 '--reverse',
128 '--date-order',
129 not_rev(self.base),
130 self.commit,
131 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700132 return self._commit_cache
133
134
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135class ReviewableBranch(object):
136 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400137 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700138
139 def __init__(self, project, branch, base):
140 self.project = project
141 self.branch = branch
142 self.base = base
143
144 @property
145 def name(self):
146 return self.branch.name
147
148 @property
149 def commits(self):
150 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400151 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
152 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
153 try:
154 self._commit_cache = self.project.bare_git.rev_list(*args)
155 except GitError:
156 # We weren't able to probe the commits for this branch. Was it tracking
157 # a branch that no longer exists? If so, return no commits. Otherwise,
158 # rethrow the error as we don't know what's going on.
159 if self.base_exists:
160 raise
161
162 self._commit_cache = []
163
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164 return self._commit_cache
165
166 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800167 def unabbrev_commits(self):
168 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700169 for commit in self.project.bare_git.rev_list(not_rev(self.base),
170 R_HEADS + self.name,
171 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800172 r[commit[0:8]] = commit
173 return r
174
175 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700177 return self.project.bare_git.log('--pretty=format:%cd',
178 '-n', '1',
179 R_HEADS + self.name,
180 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181
Mike Frysinger6da17752019-09-11 18:43:17 -0400182 @property
183 def base_exists(self):
184 """Whether the branch we're tracking exists.
185
186 Normally it should, but sometimes branches we track can get deleted.
187 """
188 if self._base_exists is None:
189 try:
190 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
191 # If we're still here, the base branch exists.
192 self._base_exists = True
193 except GitError:
194 # If we failed to verify, the base branch doesn't exist.
195 self._base_exists = False
196
197 return self._base_exists
198
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700199 def UploadForReview(self, people,
200 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000201 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200202 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700203 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200204 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200205 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800206 validate_certs=True,
207 push_options=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800208 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700209 people,
Brian Harring435370c2012-07-28 15:37:04 -0700210 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000211 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200212 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700213 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200214 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200215 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800216 validate_certs=validate_certs,
217 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700219 def GetPublishedRefs(self):
220 refs = {}
221 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700222 self.branch.remote.SshReviewUrl(self.project.UserEmail),
223 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700224 for line in output.split('\n'):
225 try:
226 (sha, ref) = line.split()
227 refs[sha] = ref
228 except ValueError:
229 pass
230
231 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700235
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236 def __init__(self, config):
237 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100238 self.project = self.printer('header', attr='bold')
239 self.branch = self.printer('header', attr='bold')
240 self.nobranch = self.printer('nobranch', fg='red')
241 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242
Anthony King7bdac712014-07-16 12:56:40 +0100243 self.added = self.printer('added', fg='green')
244 self.changed = self.printer('changed', fg='red')
245 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246
247
248class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700249
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700250 def __init__(self, config):
251 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100252 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400253 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700255
Anthony King7bdac712014-07-16 12:56:40 +0100256class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700257
James W. Mills24c13082012-04-12 15:04:13 -0500258 def __init__(self, name, value, keep):
259 self.name = name
260 self.value = value
261 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700263
Mike Frysingere6a202f2019-08-02 15:57:57 -0400264def _SafeExpandPath(base, subpath, skipfinal=False):
265 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700266
Mike Frysingere6a202f2019-08-02 15:57:57 -0400267 We make sure no intermediate symlinks are traversed, and that the final path
268 is not a special file (e.g. not a socket or fifo).
269
270 NB: We rely on a number of paths already being filtered out while parsing the
271 manifest. See the validation logic in manifest_xml.py for more details.
272 """
273 components = subpath.split(os.path.sep)
274 if skipfinal:
275 # Whether the caller handles the final component itself.
276 finalpart = components.pop()
277
278 path = base
279 for part in components:
280 if part in {'.', '..'}:
281 raise ManifestInvalidPathError(
282 '%s: "%s" not allowed in paths' % (subpath, part))
283
284 path = os.path.join(path, part)
285 if platform_utils.islink(path):
286 raise ManifestInvalidPathError(
287 '%s: traversing symlinks not allow' % (path,))
288
289 if os.path.exists(path):
290 if not os.path.isfile(path) and not platform_utils.isdir(path):
291 raise ManifestInvalidPathError(
292 '%s: only regular files & directories allowed' % (path,))
293
294 if skipfinal:
295 path = os.path.join(path, finalpart)
296
297 return path
298
299
300class _CopyFile(object):
301 """Container for <copyfile> manifest element."""
302
303 def __init__(self, git_worktree, src, topdir, dest):
304 """Register a <copyfile> request.
305
306 Args:
307 git_worktree: Absolute path to the git project checkout.
308 src: Relative path under |git_worktree| of file to read.
309 topdir: Absolute path to the top of the repo client checkout.
310 dest: Relative path under |topdir| of file to write.
311 """
312 self.git_worktree = git_worktree
313 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700314 self.src = src
315 self.dest = dest
316
317 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400318 src = _SafeExpandPath(self.git_worktree, self.src)
319 dest = _SafeExpandPath(self.topdir, self.dest)
320
321 if platform_utils.isdir(src):
322 raise ManifestInvalidPathError(
323 '%s: copying from directory not supported' % (self.src,))
324 if platform_utils.isdir(dest):
325 raise ManifestInvalidPathError(
326 '%s: copying to directory not allowed' % (self.dest,))
327
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328 # copy file if it does not exist or is out of date
329 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
330 try:
331 # remove existing file first, since it might be read-only
332 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800333 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400334 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200335 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700336 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200337 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700338 shutil.copy(src, dest)
339 # make the file read-only
340 mode = os.stat(dest)[stat.ST_MODE]
341 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
342 os.chmod(dest, mode)
343 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700344 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700345
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700346
Anthony King7bdac712014-07-16 12:56:40 +0100347class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400348 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700349
Mike Frysingere6a202f2019-08-02 15:57:57 -0400350 def __init__(self, git_worktree, src, topdir, dest):
351 """Register a <linkfile> request.
352
353 Args:
354 git_worktree: Absolute path to the git project checkout.
355 src: Target of symlink relative to path under |git_worktree|.
356 topdir: Absolute path to the top of the repo client checkout.
357 dest: Relative path under |topdir| of symlink to create.
358 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700359 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400360 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500361 self.src = src
362 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500363
Wink Saville4c426ef2015-06-03 08:05:17 -0700364 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500365 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700366 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500367 try:
368 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800369 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800370 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500371 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700372 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700373 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500374 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700375 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500376 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700377 _error('Cannot link file %s to %s', relSrc, absDest)
378
379 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400380 """Link the self.src & self.dest paths.
381
382 Handles wild cards on the src linking all of the files in the source in to
383 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700384 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500385 # Some people use src="." to create stable links to projects. Lets allow
386 # that but reject all other uses of "." to keep things simple.
387 if self.src == '.':
388 src = self.git_worktree
389 else:
390 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400391
392 if os.path.exists(src):
393 # Entity exists so just a simple one to one link operation.
394 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
395 # dest & src are absolute paths at this point. Make sure the target of
396 # the symlink is relative in the context of the repo client checkout.
397 relpath = os.path.relpath(src, os.path.dirname(dest))
398 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700399 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400400 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700401 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400402 if os.path.exists(dest) and not platform_utils.isdir(dest):
403 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700404 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400405 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700406 # Create a releative path from source dir to destination dir
407 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400408 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700409
410 # Get the source file name
411 srcFile = os.path.basename(absSrcFile)
412
413 # Now form the final full paths to srcFile. They will be
414 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400415 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700416 relSrc = os.path.join(relSrcDir, srcFile)
417 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500418
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700419
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700420class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700421
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700422 def __init__(self,
423 name,
Anthony King7bdac712014-07-16 12:56:40 +0100424 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700425 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100426 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700427 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700428 orig_name=None,
429 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700430 self.name = name
431 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700432 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700433 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100434 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700435 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700436 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700437
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700438
Doug Anderson37282b42011-03-04 11:54:18 -0800439class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700440
Doug Anderson37282b42011-03-04 11:54:18 -0800441 """A RepoHook contains information about a script to run as a hook.
442
443 Hooks are used to run a python script before running an upload (for instance,
444 to run presubmit checks). Eventually, we may have hooks for other actions.
445
446 This shouldn't be confused with files in the 'repo/hooks' directory. Those
447 files are copied into each '.git/hooks' folder for each project. Repo-level
448 hooks are associated instead with repo actions.
449
450 Hooks are always python. When a hook is run, we will load the hook into the
451 interpreter and execute its main() function.
452 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700453
Doug Anderson37282b42011-03-04 11:54:18 -0800454 def __init__(self,
455 hook_type,
456 hooks_project,
457 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400458 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800459 abort_if_user_denies=False):
460 """RepoHook constructor.
461
462 Params:
463 hook_type: A string representing the type of hook. This is also used
464 to figure out the name of the file containing the hook. For
465 example: 'pre-upload'.
466 hooks_project: The project containing the repo hooks. If you have a
467 manifest, this is manifest.repo_hooks_project. OK if this is None,
468 which will make the hook a no-op.
469 topdir: Repo's top directory (the one containing the .repo directory).
470 Scripts will run with CWD as this directory. If you have a manifest,
471 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800473 abort_if_user_denies: If True, we'll throw a HookError() if the user
474 doesn't allow us to run the hook.
475 """
476 self._hook_type = hook_type
477 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400478 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800479 self._topdir = topdir
480 self._abort_if_user_denies = abort_if_user_denies
481
482 # Store the full path to the script for convenience.
483 if self._hooks_project:
484 self._script_fullpath = os.path.join(self._hooks_project.worktree,
485 self._hook_type + '.py')
486 else:
487 self._script_fullpath = None
488
489 def _GetHash(self):
490 """Return a hash of the contents of the hooks directory.
491
492 We'll just use git to do this. This hash has the property that if anything
493 changes in the directory we will return a different has.
494
495 SECURITY CONSIDERATION:
496 This hash only represents the contents of files in the hook directory, not
497 any other files imported or called by hooks. Changes to imported files
498 can change the script behavior without affecting the hash.
499
500 Returns:
501 A string representing the hash. This will always be ASCII so that it can
502 be printed to the user easily.
503 """
504 assert self._hooks_project, "Must have hooks to calculate their hash."
505
506 # We will use the work_git object rather than just calling GetRevisionId().
507 # That gives us a hash of the latest checked in version of the files that
508 # the user will actually be executing. Specifically, GetRevisionId()
509 # doesn't appear to change even if a user checks out a different version
510 # of the hooks repo (via git checkout) nor if a user commits their own revs.
511 #
512 # NOTE: Local (non-committed) changes will not be factored into this hash.
513 # I think this is OK, since we're really only worried about warning the user
514 # about upstream changes.
515 return self._hooks_project.work_git.rev_parse('HEAD')
516
517 def _GetMustVerb(self):
518 """Return 'must' if the hook is required; 'should' if not."""
519 if self._abort_if_user_denies:
520 return 'must'
521 else:
522 return 'should'
523
524 def _CheckForHookApproval(self):
525 """Check to see whether this hook has been approved.
526
Mike Frysinger40252c22016-08-15 21:23:44 -0400527 We'll accept approval of manifest URLs if they're using secure transports.
528 This way the user can say they trust the manifest hoster. For insecure
529 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800530
531 Note that we ask permission for each individual hook even though we use
532 the hash of all hooks when detecting changes. We'd like the user to be
533 able to approve / deny each hook individually. We only use the hash of all
534 hooks because there is no other easy way to detect changes to local imports.
535
536 Returns:
537 True if this hook is approved to run; False otherwise.
538
539 Raises:
540 HookError: Raised if the user doesn't approve and abort_if_user_denies
541 was passed to the consturctor.
542 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400543 if self._ManifestUrlHasSecureScheme():
544 return self._CheckForHookApprovalManifest()
545 else:
546 return self._CheckForHookApprovalHash()
547
548 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
549 changed_prompt):
550 """Check for approval for a particular attribute and hook.
551
552 Args:
553 subkey: The git config key under [repo.hooks.<hook_type>] to store the
554 last approved string.
555 new_val: The new value to compare against the last approved one.
556 main_prompt: Message to display to the user to ask for approval.
557 changed_prompt: Message explaining why we're re-asking for approval.
558
559 Returns:
560 True if this hook is approved to run; False otherwise.
561
562 Raises:
563 HookError: Raised if the user doesn't approve and abort_if_user_denies
564 was passed to the consturctor.
565 """
Doug Anderson37282b42011-03-04 11:54:18 -0800566 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400567 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800568
Mike Frysinger40252c22016-08-15 21:23:44 -0400569 # Get the last value that the user approved for this hook; may be None.
570 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800571
Mike Frysinger40252c22016-08-15 21:23:44 -0400572 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800573 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400574 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800575 # Approval matched. We're done.
576 return True
577 else:
578 # Give the user a reason why we're prompting, since they last told
579 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400580 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800581 else:
582 prompt = ''
583
584 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
585 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400586 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530587 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900588 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800589
590 # User is doing a one-time approval.
591 if response in ('y', 'yes'):
592 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400593 elif response == 'always':
594 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800595 return True
596
597 # For anything else, we'll assume no approval.
598 if self._abort_if_user_denies:
599 raise HookError('You must allow the %s hook or use --no-verify.' %
600 self._hook_type)
601
602 return False
603
Mike Frysinger40252c22016-08-15 21:23:44 -0400604 def _ManifestUrlHasSecureScheme(self):
605 """Check if the URI for the manifest is a secure transport."""
606 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
607 parse_results = urllib.parse.urlparse(self._manifest_url)
608 return parse_results.scheme in secure_schemes
609
610 def _CheckForHookApprovalManifest(self):
611 """Check whether the user has approved this manifest host.
612
613 Returns:
614 True if this hook is approved to run; False otherwise.
615 """
616 return self._CheckForHookApprovalHelper(
617 'approvedmanifest',
618 self._manifest_url,
619 'Run hook scripts from %s' % (self._manifest_url,),
620 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
621
622 def _CheckForHookApprovalHash(self):
623 """Check whether the user has approved the hooks repo.
624
625 Returns:
626 True if this hook is approved to run; False otherwise.
627 """
628 prompt = ('Repo %s run the script:\n'
629 ' %s\n'
630 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700631 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400632 return self._CheckForHookApprovalHelper(
633 'approvedhash',
634 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700635 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400636 'Scripts have changed since %s was allowed.' % (self._hook_type,))
637
Mike Frysingerf7c51602019-06-18 17:23:39 -0400638 @staticmethod
639 def _ExtractInterpFromShebang(data):
640 """Extract the interpreter used in the shebang.
641
642 Try to locate the interpreter the script is using (ignoring `env`).
643
644 Args:
645 data: The file content of the script.
646
647 Returns:
648 The basename of the main script interpreter, or None if a shebang is not
649 used or could not be parsed out.
650 """
651 firstline = data.splitlines()[:1]
652 if not firstline:
653 return None
654
655 # The format here can be tricky.
656 shebang = firstline[0].strip()
657 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
658 if not m:
659 return None
660
661 # If the using `env`, find the target program.
662 interp = m.group(1)
663 if os.path.basename(interp) == 'env':
664 interp = m.group(2)
665
666 return interp
667
668 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
669 """Execute the hook script through |interp|.
670
671 Note: Support for this feature should be dropped ~Jun 2021.
672
673 Args:
674 interp: The Python program to run.
675 context: Basic Python context to execute the hook inside.
676 kwargs: Arbitrary arguments to pass to the hook script.
677
678 Raises:
679 HookError: When the hooks failed for any reason.
680 """
681 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
682 script = """
683import json, os, sys
684path = '''%(path)s'''
685kwargs = json.loads('''%(kwargs)s''')
686context = json.loads('''%(context)s''')
687sys.path.insert(0, os.path.dirname(path))
688data = open(path).read()
689exec(compile(data, path, 'exec'), context)
690context['main'](**kwargs)
691""" % {
692 'path': self._script_fullpath,
693 'kwargs': json.dumps(kwargs),
694 'context': json.dumps(context),
695 }
696
697 # We pass the script via stdin to avoid OS argv limits. It also makes
698 # unhandled exception tracebacks less verbose/confusing for users.
699 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
700 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
701 proc.communicate(input=script.encode('utf-8'))
702 if proc.returncode:
703 raise HookError('Failed to run %s hook.' % (self._hook_type,))
704
705 def _ExecuteHookViaImport(self, data, context, **kwargs):
706 """Execute the hook code in |data| directly.
707
708 Args:
709 data: The code of the hook to execute.
710 context: Basic Python context to execute the hook inside.
711 kwargs: Arbitrary arguments to pass to the hook script.
712
713 Raises:
714 HookError: When the hooks failed for any reason.
715 """
716 # Exec, storing global context in the context dict. We catch exceptions
717 # and convert to a HookError w/ just the failing traceback.
718 try:
719 exec(compile(data, self._script_fullpath, 'exec'), context)
720 except Exception:
721 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
722 (traceback.format_exc(), self._hook_type))
723
724 # Running the script should have defined a main() function.
725 if 'main' not in context:
726 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
727
728 # Call the main function in the hook. If the hook should cause the
729 # build to fail, it will raise an Exception. We'll catch that convert
730 # to a HookError w/ just the failing traceback.
731 try:
732 context['main'](**kwargs)
733 except Exception:
734 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
735 'above.' % (traceback.format_exc(), self._hook_type))
736
Doug Anderson37282b42011-03-04 11:54:18 -0800737 def _ExecuteHook(self, **kwargs):
738 """Actually execute the given hook.
739
740 This will run the hook's 'main' function in our python interpreter.
741
742 Args:
743 kwargs: Keyword arguments to pass to the hook. These are often specific
744 to the hook type. For instance, pre-upload hooks will contain
745 a project_list.
746 """
747 # Keep sys.path and CWD stashed away so that we can always restore them
748 # upon function exit.
749 orig_path = os.getcwd()
750 orig_syspath = sys.path
751
752 try:
753 # Always run hooks with CWD as topdir.
754 os.chdir(self._topdir)
755
756 # Put the hook dir as the first item of sys.path so hooks can do
757 # relative imports. We want to replace the repo dir as [0] so
758 # hooks can't import repo files.
759 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
760
Mike Frysingerf7c51602019-06-18 17:23:39 -0400761 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500762 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800763
Doug Anderson37282b42011-03-04 11:54:18 -0800764 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
765 # We don't actually want hooks to define their main with this argument--
766 # it's there to remind them that their hook should always take **kwargs.
767 # For instance, a pre-upload hook should be defined like:
768 # def main(project_list, **kwargs):
769 #
770 # This allows us to later expand the API without breaking old hooks.
771 kwargs = kwargs.copy()
772 kwargs['hook_should_take_kwargs'] = True
773
Mike Frysingerf7c51602019-06-18 17:23:39 -0400774 # See what version of python the hook has been written against.
775 data = open(self._script_fullpath).read()
776 interp = self._ExtractInterpFromShebang(data)
777 reexec = False
778 if interp:
779 prog = os.path.basename(interp)
780 if prog.startswith('python2') and sys.version_info.major != 2:
781 reexec = True
782 elif prog.startswith('python3') and sys.version_info.major == 2:
783 reexec = True
784
785 # Attempt to execute the hooks through the requested version of Python.
786 if reexec:
787 try:
788 self._ExecuteHookViaReexec(interp, context, **kwargs)
789 except OSError as e:
790 if e.errno == errno.ENOENT:
791 # We couldn't find the interpreter, so fallback to importing.
792 reexec = False
793 else:
794 raise
795
796 # Run the hook by importing directly.
797 if not reexec:
798 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800799 finally:
800 # Restore sys.path and CWD.
801 sys.path = orig_syspath
802 os.chdir(orig_path)
803
804 def Run(self, user_allows_all_hooks, **kwargs):
805 """Run the hook.
806
807 If the hook doesn't exist (because there is no hooks project or because
808 this particular hook is not enabled), this is a no-op.
809
810 Args:
811 user_allows_all_hooks: If True, we will never prompt about running the
812 hook--we'll just assume it's OK to run it.
813 kwargs: Keyword arguments to pass to the hook. These are often specific
814 to the hook type. For instance, pre-upload hooks will contain
815 a project_list.
816
817 Raises:
818 HookError: If there was a problem finding the hook or the user declined
819 to run a required hook (from _CheckForHookApproval).
820 """
821 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700822 if ((not self._hooks_project) or (self._hook_type not in
823 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800824 return
825
826 # Bail with a nice error if we can't find the hook.
827 if not os.path.isfile(self._script_fullpath):
828 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
829
830 # Make sure the user is OK with running the hook.
831 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
832 return
833
834 # Run the hook with the same version of python we're using.
835 self._ExecuteHook(**kwargs)
836
837
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600839 # These objects can be shared between several working trees.
840 shareable_files = ['description', 'info']
841 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
842 # These objects can only be used by a single working tree.
843 working_tree_files = ['config', 'packed-refs', 'shallow']
844 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700845
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846 def __init__(self,
847 manifest,
848 name,
849 remote,
850 gitdir,
David James8d201162013-10-11 17:03:19 -0700851 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852 worktree,
853 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700854 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800855 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100856 rebase=True,
857 groups=None,
858 sync_c=False,
859 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900860 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100861 clone_depth=None,
862 upstream=None,
863 parent=None,
864 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900865 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700866 optimized_fetch=False,
867 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800868 """Init a Project object.
869
870 Args:
871 manifest: The XmlManifest object.
872 name: The `name` attribute of manifest.xml's project element.
873 remote: RemoteSpec object specifying its remote's properties.
874 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700875 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800876 worktree: Absolute path of git working tree.
877 relpath: Relative path of git working tree to repo's top directory.
878 revisionExpr: The `revision` attribute of manifest.xml's project element.
879 revisionId: git commit id for checking out.
880 rebase: The `rebase` attribute of manifest.xml's project element.
881 groups: The `groups` attribute of manifest.xml's project element.
882 sync_c: The `sync-c` attribute of manifest.xml's project element.
883 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900884 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800885 upstream: The `upstream` attribute of manifest.xml's project element.
886 parent: The parent Project object.
887 is_derived: False if the project was explicitly defined in the manifest;
888 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400889 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900890 optimized_fetch: If True, when a project is set to a sha1 revision, only
891 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700892 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800893 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700894 self.manifest = manifest
895 self.name = name
896 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800897 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700898 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800899 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700900 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800901 else:
902 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700903 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700904 self.revisionExpr = revisionExpr
905
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700906 if revisionId is None \
907 and revisionExpr \
908 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700909 self.revisionId = revisionExpr
910 else:
911 self.revisionId = revisionId
912
Mike Pontillod3153822012-02-28 11:53:24 -0800913 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700914 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700915 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800916 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900917 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900918 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700919 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800920 self.parent = parent
921 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900922 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800923 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800924
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700925 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500927 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500928 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700929 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
930 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700931
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800932 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700933 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800934 else:
935 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700936 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700937 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700938 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400939 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700940 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941
Doug Anderson37282b42011-03-04 11:54:18 -0800942 # This will be filled in if a project is later identified to be the
943 # project containing repo hooks.
944 self.enabled_repo_hooks = []
945
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800947 def Derived(self):
948 return self.is_derived
949
950 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700951 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700952 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953
954 @property
955 def CurrentBranch(self):
956 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400957
958 The branch name omits the 'refs/heads/' prefix.
959 None is returned if the project is on a detached HEAD, or if the work_git is
960 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700961 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400962 try:
963 b = self.work_git.GetHead()
964 except NoManifestException:
965 # If the local checkout is in a bad state, don't barf. Let the callers
966 # process this like the head is unreadable.
967 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968 if b.startswith(R_HEADS):
969 return b[len(R_HEADS):]
970 return None
971
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700972 def IsRebaseInProgress(self):
973 w = self.worktree
974 g = os.path.join(w, '.git')
975 return os.path.exists(os.path.join(g, 'rebase-apply')) \
976 or os.path.exists(os.path.join(g, 'rebase-merge')) \
977 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200978
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979 def IsDirty(self, consider_untracked=True):
980 """Is the working directory modified in some way?
981 """
982 self.work_git.update_index('-q',
983 '--unmerged',
984 '--ignore-missing',
985 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900986 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700987 return True
988 if self.work_git.DiffZ('diff-files'):
989 return True
990 if consider_untracked and self.work_git.LsOthers():
991 return True
992 return False
993
994 _userident_name = None
995 _userident_email = None
996
997 @property
998 def UserName(self):
999 """Obtain the user's personal name.
1000 """
1001 if self._userident_name is None:
1002 self._LoadUserIdentity()
1003 return self._userident_name
1004
1005 @property
1006 def UserEmail(self):
1007 """Obtain the user's email address. This is very likely
1008 to be their Gerrit login.
1009 """
1010 if self._userident_email is None:
1011 self._LoadUserIdentity()
1012 return self._userident_email
1013
1014 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001015 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1016 m = re.compile("^(.*) <([^>]*)> ").match(u)
1017 if m:
1018 self._userident_name = m.group(1)
1019 self._userident_email = m.group(2)
1020 else:
1021 self._userident_name = ''
1022 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001023
1024 def GetRemote(self, name):
1025 """Get the configuration for a single remote.
1026 """
1027 return self.config.GetRemote(name)
1028
1029 def GetBranch(self, name):
1030 """Get the configuration for a single branch.
1031 """
1032 return self.config.GetBranch(name)
1033
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001034 def GetBranches(self):
1035 """Get all existing local branches.
1036 """
1037 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001038 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001039 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001040
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301041 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001042 if name.startswith(R_HEADS):
1043 name = name[len(R_HEADS):]
1044 b = self.GetBranch(name)
1045 b.current = name == current
1046 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001047 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001048 heads[name] = b
1049
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301050 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001051 if name.startswith(R_PUB):
1052 name = name[len(R_PUB):]
1053 b = heads.get(name)
1054 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001055 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001056
1057 return heads
1058
Colin Cross5acde752012-03-28 20:15:45 -07001059 def MatchesGroups(self, manifest_groups):
1060 """Returns true if the manifest groups specified at init should cause
1061 this project to be synced.
1062 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001063 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001064
1065 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001066 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001067 manifest_groups: "-group1,group2"
1068 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001069
1070 The special manifest group "default" will match any project that
1071 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001072 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001073 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001074 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001075 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001076 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001077
Conley Owens971de8e2012-04-16 10:36:08 -07001078 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001079 for group in expanded_manifest_groups:
1080 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001081 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001082 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001083 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001084
Conley Owens971de8e2012-04-16 10:36:08 -07001085 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001086
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001087# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001088 def UncommitedFiles(self, get_all=True):
1089 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001091 Args:
1092 get_all: a boolean, if True - get information about all different
1093 uncommitted files. If False - return as soon as any kind of
1094 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001095 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001096 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001097 self.work_git.update_index('-q',
1098 '--unmerged',
1099 '--ignore-missing',
1100 '--refresh')
1101 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001102 details.append("rebase in progress")
1103 if not get_all:
1104 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001105
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001106 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1107 if changes:
1108 details.extend(changes)
1109 if not get_all:
1110 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001111
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001112 changes = self.work_git.DiffZ('diff-files').keys()
1113 if changes:
1114 details.extend(changes)
1115 if not get_all:
1116 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001117
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001118 changes = self.work_git.LsOthers()
1119 if changes:
1120 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001121
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001122 return details
1123
1124 def HasChanges(self):
1125 """Returns true if there are uncommitted changes.
1126 """
1127 if self.UncommitedFiles(get_all=False):
1128 return True
1129 else:
1130 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001131
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001132 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001133 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001134
1135 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001136 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001137 quiet: If True then only print the project name. Do not print
1138 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001139 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001140 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001141 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001142 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001143 print(file=output_redir)
1144 print('project %s/' % self.relpath, file=output_redir)
1145 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 return
1147
1148 self.work_git.update_index('-q',
1149 '--unmerged',
1150 '--ignore-missing',
1151 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001152 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1154 df = self.work_git.DiffZ('diff-files')
1155 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001156 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001157 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158
1159 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001160 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001161 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001162 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001164 if quiet:
1165 out.nl()
1166 return 'DIRTY'
1167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001168 branch = self.CurrentBranch
1169 if branch is None:
1170 out.nobranch('(*** NO BRANCH ***)')
1171 else:
1172 out.branch('branch %s', branch)
1173 out.nl()
1174
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001175 if rb:
1176 out.important('prior sync failed; rebase still in progress')
1177 out.nl()
1178
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179 paths = list()
1180 paths.extend(di.keys())
1181 paths.extend(df.keys())
1182 paths.extend(do)
1183
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301184 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001185 try:
1186 i = di[p]
1187 except KeyError:
1188 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001189
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001190 try:
1191 f = df[p]
1192 except KeyError:
1193 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001194
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001195 if i:
1196 i_status = i.status.upper()
1197 else:
1198 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001200 if f:
1201 f_status = f.status.lower()
1202 else:
1203 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001204
1205 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001206 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001207 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208 else:
1209 line = ' %s%s\t%s' % (i_status, f_status, p)
1210
1211 if i and not f:
1212 out.added('%s', line)
1213 elif (i and f) or (not i and f):
1214 out.changed('%s', line)
1215 elif not i and not f:
1216 out.untracked('%s', line)
1217 else:
1218 out.write('%s', line)
1219 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001220
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001221 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001222
pelyad67872d2012-03-28 14:49:58 +03001223 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 """Prints the status of the repository to stdout.
1225 """
1226 out = DiffColoring(self.config)
1227 cmd = ['diff']
1228 if out.is_on:
1229 cmd.append('--color')
1230 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001231 if absolute_paths:
1232 cmd.append('--src-prefix=a/%s/' % self.relpath)
1233 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001234 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001235 try:
1236 p = GitCommand(self,
1237 cmd,
1238 capture_stdout=True,
1239 capture_stderr=True)
1240 except GitError as e:
1241 out.nl()
1242 out.project('project %s/' % self.relpath)
1243 out.nl()
1244 out.fail('%s', str(e))
1245 out.nl()
1246 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247 has_diff = False
1248 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001249 if not hasattr(line, 'encode'):
1250 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251 if not has_diff:
1252 out.nl()
1253 out.project('project %s/' % self.relpath)
1254 out.nl()
1255 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001256 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001257 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
1259
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001260# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001261
David Pursehouse8a68ff92012-09-24 12:15:13 +09001262 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263 """Was the branch published (uploaded) for code review?
1264 If so, returns the SHA-1 hash of the last published
1265 state for the branch.
1266 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001267 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001268 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001269 try:
1270 return self.bare_git.rev_parse(key)
1271 except GitError:
1272 return None
1273 else:
1274 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001275 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001276 except KeyError:
1277 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278
David Pursehouse8a68ff92012-09-24 12:15:13 +09001279 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 """Prunes any stale published refs.
1281 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001282 if all_refs is None:
1283 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284 heads = set()
1285 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301286 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287 if name.startswith(R_HEADS):
1288 heads.add(name)
1289 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301292 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 n = name[len(R_PUB):]
1294 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001295 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001297 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 """List any branches which can be uploaded for review.
1299 """
1300 heads = {}
1301 pubed = {}
1302
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301303 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001307 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
1309 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301310 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001311 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001313 if selected_branch and branch != selected_branch:
1314 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001316 rb = self.GetUploadableBranch(branch)
1317 if rb:
1318 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 return ready
1320
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001321 def GetUploadableBranch(self, branch_name):
1322 """Get a single uploadable branch, or None.
1323 """
1324 branch = self.GetBranch(branch_name)
1325 base = branch.LocalMerge
1326 if branch.LocalMerge:
1327 rb = ReviewableBranch(self, branch, base)
1328 if rb.commits:
1329 return rb
1330 return None
1331
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001332 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001333 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001334 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001335 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001336 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001337 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001338 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001339 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001340 validate_certs=True,
1341 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 """Uploads the named branch for code review.
1343 """
1344 if branch is None:
1345 branch = self.CurrentBranch
1346 if branch is None:
1347 raise GitError('not currently on a branch')
1348
1349 branch = self.GetBranch(branch)
1350 if not branch.LocalMerge:
1351 raise GitError('branch %s does not track a remote' % branch.name)
1352 if not branch.remote.review:
1353 raise GitError('remote %s has no review url' % branch.remote.name)
1354
Bryan Jacobsf609f912013-05-06 13:36:24 -04001355 if dest_branch is None:
1356 dest_branch = self.dest_branch
1357 if dest_branch is None:
1358 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 if not dest_branch.startswith(R_HEADS):
1360 dest_branch = R_HEADS + dest_branch
1361
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001362 if not branch.remote.projectname:
1363 branch.remote.projectname = self.name
1364 branch.remote.Save()
1365
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001366 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001367 if url is None:
1368 raise UploadError('review not configured')
1369 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001370
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001371 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001372 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001373
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001374 for push_option in (push_options or []):
1375 cmd.append('-o')
1376 cmd.append(push_option)
1377
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001378 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001379
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001380 if dest_branch.startswith(R_HEADS):
1381 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001382
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001383 upload_type = 'for'
1384 if draft:
1385 upload_type = 'drafts'
1386
1387 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1388 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001389 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001390 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001391 opts += ['topic=' + branch.name]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001392
David Pursehousef25a3702018-11-14 19:01:22 -08001393 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001394 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001395 if notify:
1396 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001397 if private:
1398 opts += ['private']
1399 if wip:
1400 opts += ['wip']
1401 if opts:
1402 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001403 cmd.append(ref_spec)
1404
Anthony King7bdac712014-07-16 12:56:40 +01001405 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001406 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
1408 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1409 self.bare_git.UpdateRef(R_PUB + branch.name,
1410 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001411 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412
1413
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001414# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001415
Julien Campergue335f5ef2013-10-16 11:02:35 +02001416 def _ExtractArchive(self, tarpath, path=None):
1417 """Extract the given tar on its current location
1418
1419 Args:
1420 - tarpath: The path to the actual tar file
1421
1422 """
1423 try:
1424 with tarfile.open(tarpath, 'r') as tar:
1425 tar.extractall(path=path)
1426 return True
1427 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001428 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001429 return False
1430
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001431 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001432 quiet=False,
1433 is_new=None,
1434 current_branch_only=False,
1435 force_sync=False,
1436 clone_bundle=True,
1437 no_tags=False,
1438 archive=False,
1439 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001440 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001441 submodules=False,
1442 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001443 """Perform only the network IO portion of the sync process.
1444 Local working directory/branch state is not affected.
1445 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001446 if archive and not isinstance(self, MetaProject):
1447 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001448 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001449 return False
1450
1451 name = self.relpath.replace('\\', '/')
1452 name = name.replace('/', '_')
1453 tarpath = '%s.tar' % name
1454 topdir = self.manifest.topdir
1455
1456 try:
1457 self._FetchArchive(tarpath, cwd=topdir)
1458 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001459 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001460 return False
1461
1462 # From now on, we only need absolute tarpath
1463 tarpath = os.path.join(topdir, tarpath)
1464
1465 if not self._ExtractArchive(tarpath, path=topdir):
1466 return False
1467 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001468 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001469 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001470 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001471 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001472 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001473 if is_new is None:
1474 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001475 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001476 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001477 else:
1478 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001479 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001480
1481 if is_new:
1482 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1483 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001484 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001485 # This works for both absolute and relative alternate directories.
1486 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001487 except IOError:
1488 alt_dir = None
1489 else:
1490 alt_dir = None
1491
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001492 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001493 and alt_dir is None \
1494 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001495 is_new = False
1496
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001497 if not current_branch_only:
1498 if self.sync_c:
1499 current_branch_only = True
1500 elif not self.manifest._loaded:
1501 # Manifest cannot check defaults until it syncs.
1502 current_branch_only = False
1503 elif self.manifest.default.sync_c:
1504 current_branch_only = True
1505
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001506 if not no_tags:
1507 if not self.sync_tags:
1508 no_tags = True
1509
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001510 if self.clone_depth:
1511 depth = self.clone_depth
1512 else:
1513 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1514
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001515 need_to_fetch = not (optimized_fetch and
1516 (ID_RE.match(self.revisionExpr) and
Zac Livingstone4332262017-06-16 08:56:09 -06001517 self._CheckForImmutableRevision()))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001518 if (need_to_fetch and
1519 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1520 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001521 no_tags=no_tags, prune=prune, depth=depth,
Xin Li745be2e2019-06-03 11:24:30 -07001522 submodules=submodules, force_sync=force_sync,
1523 clone_filter=clone_filter)):
Anthony King7bdac712014-07-16 12:56:40 +01001524 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001525
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001526 mp = self.manifest.manifestProject
1527 dissociate = mp.config.GetBoolean('repo.dissociate')
1528 if dissociate:
1529 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1530 if os.path.exists(alternates_file):
1531 cmd = ['repack', '-a', '-d']
1532 if GitCommand(self, cmd, bare=True).Wait() != 0:
1533 return False
1534 platform_utils.remove(alternates_file)
1535
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001536 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001537 self._InitMRef()
1538 else:
1539 self._InitMirrorHead()
1540 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001541 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001542 except OSError:
1543 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001544 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001545
1546 def PostRepoUpgrade(self):
1547 self._InitHooks()
1548
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001549 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001550 if self.manifest.isGitcClient:
1551 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001552 for copyfile in self.copyfiles:
1553 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001554 for linkfile in self.linkfiles:
1555 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556
Julien Camperguedd654222014-01-09 16:21:37 +01001557 def GetCommitRevisionId(self):
1558 """Get revisionId of a commit.
1559
1560 Use this method instead of GetRevisionId to get the id of the commit rather
1561 than the id of the current git object (for example, a tag)
1562
1563 """
1564 if not self.revisionExpr.startswith(R_TAGS):
1565 return self.GetRevisionId(self._allrefs)
1566
1567 try:
1568 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1569 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001570 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1571 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001572
David Pursehouse8a68ff92012-09-24 12:15:13 +09001573 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001574 if self.revisionId:
1575 return self.revisionId
1576
1577 rem = self.GetRemote(self.remote.name)
1578 rev = rem.ToLocal(self.revisionExpr)
1579
David Pursehouse8a68ff92012-09-24 12:15:13 +09001580 if all_refs is not None and rev in all_refs:
1581 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001582
1583 try:
1584 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1585 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001586 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1587 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001588
Martin Kellye4e94d22017-03-21 16:05:12 -07001589 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001590 """Perform only the local IO portion of the sync process.
1591 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001592 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001593 if not os.path.exists(self.gitdir):
1594 syncbuf.fail(self,
1595 'Cannot checkout %s due to missing network sync; Run '
1596 '`repo sync -n %s` first.' %
1597 (self.name, self.name))
1598 return
1599
Martin Kellye4e94d22017-03-21 16:05:12 -07001600 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001601 all_refs = self.bare_ref.all
1602 self.CleanPublishedCache(all_refs)
1603 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001604
David Pursehouse1d947b32012-10-25 12:23:11 +09001605 def _doff():
1606 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001607 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001608
Martin Kellye4e94d22017-03-21 16:05:12 -07001609 def _dosubmodules():
1610 self._SyncSubmodules(quiet=True)
1611
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001612 head = self.work_git.GetHead()
1613 if head.startswith(R_HEADS):
1614 branch = head[len(R_HEADS):]
1615 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001616 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001617 except KeyError:
1618 head = None
1619 else:
1620 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001621
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001622 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001623 # Currently on a detached HEAD. The user is assumed to
1624 # not have any local modifications worth worrying about.
1625 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001626 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001627 syncbuf.fail(self, _PriorSyncFailedError())
1628 return
1629
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001630 if head == revid:
1631 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001632 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001633 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001634 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001635 # The copy/linkfile config may have changed.
1636 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001637 return
1638 else:
1639 lost = self._revlist(not_rev(revid), HEAD)
1640 if lost:
1641 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001642
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001644 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001645 if submodules:
1646 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001647 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001648 syncbuf.fail(self, e)
1649 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001650 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001651 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001653 if head == revid:
1654 # No changes; don't do anything further.
1655 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001656 # The copy/linkfile config may have changed.
1657 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001658 return
1659
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001661
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001662 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001663 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001664 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001665 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001666 syncbuf.info(self,
1667 "leaving %s; does not track upstream",
1668 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001669 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001670 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001671 if submodules:
1672 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001673 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001674 syncbuf.fail(self, e)
1675 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001676 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001677 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001678
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001679 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001680
1681 # See if we can perform a fast forward merge. This can happen if our
1682 # branch isn't in the exact same state as we last published.
1683 try:
1684 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1685 # Skip the published logic.
1686 pub = False
1687 except GitError:
1688 pub = self.WasPublished(branch.name, all_refs)
1689
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001690 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001691 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001692 if not_merged:
1693 if upstream_gain:
1694 # The user has published this branch and some of those
1695 # commits are not yet merged upstream. We do not want
1696 # to rewrite the published commits so we punt.
1697 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001698 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001699 "branch %s is published (but not merged) and is now "
1700 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001701 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001702 elif pub == head:
1703 # All published commits are merged, and thus we are a
1704 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001705 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001706 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001707 if submodules:
1708 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001709 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001710
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001711 # Examine the local commits not in the remote. Find the
1712 # last one attributed to this user, if any.
1713 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001714 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001715 last_mine = None
1716 cnt_mine = 0
1717 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001718 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001719 if committer_email == self.UserEmail:
1720 last_mine = commit_id
1721 cnt_mine += 1
1722
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001723 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001724 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725
1726 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001727 syncbuf.fail(self, _DirtyError())
1728 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001729
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001730 # If the upstream switched on us, warn the user.
1731 #
1732 if branch.merge != self.revisionExpr:
1733 if branch.merge and self.revisionExpr:
1734 syncbuf.info(self,
1735 'manifest switched %s...%s',
1736 branch.merge,
1737 self.revisionExpr)
1738 elif branch.merge:
1739 syncbuf.info(self,
1740 'manifest no longer tracks %s',
1741 branch.merge)
1742
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001743 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001744 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001745 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001746 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001747 syncbuf.info(self,
1748 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001749 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001750
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001751 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001752 if not ID_RE.match(self.revisionExpr):
1753 # in case of manifest sync the revisionExpr might be a SHA1
1754 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001755 if not branch.merge.startswith('refs/'):
1756 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001757 branch.Save()
1758
Mike Pontillod3153822012-02-28 11:53:24 -08001759 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001760 def _docopyandlink():
1761 self._CopyAndLinkFiles()
1762
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001763 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001764 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001765 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001766 if submodules:
1767 syncbuf.later2(self, _dosubmodules)
1768 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001769 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001770 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001771 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001772 if submodules:
1773 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001774 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001775 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001776 syncbuf.fail(self, e)
1777 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001778 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001779 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001780 if submodules:
1781 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001782
Mike Frysingere6a202f2019-08-02 15:57:57 -04001783 def AddCopyFile(self, src, dest, topdir):
1784 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001785
Mike Frysingere6a202f2019-08-02 15:57:57 -04001786 No filesystem changes occur here. Actual copying happens later on.
1787
1788 Paths should have basic validation run on them before being queued.
1789 Further checking will be handled when the actual copy happens.
1790 """
1791 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1792
1793 def AddLinkFile(self, src, dest, topdir):
1794 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1795
1796 No filesystem changes occur here. Actual linking happens later on.
1797
1798 Paths should have basic validation run on them before being queued.
1799 Further checking will be handled when the actual link happens.
1800 """
1801 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001802
James W. Mills24c13082012-04-12 15:04:13 -05001803 def AddAnnotation(self, name, value, keep):
1804 self.annotations.append(_Annotation(name, value, keep))
1805
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001806 def DownloadPatchSet(self, change_id, patch_id):
1807 """Download a single patch set of a single change to FETCH_HEAD.
1808 """
1809 remote = self.GetRemote(self.remote.name)
1810
1811 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001812 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001813 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001814 if GitCommand(self, cmd, bare=True).Wait() != 0:
1815 return None
1816 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001817 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001818 change_id,
1819 patch_id,
1820 self.bare_git.rev_parse('FETCH_HEAD'))
1821
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001823# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001824
Mike Frysingerf914edc2020-02-09 03:01:56 -05001825 def GetHeadPath(self):
1826 """Return the full path to the HEAD ref."""
1827 dotgit = os.path.join(self.worktree, '.git')
1828 if os.path.isfile(dotgit):
1829 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1830 with open(dotgit) as fp:
1831 setting = fp.read()
1832 assert setting.startswith('gitdir:')
1833 gitdir = setting.split(':', 1)[1].strip()
1834 dotgit = os.path.join(self.worktree, gitdir)
1835 return os.path.join(dotgit, HEAD)
1836
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001837 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001838 """Create a new branch off the manifest's revision.
1839 """
Simran Basib9a1b732015-08-20 12:19:28 -07001840 if not branch_merge:
1841 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001842 head = self.work_git.GetHead()
1843 if head == (R_HEADS + name):
1844 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001845
David Pursehouse8a68ff92012-09-24 12:15:13 +09001846 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001847 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001848 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001849 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001850 capture_stdout=True,
1851 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001852
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001853 branch = self.GetBranch(name)
1854 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001855 branch.merge = branch_merge
1856 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1857 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001858
1859 if revision is None:
1860 revid = self.GetRevisionId(all_refs)
1861 else:
1862 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001863
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001864 if head.startswith(R_HEADS):
1865 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001866 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001867 except KeyError:
1868 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001869 if revid and head and revid == head:
1870 ref = os.path.join(self.gitdir, R_HEADS + name)
1871 try:
1872 os.makedirs(os.path.dirname(ref))
1873 except OSError:
1874 pass
1875 _lwrite(ref, '%s\n' % revid)
Mike Frysingerf914edc2020-02-09 03:01:56 -05001876 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001877 branch.Save()
1878 return True
1879
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001880 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001881 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001882 capture_stdout=True,
1883 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001884 branch.Save()
1885 return True
1886 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001887
Wink Saville02d79452009-04-10 13:01:24 -07001888 def CheckoutBranch(self, name):
1889 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001890
1891 Args:
1892 name: The name of the branch to checkout.
1893
1894 Returns:
1895 True if the checkout succeeded; False if it didn't; None if the branch
1896 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001897 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001898 rev = R_HEADS + name
1899 head = self.work_git.GetHead()
1900 if head == rev:
1901 # Already on the branch
1902 #
1903 return True
Wink Saville02d79452009-04-10 13:01:24 -07001904
David Pursehouse8a68ff92012-09-24 12:15:13 +09001905 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001906 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001907 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001908 except KeyError:
1909 # Branch does not exist in this project
1910 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001911 return None
Wink Saville02d79452009-04-10 13:01:24 -07001912
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001913 if head.startswith(R_HEADS):
1914 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001915 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001916 except KeyError:
1917 head = None
1918
1919 if head == revid:
1920 # Same revision; just update HEAD to point to the new
1921 # target branch, but otherwise take no other action.
1922 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05001923 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001924 return True
1925
1926 return GitCommand(self,
1927 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001928 capture_stdout=True,
1929 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001930
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001931 def AbandonBranch(self, name):
1932 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001933
1934 Args:
1935 name: The name of the branch to abandon.
1936
1937 Returns:
1938 True if the abandon succeeded; False if it didn't; None if the branch
1939 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001940 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001941 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001942 all_refs = self.bare_ref.all
1943 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001944 # Doesn't exist
1945 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001946
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001947 head = self.work_git.GetHead()
1948 if head == rev:
1949 # We can't destroy the branch while we are sitting
1950 # on it. Switch to a detached HEAD.
1951 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001952 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001953
David Pursehouse8a68ff92012-09-24 12:15:13 +09001954 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001955 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05001956 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001957 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001958 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001959
1960 return GitCommand(self,
1961 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001962 capture_stdout=True,
1963 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001964
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001965 def PruneHeads(self):
1966 """Prune any topic branches already merged into upstream.
1967 """
1968 cb = self.CurrentBranch
1969 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001970 left = self._allrefs
1971 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001972 if name.startswith(R_HEADS):
1973 name = name[len(R_HEADS):]
1974 if cb is None or name != cb:
1975 kill.append(name)
1976
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001977 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001978 if cb is not None \
1979 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001980 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001981 self.work_git.DetachHead(HEAD)
1982 kill.append(cb)
1983
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001984 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001985 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001986
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001987 try:
1988 self.bare_git.DetachHead(rev)
1989
1990 b = ['branch', '-d']
1991 b.extend(kill)
1992 b = GitCommand(self, b, bare=True,
1993 capture_stdout=True,
1994 capture_stderr=True)
1995 b.Wait()
1996 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001997 if ID_RE.match(old):
1998 self.bare_git.DetachHead(old)
1999 else:
2000 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002001 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002002
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002003 for branch in kill:
2004 if (R_HEADS + branch) not in left:
2005 self.CleanPublishedCache()
2006 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002007
2008 if cb and cb not in kill:
2009 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002010 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002011
2012 kept = []
2013 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002014 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002015 branch = self.GetBranch(branch)
2016 base = branch.LocalMerge
2017 if not base:
2018 base = rev
2019 kept.append(ReviewableBranch(self, branch, base))
2020 return kept
2021
2022
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002023# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002024
2025 def GetRegisteredSubprojects(self):
2026 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002027
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002028 def rec(subprojects):
2029 if not subprojects:
2030 return
2031 result.extend(subprojects)
2032 for p in subprojects:
2033 rec(p.subprojects)
2034 rec(self.subprojects)
2035 return result
2036
2037 def _GetSubmodules(self):
2038 # Unfortunately we cannot call `git submodule status --recursive` here
2039 # because the working tree might not exist yet, and it cannot be used
2040 # without a working tree in its current implementation.
2041
2042 def get_submodules(gitdir, rev):
2043 # Parse .gitmodules for submodule sub_paths and sub_urls
2044 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2045 if not sub_paths:
2046 return []
2047 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2048 # revision of submodule repository
2049 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2050 submodules = []
2051 for sub_path, sub_url in zip(sub_paths, sub_urls):
2052 try:
2053 sub_rev = sub_revs[sub_path]
2054 except KeyError:
2055 # Ignore non-exist submodules
2056 continue
2057 submodules.append((sub_rev, sub_path, sub_url))
2058 return submodules
2059
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002060 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2061 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002062
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002063 def parse_gitmodules(gitdir, rev):
2064 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2065 try:
Anthony King7bdac712014-07-16 12:56:40 +01002066 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2067 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002068 except GitError:
2069 return [], []
2070 if p.Wait() != 0:
2071 return [], []
2072
2073 gitmodules_lines = []
2074 fd, temp_gitmodules_path = tempfile.mkstemp()
2075 try:
2076 os.write(fd, p.stdout)
2077 os.close(fd)
2078 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002079 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2080 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002081 if p.Wait() != 0:
2082 return [], []
2083 gitmodules_lines = p.stdout.split('\n')
2084 except GitError:
2085 return [], []
2086 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002087 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002088
2089 names = set()
2090 paths = {}
2091 urls = {}
2092 for line in gitmodules_lines:
2093 if not line:
2094 continue
2095 m = re_path.match(line)
2096 if m:
2097 names.add(m.group(1))
2098 paths[m.group(1)] = m.group(2)
2099 continue
2100 m = re_url.match(line)
2101 if m:
2102 names.add(m.group(1))
2103 urls[m.group(1)] = m.group(2)
2104 continue
2105 names = sorted(names)
2106 return ([paths.get(name, '') for name in names],
2107 [urls.get(name, '') for name in names])
2108
2109 def git_ls_tree(gitdir, rev, paths):
2110 cmd = ['ls-tree', rev, '--']
2111 cmd.extend(paths)
2112 try:
Anthony King7bdac712014-07-16 12:56:40 +01002113 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2114 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002115 except GitError:
2116 return []
2117 if p.Wait() != 0:
2118 return []
2119 objects = {}
2120 for line in p.stdout.split('\n'):
2121 if not line.strip():
2122 continue
2123 object_rev, object_path = line.split()[2:4]
2124 objects[object_path] = object_rev
2125 return objects
2126
2127 try:
2128 rev = self.GetRevisionId()
2129 except GitError:
2130 return []
2131 return get_submodules(self.gitdir, rev)
2132
2133 def GetDerivedSubprojects(self):
2134 result = []
2135 if not self.Exists:
2136 # If git repo does not exist yet, querying its submodules will
2137 # mess up its states; so return here.
2138 return result
2139 for rev, path, url in self._GetSubmodules():
2140 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002141 relpath, worktree, gitdir, objdir = \
2142 self.manifest.GetSubprojectPaths(self, name, path)
2143 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002144 if project:
2145 result.extend(project.GetDerivedSubprojects())
2146 continue
David James8d201162013-10-11 17:03:19 -07002147
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002148 if url.startswith('..'):
2149 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002150 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002151 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002152 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002153 review=self.remote.review,
2154 revision=self.remote.revision)
2155 subproject = Project(manifest=self.manifest,
2156 name=name,
2157 remote=remote,
2158 gitdir=gitdir,
2159 objdir=objdir,
2160 worktree=worktree,
2161 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002162 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002163 revisionId=rev,
2164 rebase=self.rebase,
2165 groups=self.groups,
2166 sync_c=self.sync_c,
2167 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002168 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002169 parent=self,
2170 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002171 result.append(subproject)
2172 result.extend(subproject.GetDerivedSubprojects())
2173 return result
2174
2175
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002176# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002177 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002178 try:
2179 # if revision (sha or tag) is not present then following function
2180 # throws an error.
2181 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2182 return True
2183 except GitError:
2184 # There is no such persistent revision. We have to fetch it.
2185 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002186
Julien Campergue335f5ef2013-10-16 11:02:35 +02002187 def _FetchArchive(self, tarpath, cwd=None):
2188 cmd = ['archive', '-v', '-o', tarpath]
2189 cmd.append('--remote=%s' % self.remote.url)
2190 cmd.append('--prefix=%s/' % self.relpath)
2191 cmd.append(self.revisionExpr)
2192
2193 command = GitCommand(self, cmd, cwd=cwd,
2194 capture_stdout=True,
2195 capture_stderr=True)
2196
2197 if command.Wait() != 0:
2198 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2199
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002200 def _RemoteFetch(self, name=None,
2201 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002202 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002203 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002204 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002205 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002206 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002207 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002208 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002209 force_sync=False,
2210 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002211
2212 is_sha1 = False
2213 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002214 # The depth should not be used when fetching to a mirror because
2215 # it will result in a shallow repository that cannot be cloned or
2216 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002217 # The repo project should also never be synced with partial depth.
2218 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2219 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002220
Shawn Pearce69e04d82014-01-29 12:48:54 -08002221 if depth:
2222 current_branch_only = True
2223
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002224 if ID_RE.match(self.revisionExpr) is not None:
2225 is_sha1 = True
2226
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002227 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002228 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002229 # this is a tag and its sha1 value should never change
2230 tag_name = self.revisionExpr[len(R_TAGS):]
2231
2232 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002233 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002234 if not quiet:
2235 print('Skipped fetching project %s (already have persistent ref)'
2236 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002237 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002238 if is_sha1 and not depth:
2239 # When syncing a specific commit and --depth is not set:
2240 # * if upstream is explicitly specified and is not a sha1, fetch only
2241 # upstream as users expect only upstream to be fetch.
2242 # Note: The commit might not be in upstream in which case the sync
2243 # will fail.
2244 # * otherwise, fetch all branches to make sure we end up with the
2245 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002246 if self.upstream:
2247 current_branch_only = not ID_RE.match(self.upstream)
2248 else:
2249 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002250
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002251 if not name:
2252 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002253
2254 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002255 remote = self.GetRemote(name)
2256 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002257 ssh_proxy = True
2258
Shawn O. Pearce88443382010-10-08 10:02:09 +02002259 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002260 if alt_dir and 'objects' == os.path.basename(alt_dir):
2261 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002262 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2263 remote = self.GetRemote(name)
2264
David Pursehouse8a68ff92012-09-24 12:15:13 +09002265 all_refs = self.bare_ref.all
2266 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002267 tmp = set()
2268
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302269 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002270 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002271 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002272 all_refs[r] = ref_id
2273 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002274 continue
2275
David Pursehouse8a68ff92012-09-24 12:15:13 +09002276 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002277 continue
2278
David Pursehouse8a68ff92012-09-24 12:15:13 +09002279 r = 'refs/_alt/%s' % ref_id
2280 all_refs[r] = ref_id
2281 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002282 tmp.add(r)
2283
heping3d7bbc92017-04-12 19:51:47 +08002284 tmp_packed_lines = []
2285 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002286
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302287 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002288 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002289 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002290 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002291 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002292
heping3d7bbc92017-04-12 19:51:47 +08002293 tmp_packed = ''.join(tmp_packed_lines)
2294 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002295 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002296 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002297 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002298
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002299 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002300
Xin Li745be2e2019-06-03 11:24:30 -07002301 if clone_filter:
2302 git_require((2, 19, 0), fail=True, msg='partial clones')
2303 cmd.append('--filter=%s' % clone_filter)
2304 self.config.SetString('extensions.partialclone', self.remote.name)
2305
Conley Owensf97e8382015-01-21 11:12:46 -08002306 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002307 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002308 else:
2309 # If this repo has shallow objects, then we don't know which refs have
2310 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2311 # do this with projects that don't have shallow objects, since it is less
2312 # efficient.
2313 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2314 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002315
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002316 if quiet:
2317 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002318 if not self.worktree:
2319 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002320 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002321
Mike Frysingere57f1142019-03-18 21:27:54 -04002322 if force_sync:
2323 cmd.append('--force')
2324
David Pursehouse74cfd272015-10-14 10:50:15 +09002325 if prune:
2326 cmd.append('--prune')
2327
Martin Kellye4e94d22017-03-21 16:05:12 -07002328 if submodules:
2329 cmd.append('--recurse-submodules=on-demand')
2330
Kuang-che Wu6856f982019-11-25 12:37:55 +08002331 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002332 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002333 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002334 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002335 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002336 spec.append('tag')
2337 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002338
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302339 if self.manifest.IsMirror and not current_branch_only:
2340 branch = None
2341 else:
2342 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002343 if (not self.manifest.IsMirror and is_sha1 and depth
2344 and git_require((1, 8, 3))):
2345 # Shallow checkout of a specific commit, fetch from that commit and not
2346 # the heads only as the commit might be deeper in the history.
2347 spec.append(branch)
2348 else:
2349 if is_sha1:
2350 branch = self.upstream
2351 if branch is not None and branch.strip():
2352 if not branch.startswith('refs/'):
2353 branch = R_HEADS + branch
2354 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2355
2356 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2357 # whole repo.
2358 if self.manifest.IsMirror and not spec:
2359 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2360
2361 # If using depth then we should not get all the tags since they may
2362 # be outside of the depth.
2363 if no_tags or depth:
2364 cmd.append('--no-tags')
2365 else:
2366 cmd.append('--tags')
2367 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2368
Conley Owens80b87fe2014-05-09 17:13:44 -07002369 cmd.extend(spec)
2370
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002371 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002372 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002373 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002374 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002375 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002376 ok = True
2377 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002378 # If needed, run the 'git remote prune' the first time through the loop
2379 elif (not _i and
2380 "error:" in gitcmd.stderr and
2381 "git remote prune" in gitcmd.stderr):
2382 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002383 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002384 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002385 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002386 break
2387 continue
Brian Harring14a66742012-09-28 20:21:57 -07002388 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002389 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2390 # in sha1 mode, we just tried sync'ing from the upstream field; it
2391 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002392 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002393 elif ret < 0:
2394 # Git died with a signal, exit immediately
2395 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002396 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002397
2398 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002399 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002400 if old_packed != '':
2401 _lwrite(packed_refs, old_packed)
2402 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002403 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002404 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002405
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002406 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002407 # We just synced the upstream given branch; verify we
2408 # got what we wanted, else trigger a second run of all
2409 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002410 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002411 if current_branch_only and depth:
2412 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002413 return self._RemoteFetch(name=name,
2414 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002415 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002416 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002417 else:
2418 # Avoid infinite recursion: sync all branches with depth set to None
2419 return self._RemoteFetch(name=name, current_branch_only=False,
2420 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002421 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002422
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002423 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002424
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002425 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002426 if initial and \
2427 (self.manifest.manifestProject.config.GetString('repo.depth') or
2428 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002429 return False
2430
2431 remote = self.GetRemote(self.remote.name)
2432 bundle_url = remote.url + '/clone.bundle'
2433 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002434 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2435 'persistent-http',
2436 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002437 return False
2438
2439 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2440 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2441
2442 exist_dst = os.path.exists(bundle_dst)
2443 exist_tmp = os.path.exists(bundle_tmp)
2444
2445 if not initial and not exist_dst and not exist_tmp:
2446 return False
2447
2448 if not exist_dst:
2449 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2450 if not exist_dst:
2451 return False
2452
2453 cmd = ['fetch']
2454 if quiet:
2455 cmd.append('--quiet')
2456 if not self.worktree:
2457 cmd.append('--update-head-ok')
2458 cmd.append(bundle_dst)
2459 for f in remote.fetch:
2460 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002461 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002462
2463 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002464 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002465 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002466 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002467 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002468 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002469
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002470 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002471 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002472 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002473
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002474 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002475 if quiet:
2476 cmd += ['--silent']
2477 if os.path.exists(tmpPath):
2478 size = os.stat(tmpPath).st_size
2479 if size >= 1024:
2480 cmd += ['--continue-at', '%d' % (size,)]
2481 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002482 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002483 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002484 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002485 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002486 if proxy:
2487 cmd += ['--proxy', proxy]
2488 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2489 cmd += ['--proxy', os.environ['http_proxy']]
2490 if srcUrl.startswith('persistent-https'):
2491 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2492 elif srcUrl.startswith('persistent-http'):
2493 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002494 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002495
Dave Borowitz137d0132015-01-02 11:12:54 -08002496 if IsTrace():
2497 Trace('%s', ' '.join(cmd))
2498 try:
2499 proc = subprocess.Popen(cmd)
2500 except OSError:
2501 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002502
Dave Borowitz137d0132015-01-02 11:12:54 -08002503 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002504
Dave Borowitz137d0132015-01-02 11:12:54 -08002505 if curlret == 22:
2506 # From curl man page:
2507 # 22: HTTP page not retrieved. The requested url was not found or
2508 # returned another error with the HTTP error code being 400 or above.
2509 # This return code only appears if -f, --fail is used.
2510 if not quiet:
2511 print("Server does not provide clone.bundle; ignoring.",
2512 file=sys.stderr)
2513 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002514
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002515 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002516 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002517 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002518 return True
2519 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002520 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002521 return False
2522 else:
2523 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002524
Kris Giesingc8d882a2014-12-23 13:02:32 -08002525 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002526 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002527 with open(path, 'rb') as f:
2528 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002529 return True
2530 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002531 if not quiet:
2532 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002533 return False
2534 except OSError:
2535 return False
2536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537 def _Checkout(self, rev, quiet=False):
2538 cmd = ['checkout']
2539 if quiet:
2540 cmd.append('-q')
2541 cmd.append(rev)
2542 cmd.append('--')
2543 if GitCommand(self, cmd).Wait() != 0:
2544 if self._allrefs:
2545 raise GitError('%s checkout %s ' % (self.name, rev))
2546
Anthony King7bdac712014-07-16 12:56:40 +01002547 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002548 cmd = ['cherry-pick']
2549 cmd.append(rev)
2550 cmd.append('--')
2551 if GitCommand(self, cmd).Wait() != 0:
2552 if self._allrefs:
2553 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2554
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302555 def _LsRemote(self, refs):
2556 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302557 p = GitCommand(self, cmd, capture_stdout=True)
2558 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002559 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302560 return None
2561
Anthony King7bdac712014-07-16 12:56:40 +01002562 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002563 cmd = ['revert']
2564 cmd.append('--no-edit')
2565 cmd.append(rev)
2566 cmd.append('--')
2567 if GitCommand(self, cmd).Wait() != 0:
2568 if self._allrefs:
2569 raise GitError('%s revert %s ' % (self.name, rev))
2570
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002571 def _ResetHard(self, rev, quiet=True):
2572 cmd = ['reset', '--hard']
2573 if quiet:
2574 cmd.append('-q')
2575 cmd.append(rev)
2576 if GitCommand(self, cmd).Wait() != 0:
2577 raise GitError('%s reset --hard %s ' % (self.name, rev))
2578
Martin Kellye4e94d22017-03-21 16:05:12 -07002579 def _SyncSubmodules(self, quiet=True):
2580 cmd = ['submodule', 'update', '--init', '--recursive']
2581 if quiet:
2582 cmd.append('-q')
2583 if GitCommand(self, cmd).Wait() != 0:
2584 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2585
Anthony King7bdac712014-07-16 12:56:40 +01002586 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002587 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002588 if onto is not None:
2589 cmd.extend(['--onto', onto])
2590 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002591 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002592 raise GitError('%s rebase %s ' % (self.name, upstream))
2593
Pierre Tardy3d125942012-05-04 12:18:12 +02002594 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002595 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002596 if ffonly:
2597 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002598 if GitCommand(self, cmd).Wait() != 0:
2599 raise GitError('%s merge %s ' % (self.name, head))
2600
Kevin Degiabaa7f32014-11-12 11:27:45 -07002601 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002602 init_git_dir = not os.path.exists(self.gitdir)
2603 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002604 try:
2605 # Initialize the bare repository, which contains all of the objects.
2606 if init_obj_dir:
2607 os.makedirs(self.objdir)
2608 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002609
Kevin Degib1a07b82015-07-27 13:33:43 -06002610 # If we have a separate directory to hold refs, initialize it as well.
2611 if self.objdir != self.gitdir:
2612 if init_git_dir:
2613 os.makedirs(self.gitdir)
2614
2615 if init_obj_dir or init_git_dir:
2616 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2617 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002618 try:
2619 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2620 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002621 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002622 print("Retrying clone after deleting %s" %
2623 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002624 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002625 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2626 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002627 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002628 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002629 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2630 except:
2631 raise e
2632 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002633
Kevin Degi384b3c52014-10-16 16:02:58 -06002634 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002635 mp = self.manifest.manifestProject
2636 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002637
Kevin Degib1a07b82015-07-27 13:33:43 -06002638 if ref_dir or mirror_git:
2639 if not mirror_git:
2640 mirror_git = os.path.join(ref_dir, self.name + '.git')
2641 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2642 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002643
Kevin Degib1a07b82015-07-27 13:33:43 -06002644 if os.path.exists(mirror_git):
2645 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002646
Kevin Degib1a07b82015-07-27 13:33:43 -06002647 elif os.path.exists(repo_git):
2648 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002649
Kevin Degib1a07b82015-07-27 13:33:43 -06002650 else:
2651 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002652
Kevin Degib1a07b82015-07-27 13:33:43 -06002653 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002654 if not os.path.isabs(ref_dir):
2655 # The alternate directory is relative to the object database.
2656 ref_dir = os.path.relpath(ref_dir,
2657 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002658 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2659 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002660
Kevin Degib1a07b82015-07-27 13:33:43 -06002661 self._UpdateHooks()
2662
2663 m = self.manifest.manifestProject.config
2664 for key in ['user.name', 'user.email']:
2665 if m.Has(key, include_defaults=False):
2666 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002667 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002668 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002669 if self.manifest.IsMirror:
2670 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002671 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002672 self.config.SetString('core.bare', None)
2673 except Exception:
2674 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002675 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002676 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002677 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002678 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002679
Jimmie Westera0444582012-10-24 13:44:42 +02002680 def _UpdateHooks(self):
2681 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002682 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002683
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002684 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002685 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002686 if not os.path.exists(hooks):
2687 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002688 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002689 name = os.path.basename(stock_hook)
2690
Victor Boivie65e0f352011-04-18 11:23:29 +02002691 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002692 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002693 # Don't install a Gerrit Code Review hook if this
2694 # project does not appear to use it for reviews.
2695 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002696 # Since the manifest project is one of those, but also
2697 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002698 continue
2699
2700 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002701 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002702 continue
2703 if os.path.exists(dst):
2704 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002705 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002706 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002707 _warn("%s: Not replacing locally modified %s hook",
2708 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002709 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002710 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002711 platform_utils.symlink(
2712 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002713 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002714 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002715 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002716 else:
2717 raise
2718
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002719 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002720 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002721 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002722 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002723 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002724 remote.review = self.remote.review
2725 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002726
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002727 if self.worktree:
2728 remote.ResetFetch(mirror=False)
2729 else:
2730 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731 remote.Save()
2732
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002733 def _InitMRef(self):
2734 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002735 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002736
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002737 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002738 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002739
2740 def _InitAnyMRef(self, ref):
2741 cur = self.bare_ref.symref(ref)
2742
2743 if self.revisionId:
2744 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2745 msg = 'manifest set to %s' % self.revisionId
2746 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002747 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002748 else:
2749 remote = self.GetRemote(self.remote.name)
2750 dst = remote.ToLocal(self.revisionExpr)
2751 if cur != dst:
2752 msg = 'manifest set to %s' % self.revisionExpr
2753 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002754
Kevin Degi384b3c52014-10-16 16:02:58 -06002755 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002756 symlink_files = self.shareable_files[:]
2757 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002758 if share_refs:
2759 symlink_files += self.working_tree_files
2760 symlink_dirs += self.working_tree_dirs
2761 to_symlink = symlink_files + symlink_dirs
2762 for name in set(to_symlink):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002763 dst = platform_utils.realpath(os.path.join(destdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002764 if os.path.lexists(dst):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002765 src = platform_utils.realpath(os.path.join(srcdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002766 # Fail if the links are pointing to the wrong place
2767 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002768 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002769 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002770 'work tree. If you\'re comfortable with the '
2771 'possibility of losing the work tree\'s git metadata,'
2772 ' use `repo sync --force-sync {0}` to '
2773 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002774
David James8d201162013-10-11 17:03:19 -07002775 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2776 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2777
2778 Args:
2779 gitdir: The bare git repository. Must already be initialized.
2780 dotgit: The repository you would like to initialize.
2781 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2782 Only one work tree can store refs under a given |gitdir|.
2783 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2784 This saves you the effort of initializing |dotgit| yourself.
2785 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002786 symlink_files = self.shareable_files[:]
2787 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002788 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002789 symlink_files += self.working_tree_files
2790 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002791 to_symlink = symlink_files + symlink_dirs
2792
2793 to_copy = []
2794 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002795 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002796
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002797 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002798 for name in set(to_copy).union(to_symlink):
2799 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002800 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002801 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002802
Kevin Degi384b3c52014-10-16 16:02:58 -06002803 if os.path.lexists(dst):
2804 continue
David James8d201162013-10-11 17:03:19 -07002805
2806 # If the source dir doesn't exist, create an empty dir.
2807 if name in symlink_dirs and not os.path.lexists(src):
2808 os.makedirs(src)
2809
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002810 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002811 platform_utils.symlink(
2812 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002813 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002814 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002815 shutil.copytree(src, dst)
2816 elif os.path.isfile(src):
2817 shutil.copy(src, dst)
2818
Conley Owens80b87fe2014-05-09 17:13:44 -07002819 # If the source file doesn't exist, ensure the destination
2820 # file doesn't either.
2821 if name in symlink_files and not os.path.lexists(src):
2822 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002823 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002824 except OSError:
2825 pass
2826
David James8d201162013-10-11 17:03:19 -07002827 except OSError as e:
2828 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002829 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002830 else:
2831 raise
2832
Martin Kellye4e94d22017-03-21 16:05:12 -07002833 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002834 realdotgit = os.path.join(self.worktree, '.git')
2835 tmpdotgit = realdotgit + '.tmp'
2836 init_dotgit = not os.path.exists(realdotgit)
2837 if init_dotgit:
2838 dotgit = tmpdotgit
2839 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2840 os.makedirs(tmpdotgit)
2841 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2842 copy_all=False)
2843 else:
2844 dotgit = realdotgit
2845
Kevin Degib1a07b82015-07-27 13:33:43 -06002846 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002847 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2848 except GitError as e:
2849 if force_sync and not init_dotgit:
2850 try:
2851 platform_utils.rmtree(dotgit)
2852 return self._InitWorkTree(force_sync=False, submodules=submodules)
2853 except:
2854 raise e
2855 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002856
Mike Frysingerf4545122019-11-11 04:34:16 -05002857 if init_dotgit:
2858 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002859
Mike Frysingerf4545122019-11-11 04:34:16 -05002860 # Now that the .git dir is fully set up, move it to its final home.
2861 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002862
Mike Frysingerf4545122019-11-11 04:34:16 -05002863 # Finish checking out the worktree.
2864 cmd = ['read-tree', '--reset', '-u']
2865 cmd.append('-v')
2866 cmd.append(HEAD)
2867 if GitCommand(self, cmd).Wait() != 0:
2868 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002869
Mike Frysingerf4545122019-11-11 04:34:16 -05002870 if submodules:
2871 self._SyncSubmodules(quiet=True)
2872 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002873
Renaud Paquay788e9622017-01-27 11:41:12 -08002874 def _get_symlink_error_message(self):
2875 if platform_utils.isWindows():
2876 return ('Unable to create symbolic link. Please re-run the command as '
2877 'Administrator, or see '
2878 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2879 'for other options.')
2880 return 'filesystem must support symlinks'
2881
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002882 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002883 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002884
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002885 def _revlist(self, *args, **kw):
2886 a = []
2887 a.extend(args)
2888 a.append('--')
2889 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002890
2891 @property
2892 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002893 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002894
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002895 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002896 """Get logs between two revisions of this project."""
2897 comp = '..'
2898 if rev1:
2899 revs = [rev1]
2900 if rev2:
2901 revs.extend([comp, rev2])
2902 cmd = ['log', ''.join(revs)]
2903 out = DiffColoring(self.config)
2904 if out.is_on and color:
2905 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002906 if pretty_format is not None:
2907 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002908 if oneline:
2909 cmd.append('--oneline')
2910
2911 try:
2912 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2913 if log.Wait() == 0:
2914 return log.stdout
2915 except GitError:
2916 # worktree may not exist if groups changed for example. In that case,
2917 # try in gitdir instead.
2918 if not os.path.exists(self.worktree):
2919 return self.bare_git.log(*cmd[1:])
2920 else:
2921 raise
2922 return None
2923
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002924 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2925 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002926 """Get the list of logs from this revision to given revisionId"""
2927 logs = {}
2928 selfId = self.GetRevisionId(self._allrefs)
2929 toId = toProject.GetRevisionId(toProject._allrefs)
2930
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002931 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2932 pretty_format=pretty_format)
2933 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2934 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002935 return logs
2936
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002937 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002938
David James8d201162013-10-11 17:03:19 -07002939 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002940 self._project = project
2941 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002942 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002943
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002944 def LsOthers(self):
2945 p = GitCommand(self._project,
2946 ['ls-files',
2947 '-z',
2948 '--others',
2949 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002950 bare=False,
David James8d201162013-10-11 17:03:19 -07002951 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002952 capture_stdout=True,
2953 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954 if p.Wait() == 0:
2955 out = p.stdout
2956 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002957 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002958 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002959 return []
2960
2961 def DiffZ(self, name, *args):
2962 cmd = [name]
2963 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002964 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002965 cmd.extend(args)
2966 p = GitCommand(self._project,
2967 cmd,
David James8d201162013-10-11 17:03:19 -07002968 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002969 bare=False,
2970 capture_stdout=True,
2971 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002972 try:
2973 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002974 if not hasattr(out, 'encode'):
2975 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002976 r = {}
2977 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002978 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002979 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002980 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002981 info = next(out)
2982 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002983 except StopIteration:
2984 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002985
2986 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002987
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 def __init__(self, path, omode, nmode, oid, nid, state):
2989 self.path = path
2990 self.src_path = None
2991 self.old_mode = omode
2992 self.new_mode = nmode
2993 self.old_id = oid
2994 self.new_id = nid
2995
2996 if len(state) == 1:
2997 self.status = state
2998 self.level = None
2999 else:
3000 self.status = state[:1]
3001 self.level = state[1:]
3002 while self.level.startswith('0'):
3003 self.level = self.level[1:]
3004
3005 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003006 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003007 if info.status in ('R', 'C'):
3008 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003009 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003010 r[info.path] = info
3011 return r
3012 finally:
3013 p.Wait()
3014
3015 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003016 if self._bare:
3017 path = os.path.join(self._project.gitdir, HEAD)
3018 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003019 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003020 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003021 with open(path) as fd:
3022 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003023 except IOError as e:
3024 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003025 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303026 line = line.decode()
3027 except AttributeError:
3028 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003029 if line.startswith('ref: '):
3030 return line[5:-1]
3031 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003032
3033 def SetHead(self, ref, message=None):
3034 cmdv = []
3035 if message is not None:
3036 cmdv.extend(['-m', message])
3037 cmdv.append(HEAD)
3038 cmdv.append(ref)
3039 self.symbolic_ref(*cmdv)
3040
3041 def DetachHead(self, new, message=None):
3042 cmdv = ['--no-deref']
3043 if message is not None:
3044 cmdv.extend(['-m', message])
3045 cmdv.append(HEAD)
3046 cmdv.append(new)
3047 self.update_ref(*cmdv)
3048
3049 def UpdateRef(self, name, new, old=None,
3050 message=None,
3051 detach=False):
3052 cmdv = []
3053 if message is not None:
3054 cmdv.extend(['-m', message])
3055 if detach:
3056 cmdv.append('--no-deref')
3057 cmdv.append(name)
3058 cmdv.append(new)
3059 if old is not None:
3060 cmdv.append(old)
3061 self.update_ref(*cmdv)
3062
3063 def DeleteRef(self, name, old=None):
3064 if not old:
3065 old = self.rev_parse(name)
3066 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003067 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003068
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003069 def rev_list(self, *args, **kw):
3070 if 'format' in kw:
3071 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3072 else:
3073 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003074 cmdv.extend(args)
3075 p = GitCommand(self._project,
3076 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003077 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003078 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003079 capture_stdout=True,
3080 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003081 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003082 raise GitError('%s rev-list %s: %s' %
3083 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003084 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003085
3086 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003087 """Allow arbitrary git commands using pythonic syntax.
3088
3089 This allows you to do things like:
3090 git_obj.rev_parse('HEAD')
3091
3092 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3093 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003094 Any other positional arguments will be passed to the git command, and the
3095 following keyword arguments are supported:
3096 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003097
3098 Args:
3099 name: The name of the git command to call. Any '_' characters will
3100 be replaced with '-'.
3101
3102 Returns:
3103 A callable object that will try to call git with the named command.
3104 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003105 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003106
Dave Borowitz091f8932012-10-23 17:01:04 -07003107 def runner(*args, **kwargs):
3108 cmdv = []
3109 config = kwargs.pop('config', None)
3110 for k in kwargs:
3111 raise TypeError('%s() got an unexpected keyword argument %r'
3112 % (name, k))
3113 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07003114 if not git_require((1, 7, 2)):
3115 raise ValueError('cannot set config on command line for %s()'
3116 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303117 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003118 cmdv.append('-c')
3119 cmdv.append('%s=%s' % (k, v))
3120 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003121 cmdv.extend(args)
3122 p = GitCommand(self._project,
3123 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003124 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003125 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003126 capture_stdout=True,
3127 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003128 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003129 raise GitError('%s %s: %s' %
3130 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003131 r = p.stdout
3132 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3133 return r[:-1]
3134 return r
3135 return runner
3136
3137
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003138class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003139
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003140 def __str__(self):
3141 return 'prior sync failed; rebase still in progress'
3142
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003143
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003144class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003145
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003146 def __str__(self):
3147 return 'contains uncommitted changes'
3148
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003149
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003150class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003151
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003152 def __init__(self, project, text):
3153 self.project = project
3154 self.text = text
3155
3156 def Print(self, syncbuf):
3157 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3158 syncbuf.out.nl()
3159
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003160
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003161class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003162
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003163 def __init__(self, project, why):
3164 self.project = project
3165 self.why = why
3166
3167 def Print(self, syncbuf):
3168 syncbuf.out.fail('error: %s/: %s',
3169 self.project.relpath,
3170 str(self.why))
3171 syncbuf.out.nl()
3172
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003173
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003174class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003176 def __init__(self, project, action):
3177 self.project = project
3178 self.action = action
3179
3180 def Run(self, syncbuf):
3181 out = syncbuf.out
3182 out.project('project %s/', self.project.relpath)
3183 out.nl()
3184 try:
3185 self.action()
3186 out.nl()
3187 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003188 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003189 out.nl()
3190 return False
3191
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003192
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003193class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003194
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003195 def __init__(self, config):
3196 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003197 self.project = self.printer('header', attr='bold')
3198 self.info = self.printer('info')
3199 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003200
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003201
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003202class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003203
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003204 def __init__(self, config, detach_head=False):
3205 self._messages = []
3206 self._failures = []
3207 self._later_queue1 = []
3208 self._later_queue2 = []
3209
3210 self.out = _SyncColoring(config)
3211 self.out.redirect(sys.stderr)
3212
3213 self.detach_head = detach_head
3214 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003215 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003216
3217 def info(self, project, fmt, *args):
3218 self._messages.append(_InfoMessage(project, fmt % args))
3219
3220 def fail(self, project, err=None):
3221 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003222 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003223
3224 def later1(self, project, what):
3225 self._later_queue1.append(_Later(project, what))
3226
3227 def later2(self, project, what):
3228 self._later_queue2.append(_Later(project, what))
3229
3230 def Finish(self):
3231 self._PrintMessages()
3232 self._RunLater()
3233 self._PrintMessages()
3234 return self.clean
3235
David Rileye0684ad2017-04-05 00:02:59 -07003236 def Recently(self):
3237 recent_clean = self.recent_clean
3238 self.recent_clean = True
3239 return recent_clean
3240
3241 def _MarkUnclean(self):
3242 self.clean = False
3243 self.recent_clean = False
3244
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003245 def _RunLater(self):
3246 for q in ['_later_queue1', '_later_queue2']:
3247 if not self._RunQueue(q):
3248 return
3249
3250 def _RunQueue(self, queue):
3251 for m in getattr(self, queue):
3252 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003253 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003254 return False
3255 setattr(self, queue, [])
3256 return True
3257
3258 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003259 if self._messages or self._failures:
3260 if os.isatty(2):
3261 self.out.write(progress.CSI_ERASE_LINE)
3262 self.out.write('\r')
3263
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003264 for m in self._messages:
3265 m.Print(self)
3266 for m in self._failures:
3267 m.Print(self)
3268
3269 self._messages = []
3270 self._failures = []
3271
3272
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003273class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003274
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003275 """A special project housed under .repo.
3276 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003277
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003278 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003279 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003280 manifest=manifest,
3281 name=name,
3282 gitdir=gitdir,
3283 objdir=gitdir,
3284 worktree=worktree,
3285 remote=RemoteSpec('origin'),
3286 relpath='.repo/%s' % name,
3287 revisionExpr='refs/heads/master',
3288 revisionId=None,
3289 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003290
3291 def PreSync(self):
3292 if self.Exists:
3293 cb = self.CurrentBranch
3294 if cb:
3295 base = self.GetBranch(cb).merge
3296 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003297 self.revisionExpr = base
3298 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003299
Martin Kelly224a31a2017-07-10 14:46:25 -07003300 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003301 """ Prepare MetaProject for manifest branch switch
3302 """
3303
3304 # detach and delete manifest branch, allowing a new
3305 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003306 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003307 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003308 syncbuf.Finish()
3309
3310 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003311 ['update-ref', '-d', 'refs/heads/default'],
3312 capture_stdout=True,
3313 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003314
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003315 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003316 def LastFetch(self):
3317 try:
3318 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3319 return os.path.getmtime(fh)
3320 except OSError:
3321 return 0
3322
3323 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003324 def HasChanges(self):
3325 """Has the remote received new commits not yet checked out?
3326 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003327 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003328 return False
3329
David Pursehouse8a68ff92012-09-24 12:15:13 +09003330 all_refs = self.bare_ref.all
3331 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003332 head = self.work_git.GetHead()
3333 if head.startswith(R_HEADS):
3334 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003335 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003336 except KeyError:
3337 head = None
3338
3339 if revid == head:
3340 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003341 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003342 return True
3343 return False