blob: 185507c0196c56559e625badf0b12e966c6eadad [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 Frysingere6a202f2019-08-02 15:57:57 -0400385 src = _SafeExpandPath(self.git_worktree, self.src)
386
387 if os.path.exists(src):
388 # Entity exists so just a simple one to one link operation.
389 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
390 # dest & src are absolute paths at this point. Make sure the target of
391 # the symlink is relative in the context of the repo client checkout.
392 relpath = os.path.relpath(src, os.path.dirname(dest))
393 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700394 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400395 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700396 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400397 if os.path.exists(dest) and not platform_utils.isdir(dest):
398 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700399 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400400 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700401 # Create a releative path from source dir to destination dir
402 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400403 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700404
405 # Get the source file name
406 srcFile = os.path.basename(absSrcFile)
407
408 # Now form the final full paths to srcFile. They will be
409 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700411 relSrc = os.path.join(relSrcDir, srcFile)
412 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500413
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700414
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700415class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700416
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700417 def __init__(self,
418 name,
Anthony King7bdac712014-07-16 12:56:40 +0100419 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700420 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100421 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700422 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700423 orig_name=None,
424 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700425 self.name = name
426 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700427 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700428 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100429 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700430 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700431 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700432
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700433
Doug Anderson37282b42011-03-04 11:54:18 -0800434class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700435
Doug Anderson37282b42011-03-04 11:54:18 -0800436 """A RepoHook contains information about a script to run as a hook.
437
438 Hooks are used to run a python script before running an upload (for instance,
439 to run presubmit checks). Eventually, we may have hooks for other actions.
440
441 This shouldn't be confused with files in the 'repo/hooks' directory. Those
442 files are copied into each '.git/hooks' folder for each project. Repo-level
443 hooks are associated instead with repo actions.
444
445 Hooks are always python. When a hook is run, we will load the hook into the
446 interpreter and execute its main() function.
447 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700448
Doug Anderson37282b42011-03-04 11:54:18 -0800449 def __init__(self,
450 hook_type,
451 hooks_project,
452 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400453 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800454 abort_if_user_denies=False):
455 """RepoHook constructor.
456
457 Params:
458 hook_type: A string representing the type of hook. This is also used
459 to figure out the name of the file containing the hook. For
460 example: 'pre-upload'.
461 hooks_project: The project containing the repo hooks. If you have a
462 manifest, this is manifest.repo_hooks_project. OK if this is None,
463 which will make the hook a no-op.
464 topdir: Repo's top directory (the one containing the .repo directory).
465 Scripts will run with CWD as this directory. If you have a manifest,
466 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400467 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800468 abort_if_user_denies: If True, we'll throw a HookError() if the user
469 doesn't allow us to run the hook.
470 """
471 self._hook_type = hook_type
472 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400473 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800474 self._topdir = topdir
475 self._abort_if_user_denies = abort_if_user_denies
476
477 # Store the full path to the script for convenience.
478 if self._hooks_project:
479 self._script_fullpath = os.path.join(self._hooks_project.worktree,
480 self._hook_type + '.py')
481 else:
482 self._script_fullpath = None
483
484 def _GetHash(self):
485 """Return a hash of the contents of the hooks directory.
486
487 We'll just use git to do this. This hash has the property that if anything
488 changes in the directory we will return a different has.
489
490 SECURITY CONSIDERATION:
491 This hash only represents the contents of files in the hook directory, not
492 any other files imported or called by hooks. Changes to imported files
493 can change the script behavior without affecting the hash.
494
495 Returns:
496 A string representing the hash. This will always be ASCII so that it can
497 be printed to the user easily.
498 """
499 assert self._hooks_project, "Must have hooks to calculate their hash."
500
501 # We will use the work_git object rather than just calling GetRevisionId().
502 # That gives us a hash of the latest checked in version of the files that
503 # the user will actually be executing. Specifically, GetRevisionId()
504 # doesn't appear to change even if a user checks out a different version
505 # of the hooks repo (via git checkout) nor if a user commits their own revs.
506 #
507 # NOTE: Local (non-committed) changes will not be factored into this hash.
508 # I think this is OK, since we're really only worried about warning the user
509 # about upstream changes.
510 return self._hooks_project.work_git.rev_parse('HEAD')
511
512 def _GetMustVerb(self):
513 """Return 'must' if the hook is required; 'should' if not."""
514 if self._abort_if_user_denies:
515 return 'must'
516 else:
517 return 'should'
518
519 def _CheckForHookApproval(self):
520 """Check to see whether this hook has been approved.
521
Mike Frysinger40252c22016-08-15 21:23:44 -0400522 We'll accept approval of manifest URLs if they're using secure transports.
523 This way the user can say they trust the manifest hoster. For insecure
524 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800525
526 Note that we ask permission for each individual hook even though we use
527 the hash of all hooks when detecting changes. We'd like the user to be
528 able to approve / deny each hook individually. We only use the hash of all
529 hooks because there is no other easy way to detect changes to local imports.
530
531 Returns:
532 True if this hook is approved to run; False otherwise.
533
534 Raises:
535 HookError: Raised if the user doesn't approve and abort_if_user_denies
536 was passed to the consturctor.
537 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400538 if self._ManifestUrlHasSecureScheme():
539 return self._CheckForHookApprovalManifest()
540 else:
541 return self._CheckForHookApprovalHash()
542
543 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
544 changed_prompt):
545 """Check for approval for a particular attribute and hook.
546
547 Args:
548 subkey: The git config key under [repo.hooks.<hook_type>] to store the
549 last approved string.
550 new_val: The new value to compare against the last approved one.
551 main_prompt: Message to display to the user to ask for approval.
552 changed_prompt: Message explaining why we're re-asking for approval.
553
554 Returns:
555 True if this hook is approved to run; False otherwise.
556
557 Raises:
558 HookError: Raised if the user doesn't approve and abort_if_user_denies
559 was passed to the consturctor.
560 """
Doug Anderson37282b42011-03-04 11:54:18 -0800561 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400562 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800563
Mike Frysinger40252c22016-08-15 21:23:44 -0400564 # Get the last value that the user approved for this hook; may be None.
565 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800566
Mike Frysinger40252c22016-08-15 21:23:44 -0400567 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800568 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400569 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800570 # Approval matched. We're done.
571 return True
572 else:
573 # Give the user a reason why we're prompting, since they last told
574 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400575 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800576 else:
577 prompt = ''
578
579 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
580 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400581 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530582 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900583 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800584
585 # User is doing a one-time approval.
586 if response in ('y', 'yes'):
587 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400588 elif response == 'always':
589 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800590 return True
591
592 # For anything else, we'll assume no approval.
593 if self._abort_if_user_denies:
594 raise HookError('You must allow the %s hook or use --no-verify.' %
595 self._hook_type)
596
597 return False
598
Mike Frysinger40252c22016-08-15 21:23:44 -0400599 def _ManifestUrlHasSecureScheme(self):
600 """Check if the URI for the manifest is a secure transport."""
601 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
602 parse_results = urllib.parse.urlparse(self._manifest_url)
603 return parse_results.scheme in secure_schemes
604
605 def _CheckForHookApprovalManifest(self):
606 """Check whether the user has approved this manifest host.
607
608 Returns:
609 True if this hook is approved to run; False otherwise.
610 """
611 return self._CheckForHookApprovalHelper(
612 'approvedmanifest',
613 self._manifest_url,
614 'Run hook scripts from %s' % (self._manifest_url,),
615 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
616
617 def _CheckForHookApprovalHash(self):
618 """Check whether the user has approved the hooks repo.
619
620 Returns:
621 True if this hook is approved to run; False otherwise.
622 """
623 prompt = ('Repo %s run the script:\n'
624 ' %s\n'
625 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700626 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400627 return self._CheckForHookApprovalHelper(
628 'approvedhash',
629 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700630 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400631 'Scripts have changed since %s was allowed.' % (self._hook_type,))
632
Mike Frysingerf7c51602019-06-18 17:23:39 -0400633 @staticmethod
634 def _ExtractInterpFromShebang(data):
635 """Extract the interpreter used in the shebang.
636
637 Try to locate the interpreter the script is using (ignoring `env`).
638
639 Args:
640 data: The file content of the script.
641
642 Returns:
643 The basename of the main script interpreter, or None if a shebang is not
644 used or could not be parsed out.
645 """
646 firstline = data.splitlines()[:1]
647 if not firstline:
648 return None
649
650 # The format here can be tricky.
651 shebang = firstline[0].strip()
652 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
653 if not m:
654 return None
655
656 # If the using `env`, find the target program.
657 interp = m.group(1)
658 if os.path.basename(interp) == 'env':
659 interp = m.group(2)
660
661 return interp
662
663 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
664 """Execute the hook script through |interp|.
665
666 Note: Support for this feature should be dropped ~Jun 2021.
667
668 Args:
669 interp: The Python program to run.
670 context: Basic Python context to execute the hook inside.
671 kwargs: Arbitrary arguments to pass to the hook script.
672
673 Raises:
674 HookError: When the hooks failed for any reason.
675 """
676 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
677 script = """
678import json, os, sys
679path = '''%(path)s'''
680kwargs = json.loads('''%(kwargs)s''')
681context = json.loads('''%(context)s''')
682sys.path.insert(0, os.path.dirname(path))
683data = open(path).read()
684exec(compile(data, path, 'exec'), context)
685context['main'](**kwargs)
686""" % {
687 'path': self._script_fullpath,
688 'kwargs': json.dumps(kwargs),
689 'context': json.dumps(context),
690 }
691
692 # We pass the script via stdin to avoid OS argv limits. It also makes
693 # unhandled exception tracebacks less verbose/confusing for users.
694 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
695 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
696 proc.communicate(input=script.encode('utf-8'))
697 if proc.returncode:
698 raise HookError('Failed to run %s hook.' % (self._hook_type,))
699
700 def _ExecuteHookViaImport(self, data, context, **kwargs):
701 """Execute the hook code in |data| directly.
702
703 Args:
704 data: The code of the hook to execute.
705 context: Basic Python context to execute the hook inside.
706 kwargs: Arbitrary arguments to pass to the hook script.
707
708 Raises:
709 HookError: When the hooks failed for any reason.
710 """
711 # Exec, storing global context in the context dict. We catch exceptions
712 # and convert to a HookError w/ just the failing traceback.
713 try:
714 exec(compile(data, self._script_fullpath, 'exec'), context)
715 except Exception:
716 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
717 (traceback.format_exc(), self._hook_type))
718
719 # Running the script should have defined a main() function.
720 if 'main' not in context:
721 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
722
723 # Call the main function in the hook. If the hook should cause the
724 # build to fail, it will raise an Exception. We'll catch that convert
725 # to a HookError w/ just the failing traceback.
726 try:
727 context['main'](**kwargs)
728 except Exception:
729 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
730 'above.' % (traceback.format_exc(), self._hook_type))
731
Doug Anderson37282b42011-03-04 11:54:18 -0800732 def _ExecuteHook(self, **kwargs):
733 """Actually execute the given hook.
734
735 This will run the hook's 'main' function in our python interpreter.
736
737 Args:
738 kwargs: Keyword arguments to pass to the hook. These are often specific
739 to the hook type. For instance, pre-upload hooks will contain
740 a project_list.
741 """
742 # Keep sys.path and CWD stashed away so that we can always restore them
743 # upon function exit.
744 orig_path = os.getcwd()
745 orig_syspath = sys.path
746
747 try:
748 # Always run hooks with CWD as topdir.
749 os.chdir(self._topdir)
750
751 # Put the hook dir as the first item of sys.path so hooks can do
752 # relative imports. We want to replace the repo dir as [0] so
753 # hooks can't import repo files.
754 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
755
Mike Frysingerf7c51602019-06-18 17:23:39 -0400756 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500757 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800758
Doug Anderson37282b42011-03-04 11:54:18 -0800759 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
760 # We don't actually want hooks to define their main with this argument--
761 # it's there to remind them that their hook should always take **kwargs.
762 # For instance, a pre-upload hook should be defined like:
763 # def main(project_list, **kwargs):
764 #
765 # This allows us to later expand the API without breaking old hooks.
766 kwargs = kwargs.copy()
767 kwargs['hook_should_take_kwargs'] = True
768
Mike Frysingerf7c51602019-06-18 17:23:39 -0400769 # See what version of python the hook has been written against.
770 data = open(self._script_fullpath).read()
771 interp = self._ExtractInterpFromShebang(data)
772 reexec = False
773 if interp:
774 prog = os.path.basename(interp)
775 if prog.startswith('python2') and sys.version_info.major != 2:
776 reexec = True
777 elif prog.startswith('python3') and sys.version_info.major == 2:
778 reexec = True
779
780 # Attempt to execute the hooks through the requested version of Python.
781 if reexec:
782 try:
783 self._ExecuteHookViaReexec(interp, context, **kwargs)
784 except OSError as e:
785 if e.errno == errno.ENOENT:
786 # We couldn't find the interpreter, so fallback to importing.
787 reexec = False
788 else:
789 raise
790
791 # Run the hook by importing directly.
792 if not reexec:
793 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800794 finally:
795 # Restore sys.path and CWD.
796 sys.path = orig_syspath
797 os.chdir(orig_path)
798
799 def Run(self, user_allows_all_hooks, **kwargs):
800 """Run the hook.
801
802 If the hook doesn't exist (because there is no hooks project or because
803 this particular hook is not enabled), this is a no-op.
804
805 Args:
806 user_allows_all_hooks: If True, we will never prompt about running the
807 hook--we'll just assume it's OK to run it.
808 kwargs: Keyword arguments to pass to the hook. These are often specific
809 to the hook type. For instance, pre-upload hooks will contain
810 a project_list.
811
812 Raises:
813 HookError: If there was a problem finding the hook or the user declined
814 to run a required hook (from _CheckForHookApproval).
815 """
816 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700817 if ((not self._hooks_project) or (self._hook_type not in
818 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800819 return
820
821 # Bail with a nice error if we can't find the hook.
822 if not os.path.isfile(self._script_fullpath):
823 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
824
825 # Make sure the user is OK with running the hook.
826 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
827 return
828
829 # Run the hook with the same version of python we're using.
830 self._ExecuteHook(**kwargs)
831
832
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700833class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600834 # These objects can be shared between several working trees.
835 shareable_files = ['description', 'info']
836 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
837 # These objects can only be used by a single working tree.
838 working_tree_files = ['config', 'packed-refs', 'shallow']
839 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700840
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841 def __init__(self,
842 manifest,
843 name,
844 remote,
845 gitdir,
David James8d201162013-10-11 17:03:19 -0700846 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700847 worktree,
848 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700849 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800850 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100851 rebase=True,
852 groups=None,
853 sync_c=False,
854 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900855 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100856 clone_depth=None,
857 upstream=None,
858 parent=None,
859 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900860 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700861 optimized_fetch=False,
862 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800863 """Init a Project object.
864
865 Args:
866 manifest: The XmlManifest object.
867 name: The `name` attribute of manifest.xml's project element.
868 remote: RemoteSpec object specifying its remote's properties.
869 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700870 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800871 worktree: Absolute path of git working tree.
872 relpath: Relative path of git working tree to repo's top directory.
873 revisionExpr: The `revision` attribute of manifest.xml's project element.
874 revisionId: git commit id for checking out.
875 rebase: The `rebase` attribute of manifest.xml's project element.
876 groups: The `groups` attribute of manifest.xml's project element.
877 sync_c: The `sync-c` attribute of manifest.xml's project element.
878 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900879 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800880 upstream: The `upstream` attribute of manifest.xml's project element.
881 parent: The parent Project object.
882 is_derived: False if the project was explicitly defined in the manifest;
883 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400884 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900885 optimized_fetch: If True, when a project is set to a sha1 revision, only
886 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700887 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800888 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700889 self.manifest = manifest
890 self.name = name
891 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800892 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700893 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800894 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700895 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800896 else:
897 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700899 self.revisionExpr = revisionExpr
900
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700901 if revisionId is None \
902 and revisionExpr \
903 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700904 self.revisionId = revisionExpr
905 else:
906 self.revisionId = revisionId
907
Mike Pontillod3153822012-02-28 11:53:24 -0800908 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700909 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700910 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800911 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900912 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900913 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700914 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800915 self.parent = parent
916 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900917 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800918 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800919
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700920 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700921 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500922 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500923 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700924 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
925 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800927 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700928 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800929 else:
930 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700931 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700932 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700933 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400934 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700935 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936
Doug Anderson37282b42011-03-04 11:54:18 -0800937 # This will be filled in if a project is later identified to be the
938 # project containing repo hooks.
939 self.enabled_repo_hooks = []
940
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700941 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800942 def Derived(self):
943 return self.is_derived
944
945 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700946 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700947 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948
949 @property
950 def CurrentBranch(self):
951 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400952
953 The branch name omits the 'refs/heads/' prefix.
954 None is returned if the project is on a detached HEAD, or if the work_git is
955 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700956 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400957 try:
958 b = self.work_git.GetHead()
959 except NoManifestException:
960 # If the local checkout is in a bad state, don't barf. Let the callers
961 # process this like the head is unreadable.
962 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 if b.startswith(R_HEADS):
964 return b[len(R_HEADS):]
965 return None
966
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700967 def IsRebaseInProgress(self):
968 w = self.worktree
969 g = os.path.join(w, '.git')
970 return os.path.exists(os.path.join(g, 'rebase-apply')) \
971 or os.path.exists(os.path.join(g, 'rebase-merge')) \
972 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200973
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700974 def IsDirty(self, consider_untracked=True):
975 """Is the working directory modified in some way?
976 """
977 self.work_git.update_index('-q',
978 '--unmerged',
979 '--ignore-missing',
980 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900981 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700982 return True
983 if self.work_git.DiffZ('diff-files'):
984 return True
985 if consider_untracked and self.work_git.LsOthers():
986 return True
987 return False
988
989 _userident_name = None
990 _userident_email = None
991
992 @property
993 def UserName(self):
994 """Obtain the user's personal name.
995 """
996 if self._userident_name is None:
997 self._LoadUserIdentity()
998 return self._userident_name
999
1000 @property
1001 def UserEmail(self):
1002 """Obtain the user's email address. This is very likely
1003 to be their Gerrit login.
1004 """
1005 if self._userident_email is None:
1006 self._LoadUserIdentity()
1007 return self._userident_email
1008
1009 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001010 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1011 m = re.compile("^(.*) <([^>]*)> ").match(u)
1012 if m:
1013 self._userident_name = m.group(1)
1014 self._userident_email = m.group(2)
1015 else:
1016 self._userident_name = ''
1017 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001018
1019 def GetRemote(self, name):
1020 """Get the configuration for a single remote.
1021 """
1022 return self.config.GetRemote(name)
1023
1024 def GetBranch(self, name):
1025 """Get the configuration for a single branch.
1026 """
1027 return self.config.GetBranch(name)
1028
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001029 def GetBranches(self):
1030 """Get all existing local branches.
1031 """
1032 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001033 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001034 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001035
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301036 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001037 if name.startswith(R_HEADS):
1038 name = name[len(R_HEADS):]
1039 b = self.GetBranch(name)
1040 b.current = name == current
1041 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001042 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001043 heads[name] = b
1044
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301045 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001046 if name.startswith(R_PUB):
1047 name = name[len(R_PUB):]
1048 b = heads.get(name)
1049 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001050 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001051
1052 return heads
1053
Colin Cross5acde752012-03-28 20:15:45 -07001054 def MatchesGroups(self, manifest_groups):
1055 """Returns true if the manifest groups specified at init should cause
1056 this project to be synced.
1057 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001058 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001059
1060 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001061 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001062 manifest_groups: "-group1,group2"
1063 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001064
1065 The special manifest group "default" will match any project that
1066 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001067 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001068 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001069 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001070 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001071 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001072
Conley Owens971de8e2012-04-16 10:36:08 -07001073 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001074 for group in expanded_manifest_groups:
1075 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001076 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001077 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001078 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001079
Conley Owens971de8e2012-04-16 10:36:08 -07001080 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001082# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001083 def UncommitedFiles(self, get_all=True):
1084 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001086 Args:
1087 get_all: a boolean, if True - get information about all different
1088 uncommitted files. If False - return as soon as any kind of
1089 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001090 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001091 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001092 self.work_git.update_index('-q',
1093 '--unmerged',
1094 '--ignore-missing',
1095 '--refresh')
1096 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001097 details.append("rebase in progress")
1098 if not get_all:
1099 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001100
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001101 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1102 if changes:
1103 details.extend(changes)
1104 if not get_all:
1105 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001106
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001107 changes = self.work_git.DiffZ('diff-files').keys()
1108 if changes:
1109 details.extend(changes)
1110 if not get_all:
1111 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001112
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001113 changes = self.work_git.LsOthers()
1114 if changes:
1115 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001116
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001117 return details
1118
1119 def HasChanges(self):
1120 """Returns true if there are uncommitted changes.
1121 """
1122 if self.UncommitedFiles(get_all=False):
1123 return True
1124 else:
1125 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001126
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001127 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001128 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001129
1130 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001131 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001132 quiet: If True then only print the project name. Do not print
1133 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001135 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001136 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001137 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001138 print(file=output_redir)
1139 print('project %s/' % self.relpath, file=output_redir)
1140 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 return
1142
1143 self.work_git.update_index('-q',
1144 '--unmerged',
1145 '--ignore-missing',
1146 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001147 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001148 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1149 df = self.work_git.DiffZ('diff-files')
1150 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001151 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001152 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001153
1154 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001155 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001156 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001157 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001159 if quiet:
1160 out.nl()
1161 return 'DIRTY'
1162
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163 branch = self.CurrentBranch
1164 if branch is None:
1165 out.nobranch('(*** NO BRANCH ***)')
1166 else:
1167 out.branch('branch %s', branch)
1168 out.nl()
1169
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001170 if rb:
1171 out.important('prior sync failed; rebase still in progress')
1172 out.nl()
1173
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001174 paths = list()
1175 paths.extend(di.keys())
1176 paths.extend(df.keys())
1177 paths.extend(do)
1178
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301179 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001180 try:
1181 i = di[p]
1182 except KeyError:
1183 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001184
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001185 try:
1186 f = df[p]
1187 except KeyError:
1188 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001189
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001190 if i:
1191 i_status = i.status.upper()
1192 else:
1193 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001194
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001195 if f:
1196 f_status = f.status.lower()
1197 else:
1198 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199
1200 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001201 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001202 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203 else:
1204 line = ' %s%s\t%s' % (i_status, f_status, p)
1205
1206 if i and not f:
1207 out.added('%s', line)
1208 elif (i and f) or (not i and f):
1209 out.changed('%s', line)
1210 elif not i and not f:
1211 out.untracked('%s', line)
1212 else:
1213 out.write('%s', line)
1214 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001215
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001216 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001217
pelyad67872d2012-03-28 14:49:58 +03001218 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001219 """Prints the status of the repository to stdout.
1220 """
1221 out = DiffColoring(self.config)
1222 cmd = ['diff']
1223 if out.is_on:
1224 cmd.append('--color')
1225 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001226 if absolute_paths:
1227 cmd.append('--src-prefix=a/%s/' % self.relpath)
1228 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001230 try:
1231 p = GitCommand(self,
1232 cmd,
1233 capture_stdout=True,
1234 capture_stderr=True)
1235 except GitError as e:
1236 out.nl()
1237 out.project('project %s/' % self.relpath)
1238 out.nl()
1239 out.fail('%s', str(e))
1240 out.nl()
1241 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001242 has_diff = False
1243 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001244 if not hasattr(line, 'encode'):
1245 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001246 if not has_diff:
1247 out.nl()
1248 out.project('project %s/' % self.relpath)
1249 out.nl()
1250 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001251 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001252 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001253
1254
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001255# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001256
David Pursehouse8a68ff92012-09-24 12:15:13 +09001257 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258 """Was the branch published (uploaded) for code review?
1259 If so, returns the SHA-1 hash of the last published
1260 state for the branch.
1261 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001262 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001263 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001264 try:
1265 return self.bare_git.rev_parse(key)
1266 except GitError:
1267 return None
1268 else:
1269 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001270 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001271 except KeyError:
1272 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001273
David Pursehouse8a68ff92012-09-24 12:15:13 +09001274 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001275 """Prunes any stale published refs.
1276 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001277 if all_refs is None:
1278 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 heads = set()
1280 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301281 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282 if name.startswith(R_HEADS):
1283 heads.add(name)
1284 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001285 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001286
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301287 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001288 n = name[len(R_PUB):]
1289 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001292 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 """List any branches which can be uploaded for review.
1294 """
1295 heads = {}
1296 pubed = {}
1297
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301298 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001299 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001300 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001301 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001302 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001303
1304 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301305 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001306 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001308 if selected_branch and branch != selected_branch:
1309 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001311 rb = self.GetUploadableBranch(branch)
1312 if rb:
1313 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001314 return ready
1315
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001316 def GetUploadableBranch(self, branch_name):
1317 """Get a single uploadable branch, or None.
1318 """
1319 branch = self.GetBranch(branch_name)
1320 base = branch.LocalMerge
1321 if branch.LocalMerge:
1322 rb = ReviewableBranch(self, branch, base)
1323 if rb.commits:
1324 return rb
1325 return None
1326
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001327 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001328 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001329 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001330 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001331 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001332 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001333 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001334 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001335 validate_certs=True,
1336 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001337 """Uploads the named branch for code review.
1338 """
1339 if branch is None:
1340 branch = self.CurrentBranch
1341 if branch is None:
1342 raise GitError('not currently on a branch')
1343
1344 branch = self.GetBranch(branch)
1345 if not branch.LocalMerge:
1346 raise GitError('branch %s does not track a remote' % branch.name)
1347 if not branch.remote.review:
1348 raise GitError('remote %s has no review url' % branch.remote.name)
1349
Bryan Jacobsf609f912013-05-06 13:36:24 -04001350 if dest_branch is None:
1351 dest_branch = self.dest_branch
1352 if dest_branch is None:
1353 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354 if not dest_branch.startswith(R_HEADS):
1355 dest_branch = R_HEADS + dest_branch
1356
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001357 if not branch.remote.projectname:
1358 branch.remote.projectname = self.name
1359 branch.remote.Save()
1360
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001361 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001362 if url is None:
1363 raise UploadError('review not configured')
1364 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001365
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001366 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001367 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001368
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001369 for push_option in (push_options or []):
1370 cmd.append('-o')
1371 cmd.append(push_option)
1372
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001373 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001374
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001375 if dest_branch.startswith(R_HEADS):
1376 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001377
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001378 upload_type = 'for'
1379 if draft:
1380 upload_type = 'drafts'
1381
1382 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1383 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001384 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001385 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001386 opts += ['topic=' + branch.name]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001387
David Pursehousef25a3702018-11-14 19:01:22 -08001388 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001389 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001390 if notify:
1391 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001392 if private:
1393 opts += ['private']
1394 if wip:
1395 opts += ['wip']
1396 if opts:
1397 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001398 cmd.append(ref_spec)
1399
Anthony King7bdac712014-07-16 12:56:40 +01001400 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001401 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001402
1403 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1404 self.bare_git.UpdateRef(R_PUB + branch.name,
1405 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001406 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
1408
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001409# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001410
Julien Campergue335f5ef2013-10-16 11:02:35 +02001411 def _ExtractArchive(self, tarpath, path=None):
1412 """Extract the given tar on its current location
1413
1414 Args:
1415 - tarpath: The path to the actual tar file
1416
1417 """
1418 try:
1419 with tarfile.open(tarpath, 'r') as tar:
1420 tar.extractall(path=path)
1421 return True
1422 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001423 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001424 return False
1425
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001426 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001427 quiet=False,
1428 is_new=None,
1429 current_branch_only=False,
1430 force_sync=False,
1431 clone_bundle=True,
1432 no_tags=False,
1433 archive=False,
1434 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001435 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001436 submodules=False,
1437 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001438 """Perform only the network IO portion of the sync process.
1439 Local working directory/branch state is not affected.
1440 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001441 if archive and not isinstance(self, MetaProject):
1442 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001443 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001444 return False
1445
1446 name = self.relpath.replace('\\', '/')
1447 name = name.replace('/', '_')
1448 tarpath = '%s.tar' % name
1449 topdir = self.manifest.topdir
1450
1451 try:
1452 self._FetchArchive(tarpath, cwd=topdir)
1453 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001454 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001455 return False
1456
1457 # From now on, we only need absolute tarpath
1458 tarpath = os.path.join(topdir, tarpath)
1459
1460 if not self._ExtractArchive(tarpath, path=topdir):
1461 return False
1462 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001463 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001464 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001465 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001466 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001467 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001468 if is_new is None:
1469 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001470 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001471 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001472 else:
1473 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001474 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001475
1476 if is_new:
1477 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1478 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001479 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001480 # This works for both absolute and relative alternate directories.
1481 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001482 except IOError:
1483 alt_dir = None
1484 else:
1485 alt_dir = None
1486
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001487 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001488 and alt_dir is None \
1489 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001490 is_new = False
1491
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001492 if not current_branch_only:
1493 if self.sync_c:
1494 current_branch_only = True
1495 elif not self.manifest._loaded:
1496 # Manifest cannot check defaults until it syncs.
1497 current_branch_only = False
1498 elif self.manifest.default.sync_c:
1499 current_branch_only = True
1500
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001501 if not no_tags:
1502 if not self.sync_tags:
1503 no_tags = True
1504
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001505 if self.clone_depth:
1506 depth = self.clone_depth
1507 else:
1508 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1509
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001510 need_to_fetch = not (optimized_fetch and
1511 (ID_RE.match(self.revisionExpr) and
Zac Livingstone4332262017-06-16 08:56:09 -06001512 self._CheckForImmutableRevision()))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001513 if (need_to_fetch and
1514 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1515 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001516 no_tags=no_tags, prune=prune, depth=depth,
Xin Li745be2e2019-06-03 11:24:30 -07001517 submodules=submodules, force_sync=force_sync,
1518 clone_filter=clone_filter)):
Anthony King7bdac712014-07-16 12:56:40 +01001519 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001520
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001521 mp = self.manifest.manifestProject
1522 dissociate = mp.config.GetBoolean('repo.dissociate')
1523 if dissociate:
1524 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1525 if os.path.exists(alternates_file):
1526 cmd = ['repack', '-a', '-d']
1527 if GitCommand(self, cmd, bare=True).Wait() != 0:
1528 return False
1529 platform_utils.remove(alternates_file)
1530
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001531 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001532 self._InitMRef()
1533 else:
1534 self._InitMirrorHead()
1535 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001536 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001537 except OSError:
1538 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001539 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001540
1541 def PostRepoUpgrade(self):
1542 self._InitHooks()
1543
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001544 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001545 if self.manifest.isGitcClient:
1546 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001547 for copyfile in self.copyfiles:
1548 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001549 for linkfile in self.linkfiles:
1550 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551
Julien Camperguedd654222014-01-09 16:21:37 +01001552 def GetCommitRevisionId(self):
1553 """Get revisionId of a commit.
1554
1555 Use this method instead of GetRevisionId to get the id of the commit rather
1556 than the id of the current git object (for example, a tag)
1557
1558 """
1559 if not self.revisionExpr.startswith(R_TAGS):
1560 return self.GetRevisionId(self._allrefs)
1561
1562 try:
1563 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1564 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001565 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1566 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001567
David Pursehouse8a68ff92012-09-24 12:15:13 +09001568 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001569 if self.revisionId:
1570 return self.revisionId
1571
1572 rem = self.GetRemote(self.remote.name)
1573 rev = rem.ToLocal(self.revisionExpr)
1574
David Pursehouse8a68ff92012-09-24 12:15:13 +09001575 if all_refs is not None and rev in all_refs:
1576 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001577
1578 try:
1579 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1580 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001581 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1582 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001583
Martin Kellye4e94d22017-03-21 16:05:12 -07001584 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001585 """Perform only the local IO portion of the sync process.
1586 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001587 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001588 if not os.path.exists(self.gitdir):
1589 syncbuf.fail(self,
1590 'Cannot checkout %s due to missing network sync; Run '
1591 '`repo sync -n %s` first.' %
1592 (self.name, self.name))
1593 return
1594
Martin Kellye4e94d22017-03-21 16:05:12 -07001595 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001596 all_refs = self.bare_ref.all
1597 self.CleanPublishedCache(all_refs)
1598 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001599
David Pursehouse1d947b32012-10-25 12:23:11 +09001600 def _doff():
1601 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001602 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001603
Martin Kellye4e94d22017-03-21 16:05:12 -07001604 def _dosubmodules():
1605 self._SyncSubmodules(quiet=True)
1606
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001607 head = self.work_git.GetHead()
1608 if head.startswith(R_HEADS):
1609 branch = head[len(R_HEADS):]
1610 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001611 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001612 except KeyError:
1613 head = None
1614 else:
1615 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001616
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001617 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001618 # Currently on a detached HEAD. The user is assumed to
1619 # not have any local modifications worth worrying about.
1620 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001621 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001622 syncbuf.fail(self, _PriorSyncFailedError())
1623 return
1624
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001625 if head == revid:
1626 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001627 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001628 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001629 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001630 # The copy/linkfile config may have changed.
1631 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001632 return
1633 else:
1634 lost = self._revlist(not_rev(revid), HEAD)
1635 if lost:
1636 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001638 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001639 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001640 if submodules:
1641 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001642 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001643 syncbuf.fail(self, e)
1644 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001645 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001646 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001647
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001648 if head == revid:
1649 # No changes; don't do anything further.
1650 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001651 # The copy/linkfile config may have changed.
1652 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001653 return
1654
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001655 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001657 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001658 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001659 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001661 syncbuf.info(self,
1662 "leaving %s; does not track upstream",
1663 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001665 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001666 if submodules:
1667 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001668 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001669 syncbuf.fail(self, e)
1670 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001671 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001672 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001673
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001674 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001675
1676 # See if we can perform a fast forward merge. This can happen if our
1677 # branch isn't in the exact same state as we last published.
1678 try:
1679 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1680 # Skip the published logic.
1681 pub = False
1682 except GitError:
1683 pub = self.WasPublished(branch.name, all_refs)
1684
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001685 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001686 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687 if not_merged:
1688 if upstream_gain:
1689 # The user has published this branch and some of those
1690 # commits are not yet merged upstream. We do not want
1691 # to rewrite the published commits so we punt.
1692 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001693 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001694 "branch %s is published (but not merged) and is now "
1695 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001696 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001697 elif pub == head:
1698 # All published commits are merged, and thus we are a
1699 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001700 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001701 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001702 if submodules:
1703 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001704 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001705
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001706 # Examine the local commits not in the remote. Find the
1707 # last one attributed to this user, if any.
1708 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001709 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001710 last_mine = None
1711 cnt_mine = 0
1712 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001713 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001714 if committer_email == self.UserEmail:
1715 last_mine = commit_id
1716 cnt_mine += 1
1717
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001718 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001719 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001720
1721 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001722 syncbuf.fail(self, _DirtyError())
1723 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001724
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001725 # If the upstream switched on us, warn the user.
1726 #
1727 if branch.merge != self.revisionExpr:
1728 if branch.merge and self.revisionExpr:
1729 syncbuf.info(self,
1730 'manifest switched %s...%s',
1731 branch.merge,
1732 self.revisionExpr)
1733 elif branch.merge:
1734 syncbuf.info(self,
1735 'manifest no longer tracks %s',
1736 branch.merge)
1737
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001738 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001740 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001742 syncbuf.info(self,
1743 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001744 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001746 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001747 if not ID_RE.match(self.revisionExpr):
1748 # in case of manifest sync the revisionExpr might be a SHA1
1749 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001750 if not branch.merge.startswith('refs/'):
1751 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001752 branch.Save()
1753
Mike Pontillod3153822012-02-28 11:53:24 -08001754 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001755 def _docopyandlink():
1756 self._CopyAndLinkFiles()
1757
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001758 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001759 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001760 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001761 if submodules:
1762 syncbuf.later2(self, _dosubmodules)
1763 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001764 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001765 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001766 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001767 if submodules:
1768 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001769 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001770 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001771 syncbuf.fail(self, e)
1772 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001773 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001774 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001775 if submodules:
1776 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001777
Mike Frysingere6a202f2019-08-02 15:57:57 -04001778 def AddCopyFile(self, src, dest, topdir):
1779 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001780
Mike Frysingere6a202f2019-08-02 15:57:57 -04001781 No filesystem changes occur here. Actual copying happens later on.
1782
1783 Paths should have basic validation run on them before being queued.
1784 Further checking will be handled when the actual copy happens.
1785 """
1786 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1787
1788 def AddLinkFile(self, src, dest, topdir):
1789 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1790
1791 No filesystem changes occur here. Actual linking happens later on.
1792
1793 Paths should have basic validation run on them before being queued.
1794 Further checking will be handled when the actual link happens.
1795 """
1796 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001797
James W. Mills24c13082012-04-12 15:04:13 -05001798 def AddAnnotation(self, name, value, keep):
1799 self.annotations.append(_Annotation(name, value, keep))
1800
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001801 def DownloadPatchSet(self, change_id, patch_id):
1802 """Download a single patch set of a single change to FETCH_HEAD.
1803 """
1804 remote = self.GetRemote(self.remote.name)
1805
1806 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001807 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001808 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001809 if GitCommand(self, cmd, bare=True).Wait() != 0:
1810 return None
1811 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001812 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001813 change_id,
1814 patch_id,
1815 self.bare_git.rev_parse('FETCH_HEAD'))
1816
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001817
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001818# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001819
Mike Frysingerf914edc2020-02-09 03:01:56 -05001820 def GetHeadPath(self):
1821 """Return the full path to the HEAD ref."""
1822 dotgit = os.path.join(self.worktree, '.git')
1823 if os.path.isfile(dotgit):
1824 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1825 with open(dotgit) as fp:
1826 setting = fp.read()
1827 assert setting.startswith('gitdir:')
1828 gitdir = setting.split(':', 1)[1].strip()
1829 dotgit = os.path.join(self.worktree, gitdir)
1830 return os.path.join(dotgit, HEAD)
1831
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001832 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001833 """Create a new branch off the manifest's revision.
1834 """
Simran Basib9a1b732015-08-20 12:19:28 -07001835 if not branch_merge:
1836 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001837 head = self.work_git.GetHead()
1838 if head == (R_HEADS + name):
1839 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001840
David Pursehouse8a68ff92012-09-24 12:15:13 +09001841 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001842 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001843 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001844 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001845 capture_stdout=True,
1846 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001847
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001848 branch = self.GetBranch(name)
1849 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001850 branch.merge = branch_merge
1851 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1852 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001853
1854 if revision is None:
1855 revid = self.GetRevisionId(all_refs)
1856 else:
1857 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001858
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001859 if head.startswith(R_HEADS):
1860 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001861 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001862 except KeyError:
1863 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001864 if revid and head and revid == head:
1865 ref = os.path.join(self.gitdir, R_HEADS + name)
1866 try:
1867 os.makedirs(os.path.dirname(ref))
1868 except OSError:
1869 pass
1870 _lwrite(ref, '%s\n' % revid)
Mike Frysingerf914edc2020-02-09 03:01:56 -05001871 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001872 branch.Save()
1873 return True
1874
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001875 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001876 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001877 capture_stdout=True,
1878 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001879 branch.Save()
1880 return True
1881 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001882
Wink Saville02d79452009-04-10 13:01:24 -07001883 def CheckoutBranch(self, name):
1884 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001885
1886 Args:
1887 name: The name of the branch to checkout.
1888
1889 Returns:
1890 True if the checkout succeeded; False if it didn't; None if the branch
1891 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001892 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001893 rev = R_HEADS + name
1894 head = self.work_git.GetHead()
1895 if head == rev:
1896 # Already on the branch
1897 #
1898 return True
Wink Saville02d79452009-04-10 13:01:24 -07001899
David Pursehouse8a68ff92012-09-24 12:15:13 +09001900 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001901 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001902 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001903 except KeyError:
1904 # Branch does not exist in this project
1905 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001906 return None
Wink Saville02d79452009-04-10 13:01:24 -07001907
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001908 if head.startswith(R_HEADS):
1909 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001910 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001911 except KeyError:
1912 head = None
1913
1914 if head == revid:
1915 # Same revision; just update HEAD to point to the new
1916 # target branch, but otherwise take no other action.
1917 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05001918 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001919 return True
1920
1921 return GitCommand(self,
1922 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001923 capture_stdout=True,
1924 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001925
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001926 def AbandonBranch(self, name):
1927 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001928
1929 Args:
1930 name: The name of the branch to abandon.
1931
1932 Returns:
1933 True if the abandon succeeded; False if it didn't; None if the branch
1934 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001935 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001936 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001937 all_refs = self.bare_ref.all
1938 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001939 # Doesn't exist
1940 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001941
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001942 head = self.work_git.GetHead()
1943 if head == rev:
1944 # We can't destroy the branch while we are sitting
1945 # on it. Switch to a detached HEAD.
1946 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001947 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001948
David Pursehouse8a68ff92012-09-24 12:15:13 +09001949 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001950 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05001951 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001952 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001953 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001954
1955 return GitCommand(self,
1956 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001957 capture_stdout=True,
1958 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001959
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001960 def PruneHeads(self):
1961 """Prune any topic branches already merged into upstream.
1962 """
1963 cb = self.CurrentBranch
1964 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001965 left = self._allrefs
1966 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001967 if name.startswith(R_HEADS):
1968 name = name[len(R_HEADS):]
1969 if cb is None or name != cb:
1970 kill.append(name)
1971
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001972 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001973 if cb is not None \
1974 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001975 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001976 self.work_git.DetachHead(HEAD)
1977 kill.append(cb)
1978
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001979 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001980 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001981
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001982 try:
1983 self.bare_git.DetachHead(rev)
1984
1985 b = ['branch', '-d']
1986 b.extend(kill)
1987 b = GitCommand(self, b, bare=True,
1988 capture_stdout=True,
1989 capture_stderr=True)
1990 b.Wait()
1991 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001992 if ID_RE.match(old):
1993 self.bare_git.DetachHead(old)
1994 else:
1995 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001996 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001997
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001998 for branch in kill:
1999 if (R_HEADS + branch) not in left:
2000 self.CleanPublishedCache()
2001 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002002
2003 if cb and cb not in kill:
2004 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002005 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002006
2007 kept = []
2008 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002009 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002010 branch = self.GetBranch(branch)
2011 base = branch.LocalMerge
2012 if not base:
2013 base = rev
2014 kept.append(ReviewableBranch(self, branch, base))
2015 return kept
2016
2017
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002018# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002019
2020 def GetRegisteredSubprojects(self):
2021 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002022
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002023 def rec(subprojects):
2024 if not subprojects:
2025 return
2026 result.extend(subprojects)
2027 for p in subprojects:
2028 rec(p.subprojects)
2029 rec(self.subprojects)
2030 return result
2031
2032 def _GetSubmodules(self):
2033 # Unfortunately we cannot call `git submodule status --recursive` here
2034 # because the working tree might not exist yet, and it cannot be used
2035 # without a working tree in its current implementation.
2036
2037 def get_submodules(gitdir, rev):
2038 # Parse .gitmodules for submodule sub_paths and sub_urls
2039 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2040 if not sub_paths:
2041 return []
2042 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2043 # revision of submodule repository
2044 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2045 submodules = []
2046 for sub_path, sub_url in zip(sub_paths, sub_urls):
2047 try:
2048 sub_rev = sub_revs[sub_path]
2049 except KeyError:
2050 # Ignore non-exist submodules
2051 continue
2052 submodules.append((sub_rev, sub_path, sub_url))
2053 return submodules
2054
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002055 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2056 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002057
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002058 def parse_gitmodules(gitdir, rev):
2059 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2060 try:
Anthony King7bdac712014-07-16 12:56:40 +01002061 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2062 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002063 except GitError:
2064 return [], []
2065 if p.Wait() != 0:
2066 return [], []
2067
2068 gitmodules_lines = []
2069 fd, temp_gitmodules_path = tempfile.mkstemp()
2070 try:
2071 os.write(fd, p.stdout)
2072 os.close(fd)
2073 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002074 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2075 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002076 if p.Wait() != 0:
2077 return [], []
2078 gitmodules_lines = p.stdout.split('\n')
2079 except GitError:
2080 return [], []
2081 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002082 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002083
2084 names = set()
2085 paths = {}
2086 urls = {}
2087 for line in gitmodules_lines:
2088 if not line:
2089 continue
2090 m = re_path.match(line)
2091 if m:
2092 names.add(m.group(1))
2093 paths[m.group(1)] = m.group(2)
2094 continue
2095 m = re_url.match(line)
2096 if m:
2097 names.add(m.group(1))
2098 urls[m.group(1)] = m.group(2)
2099 continue
2100 names = sorted(names)
2101 return ([paths.get(name, '') for name in names],
2102 [urls.get(name, '') for name in names])
2103
2104 def git_ls_tree(gitdir, rev, paths):
2105 cmd = ['ls-tree', rev, '--']
2106 cmd.extend(paths)
2107 try:
Anthony King7bdac712014-07-16 12:56:40 +01002108 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2109 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002110 except GitError:
2111 return []
2112 if p.Wait() != 0:
2113 return []
2114 objects = {}
2115 for line in p.stdout.split('\n'):
2116 if not line.strip():
2117 continue
2118 object_rev, object_path = line.split()[2:4]
2119 objects[object_path] = object_rev
2120 return objects
2121
2122 try:
2123 rev = self.GetRevisionId()
2124 except GitError:
2125 return []
2126 return get_submodules(self.gitdir, rev)
2127
2128 def GetDerivedSubprojects(self):
2129 result = []
2130 if not self.Exists:
2131 # If git repo does not exist yet, querying its submodules will
2132 # mess up its states; so return here.
2133 return result
2134 for rev, path, url in self._GetSubmodules():
2135 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002136 relpath, worktree, gitdir, objdir = \
2137 self.manifest.GetSubprojectPaths(self, name, path)
2138 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002139 if project:
2140 result.extend(project.GetDerivedSubprojects())
2141 continue
David James8d201162013-10-11 17:03:19 -07002142
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002143 if url.startswith('..'):
2144 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002145 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002146 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002147 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002148 review=self.remote.review,
2149 revision=self.remote.revision)
2150 subproject = Project(manifest=self.manifest,
2151 name=name,
2152 remote=remote,
2153 gitdir=gitdir,
2154 objdir=objdir,
2155 worktree=worktree,
2156 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002157 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002158 revisionId=rev,
2159 rebase=self.rebase,
2160 groups=self.groups,
2161 sync_c=self.sync_c,
2162 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002163 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002164 parent=self,
2165 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002166 result.append(subproject)
2167 result.extend(subproject.GetDerivedSubprojects())
2168 return result
2169
2170
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002171# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002172 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002173 try:
2174 # if revision (sha or tag) is not present then following function
2175 # throws an error.
2176 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2177 return True
2178 except GitError:
2179 # There is no such persistent revision. We have to fetch it.
2180 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002181
Julien Campergue335f5ef2013-10-16 11:02:35 +02002182 def _FetchArchive(self, tarpath, cwd=None):
2183 cmd = ['archive', '-v', '-o', tarpath]
2184 cmd.append('--remote=%s' % self.remote.url)
2185 cmd.append('--prefix=%s/' % self.relpath)
2186 cmd.append(self.revisionExpr)
2187
2188 command = GitCommand(self, cmd, cwd=cwd,
2189 capture_stdout=True,
2190 capture_stderr=True)
2191
2192 if command.Wait() != 0:
2193 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2194
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002195 def _RemoteFetch(self, name=None,
2196 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002197 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002198 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002199 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002200 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002201 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002202 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002203 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002204 force_sync=False,
2205 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002206
2207 is_sha1 = False
2208 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002209 # The depth should not be used when fetching to a mirror because
2210 # it will result in a shallow repository that cannot be cloned or
2211 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002212 # The repo project should also never be synced with partial depth.
2213 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2214 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002215
Shawn Pearce69e04d82014-01-29 12:48:54 -08002216 if depth:
2217 current_branch_only = True
2218
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002219 if ID_RE.match(self.revisionExpr) is not None:
2220 is_sha1 = True
2221
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002222 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002223 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002224 # this is a tag and its sha1 value should never change
2225 tag_name = self.revisionExpr[len(R_TAGS):]
2226
2227 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002228 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002229 if not quiet:
2230 print('Skipped fetching project %s (already have persistent ref)'
2231 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002232 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002233 if is_sha1 and not depth:
2234 # When syncing a specific commit and --depth is not set:
2235 # * if upstream is explicitly specified and is not a sha1, fetch only
2236 # upstream as users expect only upstream to be fetch.
2237 # Note: The commit might not be in upstream in which case the sync
2238 # will fail.
2239 # * otherwise, fetch all branches to make sure we end up with the
2240 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002241 if self.upstream:
2242 current_branch_only = not ID_RE.match(self.upstream)
2243 else:
2244 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002245
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002246 if not name:
2247 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002248
2249 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002250 remote = self.GetRemote(name)
2251 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002252 ssh_proxy = True
2253
Shawn O. Pearce88443382010-10-08 10:02:09 +02002254 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002255 if alt_dir and 'objects' == os.path.basename(alt_dir):
2256 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002257 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2258 remote = self.GetRemote(name)
2259
David Pursehouse8a68ff92012-09-24 12:15:13 +09002260 all_refs = self.bare_ref.all
2261 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002262 tmp = set()
2263
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302264 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002265 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002266 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002267 all_refs[r] = ref_id
2268 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002269 continue
2270
David Pursehouse8a68ff92012-09-24 12:15:13 +09002271 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002272 continue
2273
David Pursehouse8a68ff92012-09-24 12:15:13 +09002274 r = 'refs/_alt/%s' % ref_id
2275 all_refs[r] = ref_id
2276 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002277 tmp.add(r)
2278
heping3d7bbc92017-04-12 19:51:47 +08002279 tmp_packed_lines = []
2280 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002281
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302282 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002283 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002284 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002285 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002286 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002287
heping3d7bbc92017-04-12 19:51:47 +08002288 tmp_packed = ''.join(tmp_packed_lines)
2289 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002290 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002291 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002292 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002293
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002294 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002295
Xin Li745be2e2019-06-03 11:24:30 -07002296 if clone_filter:
2297 git_require((2, 19, 0), fail=True, msg='partial clones')
2298 cmd.append('--filter=%s' % clone_filter)
2299 self.config.SetString('extensions.partialclone', self.remote.name)
2300
Conley Owensf97e8382015-01-21 11:12:46 -08002301 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002302 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002303 else:
2304 # If this repo has shallow objects, then we don't know which refs have
2305 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2306 # do this with projects that don't have shallow objects, since it is less
2307 # efficient.
2308 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2309 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002310
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002311 if quiet:
2312 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002313 if not self.worktree:
2314 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002315 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002316
Mike Frysingere57f1142019-03-18 21:27:54 -04002317 if force_sync:
2318 cmd.append('--force')
2319
David Pursehouse74cfd272015-10-14 10:50:15 +09002320 if prune:
2321 cmd.append('--prune')
2322
Martin Kellye4e94d22017-03-21 16:05:12 -07002323 if submodules:
2324 cmd.append('--recurse-submodules=on-demand')
2325
Kuang-che Wu6856f982019-11-25 12:37:55 +08002326 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002327 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002328 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002329 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002330 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002331 spec.append('tag')
2332 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002333
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302334 if self.manifest.IsMirror and not current_branch_only:
2335 branch = None
2336 else:
2337 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002338 if (not self.manifest.IsMirror and is_sha1 and depth
2339 and git_require((1, 8, 3))):
2340 # Shallow checkout of a specific commit, fetch from that commit and not
2341 # the heads only as the commit might be deeper in the history.
2342 spec.append(branch)
2343 else:
2344 if is_sha1:
2345 branch = self.upstream
2346 if branch is not None and branch.strip():
2347 if not branch.startswith('refs/'):
2348 branch = R_HEADS + branch
2349 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2350
2351 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2352 # whole repo.
2353 if self.manifest.IsMirror and not spec:
2354 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2355
2356 # If using depth then we should not get all the tags since they may
2357 # be outside of the depth.
2358 if no_tags or depth:
2359 cmd.append('--no-tags')
2360 else:
2361 cmd.append('--tags')
2362 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2363
Conley Owens80b87fe2014-05-09 17:13:44 -07002364 cmd.extend(spec)
2365
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002366 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002367 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002368 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002369 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002370 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002371 ok = True
2372 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002373 # If needed, run the 'git remote prune' the first time through the loop
2374 elif (not _i and
2375 "error:" in gitcmd.stderr and
2376 "git remote prune" in gitcmd.stderr):
2377 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002378 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002379 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002380 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002381 break
2382 continue
Brian Harring14a66742012-09-28 20:21:57 -07002383 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002384 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2385 # in sha1 mode, we just tried sync'ing from the upstream field; it
2386 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002387 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002388 elif ret < 0:
2389 # Git died with a signal, exit immediately
2390 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002391 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002392
2393 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002394 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002395 if old_packed != '':
2396 _lwrite(packed_refs, old_packed)
2397 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002398 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002399 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002400
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002401 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002402 # We just synced the upstream given branch; verify we
2403 # got what we wanted, else trigger a second run of all
2404 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002405 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002406 if current_branch_only and depth:
2407 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002408 return self._RemoteFetch(name=name,
2409 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002410 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002411 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002412 else:
2413 # Avoid infinite recursion: sync all branches with depth set to None
2414 return self._RemoteFetch(name=name, current_branch_only=False,
2415 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002416 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002417
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002418 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002419
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002420 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002421 if initial and \
2422 (self.manifest.manifestProject.config.GetString('repo.depth') or
2423 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002424 return False
2425
2426 remote = self.GetRemote(self.remote.name)
2427 bundle_url = remote.url + '/clone.bundle'
2428 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002429 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2430 'persistent-http',
2431 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002432 return False
2433
2434 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2435 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2436
2437 exist_dst = os.path.exists(bundle_dst)
2438 exist_tmp = os.path.exists(bundle_tmp)
2439
2440 if not initial and not exist_dst and not exist_tmp:
2441 return False
2442
2443 if not exist_dst:
2444 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2445 if not exist_dst:
2446 return False
2447
2448 cmd = ['fetch']
2449 if quiet:
2450 cmd.append('--quiet')
2451 if not self.worktree:
2452 cmd.append('--update-head-ok')
2453 cmd.append(bundle_dst)
2454 for f in remote.fetch:
2455 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002456 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002457
2458 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002459 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002460 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002461 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002462 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002463 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002464
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002465 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002466 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002467 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002468
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002469 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002470 if quiet:
2471 cmd += ['--silent']
2472 if os.path.exists(tmpPath):
2473 size = os.stat(tmpPath).st_size
2474 if size >= 1024:
2475 cmd += ['--continue-at', '%d' % (size,)]
2476 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002477 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002478 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002479 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002480 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002481 if proxy:
2482 cmd += ['--proxy', proxy]
2483 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2484 cmd += ['--proxy', os.environ['http_proxy']]
2485 if srcUrl.startswith('persistent-https'):
2486 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2487 elif srcUrl.startswith('persistent-http'):
2488 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002489 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002490
Dave Borowitz137d0132015-01-02 11:12:54 -08002491 if IsTrace():
2492 Trace('%s', ' '.join(cmd))
2493 try:
2494 proc = subprocess.Popen(cmd)
2495 except OSError:
2496 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002497
Dave Borowitz137d0132015-01-02 11:12:54 -08002498 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002499
Dave Borowitz137d0132015-01-02 11:12:54 -08002500 if curlret == 22:
2501 # From curl man page:
2502 # 22: HTTP page not retrieved. The requested url was not found or
2503 # returned another error with the HTTP error code being 400 or above.
2504 # This return code only appears if -f, --fail is used.
2505 if not quiet:
2506 print("Server does not provide clone.bundle; ignoring.",
2507 file=sys.stderr)
2508 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002509
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002510 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002511 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002512 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002513 return True
2514 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002515 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002516 return False
2517 else:
2518 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002519
Kris Giesingc8d882a2014-12-23 13:02:32 -08002520 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002521 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002522 with open(path, 'rb') as f:
2523 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002524 return True
2525 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002526 if not quiet:
2527 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002528 return False
2529 except OSError:
2530 return False
2531
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002532 def _Checkout(self, rev, quiet=False):
2533 cmd = ['checkout']
2534 if quiet:
2535 cmd.append('-q')
2536 cmd.append(rev)
2537 cmd.append('--')
2538 if GitCommand(self, cmd).Wait() != 0:
2539 if self._allrefs:
2540 raise GitError('%s checkout %s ' % (self.name, rev))
2541
Anthony King7bdac712014-07-16 12:56:40 +01002542 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002543 cmd = ['cherry-pick']
2544 cmd.append(rev)
2545 cmd.append('--')
2546 if GitCommand(self, cmd).Wait() != 0:
2547 if self._allrefs:
2548 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2549
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302550 def _LsRemote(self, refs):
2551 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302552 p = GitCommand(self, cmd, capture_stdout=True)
2553 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002554 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302555 return None
2556
Anthony King7bdac712014-07-16 12:56:40 +01002557 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002558 cmd = ['revert']
2559 cmd.append('--no-edit')
2560 cmd.append(rev)
2561 cmd.append('--')
2562 if GitCommand(self, cmd).Wait() != 0:
2563 if self._allrefs:
2564 raise GitError('%s revert %s ' % (self.name, rev))
2565
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002566 def _ResetHard(self, rev, quiet=True):
2567 cmd = ['reset', '--hard']
2568 if quiet:
2569 cmd.append('-q')
2570 cmd.append(rev)
2571 if GitCommand(self, cmd).Wait() != 0:
2572 raise GitError('%s reset --hard %s ' % (self.name, rev))
2573
Martin Kellye4e94d22017-03-21 16:05:12 -07002574 def _SyncSubmodules(self, quiet=True):
2575 cmd = ['submodule', 'update', '--init', '--recursive']
2576 if quiet:
2577 cmd.append('-q')
2578 if GitCommand(self, cmd).Wait() != 0:
2579 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2580
Anthony King7bdac712014-07-16 12:56:40 +01002581 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002582 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002583 if onto is not None:
2584 cmd.extend(['--onto', onto])
2585 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002586 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002587 raise GitError('%s rebase %s ' % (self.name, upstream))
2588
Pierre Tardy3d125942012-05-04 12:18:12 +02002589 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002590 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002591 if ffonly:
2592 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002593 if GitCommand(self, cmd).Wait() != 0:
2594 raise GitError('%s merge %s ' % (self.name, head))
2595
Kevin Degiabaa7f32014-11-12 11:27:45 -07002596 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002597 init_git_dir = not os.path.exists(self.gitdir)
2598 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002599 try:
2600 # Initialize the bare repository, which contains all of the objects.
2601 if init_obj_dir:
2602 os.makedirs(self.objdir)
2603 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002604
Kevin Degib1a07b82015-07-27 13:33:43 -06002605 # If we have a separate directory to hold refs, initialize it as well.
2606 if self.objdir != self.gitdir:
2607 if init_git_dir:
2608 os.makedirs(self.gitdir)
2609
2610 if init_obj_dir or init_git_dir:
2611 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2612 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002613 try:
2614 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2615 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002616 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002617 print("Retrying clone after deleting %s" %
2618 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002619 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002620 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2621 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002622 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002623 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002624 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2625 except:
2626 raise e
2627 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002628
Kevin Degi384b3c52014-10-16 16:02:58 -06002629 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002630 mp = self.manifest.manifestProject
2631 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002632
Kevin Degib1a07b82015-07-27 13:33:43 -06002633 if ref_dir or mirror_git:
2634 if not mirror_git:
2635 mirror_git = os.path.join(ref_dir, self.name + '.git')
2636 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2637 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002638
Kevin Degib1a07b82015-07-27 13:33:43 -06002639 if os.path.exists(mirror_git):
2640 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002641
Kevin Degib1a07b82015-07-27 13:33:43 -06002642 elif os.path.exists(repo_git):
2643 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002644
Kevin Degib1a07b82015-07-27 13:33:43 -06002645 else:
2646 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002647
Kevin Degib1a07b82015-07-27 13:33:43 -06002648 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002649 if not os.path.isabs(ref_dir):
2650 # The alternate directory is relative to the object database.
2651 ref_dir = os.path.relpath(ref_dir,
2652 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002653 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2654 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002655
Kevin Degib1a07b82015-07-27 13:33:43 -06002656 self._UpdateHooks()
2657
2658 m = self.manifest.manifestProject.config
2659 for key in ['user.name', 'user.email']:
2660 if m.Has(key, include_defaults=False):
2661 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002662 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002663 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002664 if self.manifest.IsMirror:
2665 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002666 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002667 self.config.SetString('core.bare', None)
2668 except Exception:
2669 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002670 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002671 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002672 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002673 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002674
Jimmie Westera0444582012-10-24 13:44:42 +02002675 def _UpdateHooks(self):
2676 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002677 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002678
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002679 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002680 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002681 if not os.path.exists(hooks):
2682 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002683 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002684 name = os.path.basename(stock_hook)
2685
Victor Boivie65e0f352011-04-18 11:23:29 +02002686 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002687 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002688 # Don't install a Gerrit Code Review hook if this
2689 # project does not appear to use it for reviews.
2690 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002691 # Since the manifest project is one of those, but also
2692 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002693 continue
2694
2695 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002696 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002697 continue
2698 if os.path.exists(dst):
2699 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002700 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002701 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002702 _warn("%s: Not replacing locally modified %s hook",
2703 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002704 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002705 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002706 platform_utils.symlink(
2707 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002708 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002709 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002710 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002711 else:
2712 raise
2713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002714 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002715 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002716 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002717 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002718 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002719 remote.review = self.remote.review
2720 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002721
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002722 if self.worktree:
2723 remote.ResetFetch(mirror=False)
2724 else:
2725 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002726 remote.Save()
2727
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002728 def _InitMRef(self):
2729 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002730 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002731
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002732 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002733 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002734
2735 def _InitAnyMRef(self, ref):
2736 cur = self.bare_ref.symref(ref)
2737
2738 if self.revisionId:
2739 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2740 msg = 'manifest set to %s' % self.revisionId
2741 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002742 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002743 else:
2744 remote = self.GetRemote(self.remote.name)
2745 dst = remote.ToLocal(self.revisionExpr)
2746 if cur != dst:
2747 msg = 'manifest set to %s' % self.revisionExpr
2748 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002749
Kevin Degi384b3c52014-10-16 16:02:58 -06002750 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002751 symlink_files = self.shareable_files[:]
2752 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002753 if share_refs:
2754 symlink_files += self.working_tree_files
2755 symlink_dirs += self.working_tree_dirs
2756 to_symlink = symlink_files + symlink_dirs
2757 for name in set(to_symlink):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002758 dst = platform_utils.realpath(os.path.join(destdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002759 if os.path.lexists(dst):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002760 src = platform_utils.realpath(os.path.join(srcdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002761 # Fail if the links are pointing to the wrong place
2762 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002763 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002764 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002765 'work tree. If you\'re comfortable with the '
2766 'possibility of losing the work tree\'s git metadata,'
2767 ' use `repo sync --force-sync {0}` to '
2768 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002769
David James8d201162013-10-11 17:03:19 -07002770 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2771 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2772
2773 Args:
2774 gitdir: The bare git repository. Must already be initialized.
2775 dotgit: The repository you would like to initialize.
2776 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2777 Only one work tree can store refs under a given |gitdir|.
2778 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2779 This saves you the effort of initializing |dotgit| yourself.
2780 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002781 symlink_files = self.shareable_files[:]
2782 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002783 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002784 symlink_files += self.working_tree_files
2785 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002786 to_symlink = symlink_files + symlink_dirs
2787
2788 to_copy = []
2789 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002790 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002791
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002792 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002793 for name in set(to_copy).union(to_symlink):
2794 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002795 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002796 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002797
Kevin Degi384b3c52014-10-16 16:02:58 -06002798 if os.path.lexists(dst):
2799 continue
David James8d201162013-10-11 17:03:19 -07002800
2801 # If the source dir doesn't exist, create an empty dir.
2802 if name in symlink_dirs and not os.path.lexists(src):
2803 os.makedirs(src)
2804
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002805 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002806 platform_utils.symlink(
2807 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002808 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002809 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002810 shutil.copytree(src, dst)
2811 elif os.path.isfile(src):
2812 shutil.copy(src, dst)
2813
Conley Owens80b87fe2014-05-09 17:13:44 -07002814 # If the source file doesn't exist, ensure the destination
2815 # file doesn't either.
2816 if name in symlink_files and not os.path.lexists(src):
2817 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002818 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002819 except OSError:
2820 pass
2821
David James8d201162013-10-11 17:03:19 -07002822 except OSError as e:
2823 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002824 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002825 else:
2826 raise
2827
Martin Kellye4e94d22017-03-21 16:05:12 -07002828 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002829 realdotgit = os.path.join(self.worktree, '.git')
2830 tmpdotgit = realdotgit + '.tmp'
2831 init_dotgit = not os.path.exists(realdotgit)
2832 if init_dotgit:
2833 dotgit = tmpdotgit
2834 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2835 os.makedirs(tmpdotgit)
2836 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2837 copy_all=False)
2838 else:
2839 dotgit = realdotgit
2840
Kevin Degib1a07b82015-07-27 13:33:43 -06002841 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002842 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2843 except GitError as e:
2844 if force_sync and not init_dotgit:
2845 try:
2846 platform_utils.rmtree(dotgit)
2847 return self._InitWorkTree(force_sync=False, submodules=submodules)
2848 except:
2849 raise e
2850 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002851
Mike Frysingerf4545122019-11-11 04:34:16 -05002852 if init_dotgit:
2853 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002854
Mike Frysingerf4545122019-11-11 04:34:16 -05002855 # Now that the .git dir is fully set up, move it to its final home.
2856 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002857
Mike Frysingerf4545122019-11-11 04:34:16 -05002858 # Finish checking out the worktree.
2859 cmd = ['read-tree', '--reset', '-u']
2860 cmd.append('-v')
2861 cmd.append(HEAD)
2862 if GitCommand(self, cmd).Wait() != 0:
2863 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002864
Mike Frysingerf4545122019-11-11 04:34:16 -05002865 if submodules:
2866 self._SyncSubmodules(quiet=True)
2867 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002868
Renaud Paquay788e9622017-01-27 11:41:12 -08002869 def _get_symlink_error_message(self):
2870 if platform_utils.isWindows():
2871 return ('Unable to create symbolic link. Please re-run the command as '
2872 'Administrator, or see '
2873 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2874 'for other options.')
2875 return 'filesystem must support symlinks'
2876
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002877 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002878 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002879
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002880 def _revlist(self, *args, **kw):
2881 a = []
2882 a.extend(args)
2883 a.append('--')
2884 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002885
2886 @property
2887 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002888 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002889
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002890 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002891 """Get logs between two revisions of this project."""
2892 comp = '..'
2893 if rev1:
2894 revs = [rev1]
2895 if rev2:
2896 revs.extend([comp, rev2])
2897 cmd = ['log', ''.join(revs)]
2898 out = DiffColoring(self.config)
2899 if out.is_on and color:
2900 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002901 if pretty_format is not None:
2902 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002903 if oneline:
2904 cmd.append('--oneline')
2905
2906 try:
2907 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2908 if log.Wait() == 0:
2909 return log.stdout
2910 except GitError:
2911 # worktree may not exist if groups changed for example. In that case,
2912 # try in gitdir instead.
2913 if not os.path.exists(self.worktree):
2914 return self.bare_git.log(*cmd[1:])
2915 else:
2916 raise
2917 return None
2918
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002919 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2920 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002921 """Get the list of logs from this revision to given revisionId"""
2922 logs = {}
2923 selfId = self.GetRevisionId(self._allrefs)
2924 toId = toProject.GetRevisionId(toProject._allrefs)
2925
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002926 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2927 pretty_format=pretty_format)
2928 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2929 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002930 return logs
2931
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002932 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002933
David James8d201162013-10-11 17:03:19 -07002934 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002935 self._project = project
2936 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002937 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002938
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002939 def LsOthers(self):
2940 p = GitCommand(self._project,
2941 ['ls-files',
2942 '-z',
2943 '--others',
2944 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002945 bare=False,
David James8d201162013-10-11 17:03:19 -07002946 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002947 capture_stdout=True,
2948 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002949 if p.Wait() == 0:
2950 out = p.stdout
2951 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002952 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002953 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002954 return []
2955
2956 def DiffZ(self, name, *args):
2957 cmd = [name]
2958 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002959 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002960 cmd.extend(args)
2961 p = GitCommand(self._project,
2962 cmd,
David James8d201162013-10-11 17:03:19 -07002963 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002964 bare=False,
2965 capture_stdout=True,
2966 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002967 try:
2968 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002969 if not hasattr(out, 'encode'):
2970 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002971 r = {}
2972 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002973 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002974 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002975 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002976 info = next(out)
2977 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002978 except StopIteration:
2979 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002980
2981 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002982
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002983 def __init__(self, path, omode, nmode, oid, nid, state):
2984 self.path = path
2985 self.src_path = None
2986 self.old_mode = omode
2987 self.new_mode = nmode
2988 self.old_id = oid
2989 self.new_id = nid
2990
2991 if len(state) == 1:
2992 self.status = state
2993 self.level = None
2994 else:
2995 self.status = state[:1]
2996 self.level = state[1:]
2997 while self.level.startswith('0'):
2998 self.level = self.level[1:]
2999
3000 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003001 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003002 if info.status in ('R', 'C'):
3003 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003004 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005 r[info.path] = info
3006 return r
3007 finally:
3008 p.Wait()
3009
3010 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003011 if self._bare:
3012 path = os.path.join(self._project.gitdir, HEAD)
3013 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003014 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003015 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003016 with open(path) as fd:
3017 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003018 except IOError as e:
3019 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003020 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303021 line = line.decode()
3022 except AttributeError:
3023 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003024 if line.startswith('ref: '):
3025 return line[5:-1]
3026 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003027
3028 def SetHead(self, ref, message=None):
3029 cmdv = []
3030 if message is not None:
3031 cmdv.extend(['-m', message])
3032 cmdv.append(HEAD)
3033 cmdv.append(ref)
3034 self.symbolic_ref(*cmdv)
3035
3036 def DetachHead(self, new, message=None):
3037 cmdv = ['--no-deref']
3038 if message is not None:
3039 cmdv.extend(['-m', message])
3040 cmdv.append(HEAD)
3041 cmdv.append(new)
3042 self.update_ref(*cmdv)
3043
3044 def UpdateRef(self, name, new, old=None,
3045 message=None,
3046 detach=False):
3047 cmdv = []
3048 if message is not None:
3049 cmdv.extend(['-m', message])
3050 if detach:
3051 cmdv.append('--no-deref')
3052 cmdv.append(name)
3053 cmdv.append(new)
3054 if old is not None:
3055 cmdv.append(old)
3056 self.update_ref(*cmdv)
3057
3058 def DeleteRef(self, name, old=None):
3059 if not old:
3060 old = self.rev_parse(name)
3061 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003062 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003063
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003064 def rev_list(self, *args, **kw):
3065 if 'format' in kw:
3066 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3067 else:
3068 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003069 cmdv.extend(args)
3070 p = GitCommand(self._project,
3071 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003072 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003073 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003074 capture_stdout=True,
3075 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003076 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003077 raise GitError('%s rev-list %s: %s' %
3078 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003079 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003080
3081 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003082 """Allow arbitrary git commands using pythonic syntax.
3083
3084 This allows you to do things like:
3085 git_obj.rev_parse('HEAD')
3086
3087 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3088 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003089 Any other positional arguments will be passed to the git command, and the
3090 following keyword arguments are supported:
3091 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003092
3093 Args:
3094 name: The name of the git command to call. Any '_' characters will
3095 be replaced with '-'.
3096
3097 Returns:
3098 A callable object that will try to call git with the named command.
3099 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003100 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003101
Dave Borowitz091f8932012-10-23 17:01:04 -07003102 def runner(*args, **kwargs):
3103 cmdv = []
3104 config = kwargs.pop('config', None)
3105 for k in kwargs:
3106 raise TypeError('%s() got an unexpected keyword argument %r'
3107 % (name, k))
3108 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07003109 if not git_require((1, 7, 2)):
3110 raise ValueError('cannot set config on command line for %s()'
3111 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303112 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003113 cmdv.append('-c')
3114 cmdv.append('%s=%s' % (k, v))
3115 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003116 cmdv.extend(args)
3117 p = GitCommand(self._project,
3118 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003119 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003120 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003121 capture_stdout=True,
3122 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003123 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003124 raise GitError('%s %s: %s' %
3125 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003126 r = p.stdout
3127 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3128 return r[:-1]
3129 return r
3130 return runner
3131
3132
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003133class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003134
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003135 def __str__(self):
3136 return 'prior sync failed; rebase still in progress'
3137
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003138
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003139class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003140
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003141 def __str__(self):
3142 return 'contains uncommitted changes'
3143
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003144
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003145class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003146
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003147 def __init__(self, project, text):
3148 self.project = project
3149 self.text = text
3150
3151 def Print(self, syncbuf):
3152 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3153 syncbuf.out.nl()
3154
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003155
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003156class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003157
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003158 def __init__(self, project, why):
3159 self.project = project
3160 self.why = why
3161
3162 def Print(self, syncbuf):
3163 syncbuf.out.fail('error: %s/: %s',
3164 self.project.relpath,
3165 str(self.why))
3166 syncbuf.out.nl()
3167
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003168
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003169class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003170
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003171 def __init__(self, project, action):
3172 self.project = project
3173 self.action = action
3174
3175 def Run(self, syncbuf):
3176 out = syncbuf.out
3177 out.project('project %s/', self.project.relpath)
3178 out.nl()
3179 try:
3180 self.action()
3181 out.nl()
3182 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003183 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003184 out.nl()
3185 return False
3186
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003187
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003188class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003189
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003190 def __init__(self, config):
3191 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003192 self.project = self.printer('header', attr='bold')
3193 self.info = self.printer('info')
3194 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003195
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003196
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003197class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003198
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003199 def __init__(self, config, detach_head=False):
3200 self._messages = []
3201 self._failures = []
3202 self._later_queue1 = []
3203 self._later_queue2 = []
3204
3205 self.out = _SyncColoring(config)
3206 self.out.redirect(sys.stderr)
3207
3208 self.detach_head = detach_head
3209 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003210 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003211
3212 def info(self, project, fmt, *args):
3213 self._messages.append(_InfoMessage(project, fmt % args))
3214
3215 def fail(self, project, err=None):
3216 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003217 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003218
3219 def later1(self, project, what):
3220 self._later_queue1.append(_Later(project, what))
3221
3222 def later2(self, project, what):
3223 self._later_queue2.append(_Later(project, what))
3224
3225 def Finish(self):
3226 self._PrintMessages()
3227 self._RunLater()
3228 self._PrintMessages()
3229 return self.clean
3230
David Rileye0684ad2017-04-05 00:02:59 -07003231 def Recently(self):
3232 recent_clean = self.recent_clean
3233 self.recent_clean = True
3234 return recent_clean
3235
3236 def _MarkUnclean(self):
3237 self.clean = False
3238 self.recent_clean = False
3239
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003240 def _RunLater(self):
3241 for q in ['_later_queue1', '_later_queue2']:
3242 if not self._RunQueue(q):
3243 return
3244
3245 def _RunQueue(self, queue):
3246 for m in getattr(self, queue):
3247 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003248 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003249 return False
3250 setattr(self, queue, [])
3251 return True
3252
3253 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003254 if self._messages or self._failures:
3255 if os.isatty(2):
3256 self.out.write(progress.CSI_ERASE_LINE)
3257 self.out.write('\r')
3258
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003259 for m in self._messages:
3260 m.Print(self)
3261 for m in self._failures:
3262 m.Print(self)
3263
3264 self._messages = []
3265 self._failures = []
3266
3267
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003268class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003269
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003270 """A special project housed under .repo.
3271 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003272
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003273 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003274 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003275 manifest=manifest,
3276 name=name,
3277 gitdir=gitdir,
3278 objdir=gitdir,
3279 worktree=worktree,
3280 remote=RemoteSpec('origin'),
3281 relpath='.repo/%s' % name,
3282 revisionExpr='refs/heads/master',
3283 revisionId=None,
3284 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003285
3286 def PreSync(self):
3287 if self.Exists:
3288 cb = self.CurrentBranch
3289 if cb:
3290 base = self.GetBranch(cb).merge
3291 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003292 self.revisionExpr = base
3293 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003294
Martin Kelly224a31a2017-07-10 14:46:25 -07003295 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003296 """ Prepare MetaProject for manifest branch switch
3297 """
3298
3299 # detach and delete manifest branch, allowing a new
3300 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003301 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003302 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003303 syncbuf.Finish()
3304
3305 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003306 ['update-ref', '-d', 'refs/heads/default'],
3307 capture_stdout=True,
3308 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003309
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003310 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003311 def LastFetch(self):
3312 try:
3313 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3314 return os.path.getmtime(fh)
3315 except OSError:
3316 return 0
3317
3318 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003319 def HasChanges(self):
3320 """Has the remote received new commits not yet checked out?
3321 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003322 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003323 return False
3324
David Pursehouse8a68ff92012-09-24 12:15:13 +09003325 all_refs = self.bare_ref.all
3326 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003327 head = self.work_git.GetHead()
3328 if head.startswith(R_HEADS):
3329 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003330 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003331 except KeyError:
3332 head = None
3333
3334 if revid == head:
3335 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003336 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003337 return True
3338 return False