blob: 855ea57f901bc981db5c2e22dc2bc9d080ab860e [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, \
Ningning Xiac2fbc782016-08-22 14:24:39 -070037 ID_RE, RefSpec
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Ningning Xiac2fbc782016-08-22 14:24:39 -070039from error import CacheApplyError
Mike Frysingere6a202f2019-08-02 15:57:57 -040040from error import ManifestInvalidRevisionError, ManifestInvalidPathError
Conley Owens75ee0572012-11-15 17:33:11 -080041from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070042import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040043import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040044from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070045
Shawn O. Pearced237b692009-04-17 18:49:50 -070046from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070047
David Pursehouse59bbb582013-05-17 10:49:33 +090048from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040049if is_python3():
50 import urllib.parse
51else:
52 import imp
53 import urlparse
54 urllib = imp.new_module('urllib')
55 urllib.parse = urlparse
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056 input = raw_input
Chirayu Desai217ea7d2013-03-01 19:14:38 +053057
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070058
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070059def _lwrite(path, content):
60 lock = '%s.lock' % path
61
Mike Frysinger3164d402019-11-11 05:40:22 -050062 with open(lock, 'w') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070063 fd.write(content)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070064
65 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070066 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070067 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080068 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070069 raise
70
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070071
Shawn O. Pearce48244782009-04-16 08:25:57 -070072def _error(fmt, *args):
73 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070074 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070075
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070076
David Pursehousef33929d2015-08-24 14:39:14 +090077def _warn(fmt, *args):
78 msg = fmt % args
79 print('warn: %s' % msg, file=sys.stderr)
80
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070081
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082def not_rev(r):
83 return '^' + r
84
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070085
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080086def sq(r):
87 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080088
David Pursehouse819827a2020-02-12 15:20:19 +090089
Jonathan Nieder93719792015-03-17 11:29:58 -070090_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070091
92
Jonathan Nieder93719792015-03-17 11:29:58 -070093def _ProjectHooks():
94 """List the hooks present in the 'hooks' directory.
95
96 These hooks are project hooks and are copied to the '.git/hooks' directory
97 of all subprojects.
98
99 This function caches the list of hooks (based on the contents of the
100 'repo/hooks' directory) on the first call.
101
102 Returns:
103 A list of absolute paths to all of the files in the hooks directory.
104 """
105 global _project_hook_list
106 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700107 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700108 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700109 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700110 return _project_hook_list
111
112
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700113class DownloadedChange(object):
114 _commit_cache = None
115
116 def __init__(self, project, base, change_id, ps_id, commit):
117 self.project = project
118 self.base = base
119 self.change_id = change_id
120 self.ps_id = ps_id
121 self.commit = commit
122
123 @property
124 def commits(self):
125 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700126 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
127 '--abbrev-commit',
128 '--pretty=oneline',
129 '--reverse',
130 '--date-order',
131 not_rev(self.base),
132 self.commit,
133 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700134 return self._commit_cache
135
136
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137class ReviewableBranch(object):
138 _commit_cache = None
Mike Frysinger6da17752019-09-11 18:43:17 -0400139 _base_exists = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140
141 def __init__(self, project, branch, base):
142 self.project = project
143 self.branch = branch
144 self.base = base
145
146 @property
147 def name(self):
148 return self.branch.name
149
150 @property
151 def commits(self):
152 if self._commit_cache is None:
Mike Frysinger6da17752019-09-11 18:43:17 -0400153 args = ('--abbrev=8', '--abbrev-commit', '--pretty=oneline', '--reverse',
154 '--date-order', not_rev(self.base), R_HEADS + self.name, '--')
155 try:
156 self._commit_cache = self.project.bare_git.rev_list(*args)
157 except GitError:
158 # We weren't able to probe the commits for this branch. Was it tracking
159 # a branch that no longer exists? If so, return no commits. Otherwise,
160 # rethrow the error as we don't know what's going on.
161 if self.base_exists:
162 raise
163
164 self._commit_cache = []
165
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166 return self._commit_cache
167
168 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800169 def unabbrev_commits(self):
170 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700171 for commit in self.project.bare_git.rev_list(not_rev(self.base),
172 R_HEADS + self.name,
173 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800174 r[commit[0:8]] = commit
175 return r
176
177 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700179 return self.project.bare_git.log('--pretty=format:%cd',
180 '-n', '1',
181 R_HEADS + self.name,
182 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183
Mike Frysinger6da17752019-09-11 18:43:17 -0400184 @property
185 def base_exists(self):
186 """Whether the branch we're tracking exists.
187
188 Normally it should, but sometimes branches we track can get deleted.
189 """
190 if self._base_exists is None:
191 try:
192 self.project.bare_git.rev_parse('--verify', not_rev(self.base))
193 # If we're still here, the base branch exists.
194 self._base_exists = True
195 except GitError:
196 # If we failed to verify, the base branch doesn't exist.
197 self._base_exists = False
198
199 return self._base_exists
200
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700201 def UploadForReview(self, people,
202 auto_topic=False,
203 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200204 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700205 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200206 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200207 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800208 validate_certs=True,
209 push_options=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800210 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700211 people,
Brian Harring435370c2012-07-28 15:37:04 -0700212 auto_topic=auto_topic,
Bryan Jacobsf609f912013-05-06 13:36:24 -0400213 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200214 private=private,
Vadim Bendebury75bcd242018-10-31 13:48:01 -0700215 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200216 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200217 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800218 validate_certs=validate_certs,
219 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700221 def GetPublishedRefs(self):
222 refs = {}
223 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700224 self.branch.remote.SshReviewUrl(self.project.UserEmail),
225 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700226 for line in output.split('\n'):
227 try:
228 (sha, ref) = line.split()
229 refs[sha] = ref
230 except ValueError:
231 pass
232
233 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700235
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700236class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700237
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238 def __init__(self, config):
239 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100240 self.project = self.printer('header', attr='bold')
241 self.branch = self.printer('header', attr='bold')
242 self.nobranch = self.printer('nobranch', fg='red')
243 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244
Anthony King7bdac712014-07-16 12:56:40 +0100245 self.added = self.printer('added', fg='green')
246 self.changed = self.printer('changed', fg='red')
247 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248
249
250class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700251
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252 def __init__(self, config):
253 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100254 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400255 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700257
Anthony King7bdac712014-07-16 12:56:40 +0100258class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700259
James W. Mills24c13082012-04-12 15:04:13 -0500260 def __init__(self, name, value, keep):
261 self.name = name
262 self.value = value
263 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700265
Mike Frysingere6a202f2019-08-02 15:57:57 -0400266def _SafeExpandPath(base, subpath, skipfinal=False):
267 """Make sure |subpath| is completely safe under |base|.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700268
Mike Frysingere6a202f2019-08-02 15:57:57 -0400269 We make sure no intermediate symlinks are traversed, and that the final path
270 is not a special file (e.g. not a socket or fifo).
271
272 NB: We rely on a number of paths already being filtered out while parsing the
273 manifest. See the validation logic in manifest_xml.py for more details.
274 """
275 components = subpath.split(os.path.sep)
276 if skipfinal:
277 # Whether the caller handles the final component itself.
278 finalpart = components.pop()
279
280 path = base
281 for part in components:
282 if part in {'.', '..'}:
283 raise ManifestInvalidPathError(
284 '%s: "%s" not allowed in paths' % (subpath, part))
285
286 path = os.path.join(path, part)
287 if platform_utils.islink(path):
288 raise ManifestInvalidPathError(
289 '%s: traversing symlinks not allow' % (path,))
290
291 if os.path.exists(path):
292 if not os.path.isfile(path) and not platform_utils.isdir(path):
293 raise ManifestInvalidPathError(
294 '%s: only regular files & directories allowed' % (path,))
295
296 if skipfinal:
297 path = os.path.join(path, finalpart)
298
299 return path
300
301
302class _CopyFile(object):
303 """Container for <copyfile> manifest element."""
304
305 def __init__(self, git_worktree, src, topdir, dest):
306 """Register a <copyfile> request.
307
308 Args:
309 git_worktree: Absolute path to the git project checkout.
310 src: Relative path under |git_worktree| of file to read.
311 topdir: Absolute path to the top of the repo client checkout.
312 dest: Relative path under |topdir| of file to write.
313 """
314 self.git_worktree = git_worktree
315 self.topdir = topdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700316 self.src = src
317 self.dest = dest
318
319 def _Copy(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400320 src = _SafeExpandPath(self.git_worktree, self.src)
321 dest = _SafeExpandPath(self.topdir, self.dest)
322
323 if platform_utils.isdir(src):
324 raise ManifestInvalidPathError(
325 '%s: copying from directory not supported' % (self.src,))
326 if platform_utils.isdir(dest):
327 raise ManifestInvalidPathError(
328 '%s: copying to directory not allowed' % (self.dest,))
329
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700330 # copy file if it does not exist or is out of date
331 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
332 try:
333 # remove existing file first, since it might be read-only
334 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800335 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400336 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200337 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700338 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200339 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700340 shutil.copy(src, dest)
341 # make the file read-only
342 mode = os.stat(dest)[stat.ST_MODE]
343 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
344 os.chmod(dest, mode)
345 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700346 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700347
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700348
Anthony King7bdac712014-07-16 12:56:40 +0100349class _LinkFile(object):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400350 """Container for <linkfile> manifest element."""
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700351
Mike Frysingere6a202f2019-08-02 15:57:57 -0400352 def __init__(self, git_worktree, src, topdir, dest):
353 """Register a <linkfile> request.
354
355 Args:
356 git_worktree: Absolute path to the git project checkout.
357 src: Target of symlink relative to path under |git_worktree|.
358 topdir: Absolute path to the top of the repo client checkout.
359 dest: Relative path under |topdir| of symlink to create.
360 """
Wink Saville4c426ef2015-06-03 08:05:17 -0700361 self.git_worktree = git_worktree
Mike Frysingere6a202f2019-08-02 15:57:57 -0400362 self.topdir = topdir
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500363 self.src = src
364 self.dest = dest
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500365
Wink Saville4c426ef2015-06-03 08:05:17 -0700366 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500367 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700368 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500369 try:
370 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800371 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800372 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500373 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700374 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700375 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500376 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700377 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500378 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700379 _error('Cannot link file %s to %s', relSrc, absDest)
380
381 def _Link(self):
Mike Frysingere6a202f2019-08-02 15:57:57 -0400382 """Link the self.src & self.dest paths.
383
384 Handles wild cards on the src linking all of the files in the source in to
385 the destination directory.
Wink Saville4c426ef2015-06-03 08:05:17 -0700386 """
Mike Frysinger07392ed2020-02-10 21:35:48 -0500387 # Some people use src="." to create stable links to projects. Lets allow
388 # that but reject all other uses of "." to keep things simple.
389 if self.src == '.':
390 src = self.git_worktree
391 else:
392 src = _SafeExpandPath(self.git_worktree, self.src)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400393
394 if os.path.exists(src):
395 # Entity exists so just a simple one to one link operation.
396 dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True)
397 # dest & src are absolute paths at this point. Make sure the target of
398 # the symlink is relative in the context of the repo client checkout.
399 relpath = os.path.relpath(src, os.path.dirname(dest))
400 self.__linkIt(relpath, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700401 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400402 dest = _SafeExpandPath(self.topdir, self.dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700403 # Entity doesn't exist assume there is a wild card
Mike Frysingere6a202f2019-08-02 15:57:57 -0400404 if os.path.exists(dest) and not platform_utils.isdir(dest):
405 _error('Link error: src with wildcard, %s must be a directory', dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700406 else:
Mike Frysingere6a202f2019-08-02 15:57:57 -0400407 for absSrcFile in glob.glob(src):
Wink Saville4c426ef2015-06-03 08:05:17 -0700408 # Create a releative path from source dir to destination dir
409 absSrcDir = os.path.dirname(absSrcFile)
Mike Frysingere6a202f2019-08-02 15:57:57 -0400410 relSrcDir = os.path.relpath(absSrcDir, dest)
Wink Saville4c426ef2015-06-03 08:05:17 -0700411
412 # Get the source file name
413 srcFile = os.path.basename(absSrcFile)
414
415 # Now form the final full paths to srcFile. They will be
416 # absolute for the desintaiton and relative for the srouce.
Mike Frysingere6a202f2019-08-02 15:57:57 -0400417 absDest = os.path.join(dest, srcFile)
Wink Saville4c426ef2015-06-03 08:05:17 -0700418 relSrc = os.path.join(relSrcDir, srcFile)
419 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500420
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700421
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700422class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700423
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700424 def __init__(self,
425 name,
Anthony King7bdac712014-07-16 12:56:40 +0100426 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700427 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100428 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700429 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700430 orig_name=None,
431 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700432 self.name = name
433 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700434 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700435 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100436 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700437 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700438 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700439
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700440
Doug Anderson37282b42011-03-04 11:54:18 -0800441class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700442
Doug Anderson37282b42011-03-04 11:54:18 -0800443 """A RepoHook contains information about a script to run as a hook.
444
445 Hooks are used to run a python script before running an upload (for instance,
446 to run presubmit checks). Eventually, we may have hooks for other actions.
447
448 This shouldn't be confused with files in the 'repo/hooks' directory. Those
449 files are copied into each '.git/hooks' folder for each project. Repo-level
450 hooks are associated instead with repo actions.
451
452 Hooks are always python. When a hook is run, we will load the hook into the
453 interpreter and execute its main() function.
454 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700455
Doug Anderson37282b42011-03-04 11:54:18 -0800456 def __init__(self,
457 hook_type,
458 hooks_project,
459 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400460 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800461 abort_if_user_denies=False):
462 """RepoHook constructor.
463
464 Params:
465 hook_type: A string representing the type of hook. This is also used
466 to figure out the name of the file containing the hook. For
467 example: 'pre-upload'.
468 hooks_project: The project containing the repo hooks. If you have a
469 manifest, this is manifest.repo_hooks_project. OK if this is None,
470 which will make the hook a no-op.
471 topdir: Repo's top directory (the one containing the .repo directory).
472 Scripts will run with CWD as this directory. If you have a manifest,
473 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800475 abort_if_user_denies: If True, we'll throw a HookError() if the user
476 doesn't allow us to run the hook.
477 """
478 self._hook_type = hook_type
479 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400480 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800481 self._topdir = topdir
482 self._abort_if_user_denies = abort_if_user_denies
483
484 # Store the full path to the script for convenience.
485 if self._hooks_project:
486 self._script_fullpath = os.path.join(self._hooks_project.worktree,
487 self._hook_type + '.py')
488 else:
489 self._script_fullpath = None
490
491 def _GetHash(self):
492 """Return a hash of the contents of the hooks directory.
493
494 We'll just use git to do this. This hash has the property that if anything
495 changes in the directory we will return a different has.
496
497 SECURITY CONSIDERATION:
498 This hash only represents the contents of files in the hook directory, not
499 any other files imported or called by hooks. Changes to imported files
500 can change the script behavior without affecting the hash.
501
502 Returns:
503 A string representing the hash. This will always be ASCII so that it can
504 be printed to the user easily.
505 """
506 assert self._hooks_project, "Must have hooks to calculate their hash."
507
508 # We will use the work_git object rather than just calling GetRevisionId().
509 # That gives us a hash of the latest checked in version of the files that
510 # the user will actually be executing. Specifically, GetRevisionId()
511 # doesn't appear to change even if a user checks out a different version
512 # of the hooks repo (via git checkout) nor if a user commits their own revs.
513 #
514 # NOTE: Local (non-committed) changes will not be factored into this hash.
515 # I think this is OK, since we're really only worried about warning the user
516 # about upstream changes.
517 return self._hooks_project.work_git.rev_parse('HEAD')
518
519 def _GetMustVerb(self):
520 """Return 'must' if the hook is required; 'should' if not."""
521 if self._abort_if_user_denies:
522 return 'must'
523 else:
524 return 'should'
525
526 def _CheckForHookApproval(self):
527 """Check to see whether this hook has been approved.
528
Mike Frysinger40252c22016-08-15 21:23:44 -0400529 We'll accept approval of manifest URLs if they're using secure transports.
530 This way the user can say they trust the manifest hoster. For insecure
531 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800532
533 Note that we ask permission for each individual hook even though we use
534 the hash of all hooks when detecting changes. We'd like the user to be
535 able to approve / deny each hook individually. We only use the hash of all
536 hooks because there is no other easy way to detect changes to local imports.
537
538 Returns:
539 True if this hook is approved to run; False otherwise.
540
541 Raises:
542 HookError: Raised if the user doesn't approve and abort_if_user_denies
543 was passed to the consturctor.
544 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400545 if self._ManifestUrlHasSecureScheme():
546 return self._CheckForHookApprovalManifest()
547 else:
548 return self._CheckForHookApprovalHash()
549
550 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
551 changed_prompt):
552 """Check for approval for a particular attribute and hook.
553
554 Args:
555 subkey: The git config key under [repo.hooks.<hook_type>] to store the
556 last approved string.
557 new_val: The new value to compare against the last approved one.
558 main_prompt: Message to display to the user to ask for approval.
559 changed_prompt: Message explaining why we're re-asking for approval.
560
561 Returns:
562 True if this hook is approved to run; False otherwise.
563
564 Raises:
565 HookError: Raised if the user doesn't approve and abort_if_user_denies
566 was passed to the consturctor.
567 """
Doug Anderson37282b42011-03-04 11:54:18 -0800568 hooks_config = self._hooks_project.config
Mike Frysinger40252c22016-08-15 21:23:44 -0400569 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800570
Mike Frysinger40252c22016-08-15 21:23:44 -0400571 # Get the last value that the user approved for this hook; may be None.
572 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800573
Mike Frysinger40252c22016-08-15 21:23:44 -0400574 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800575 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400576 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800577 # Approval matched. We're done.
578 return True
579 else:
580 # Give the user a reason why we're prompting, since they last told
581 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400582 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800583 else:
584 prompt = ''
585
586 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
587 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400588 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530589 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900590 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800591
592 # User is doing a one-time approval.
593 if response in ('y', 'yes'):
594 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400595 elif response == 'always':
596 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800597 return True
598
599 # For anything else, we'll assume no approval.
600 if self._abort_if_user_denies:
601 raise HookError('You must allow the %s hook or use --no-verify.' %
602 self._hook_type)
603
604 return False
605
Mike Frysinger40252c22016-08-15 21:23:44 -0400606 def _ManifestUrlHasSecureScheme(self):
607 """Check if the URI for the manifest is a secure transport."""
608 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
609 parse_results = urllib.parse.urlparse(self._manifest_url)
610 return parse_results.scheme in secure_schemes
611
612 def _CheckForHookApprovalManifest(self):
613 """Check whether the user has approved this manifest host.
614
615 Returns:
616 True if this hook is approved to run; False otherwise.
617 """
618 return self._CheckForHookApprovalHelper(
619 'approvedmanifest',
620 self._manifest_url,
621 'Run hook scripts from %s' % (self._manifest_url,),
622 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
623
624 def _CheckForHookApprovalHash(self):
625 """Check whether the user has approved the hooks repo.
626
627 Returns:
628 True if this hook is approved to run; False otherwise.
629 """
630 prompt = ('Repo %s run the script:\n'
631 ' %s\n'
632 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700633 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400634 return self._CheckForHookApprovalHelper(
635 'approvedhash',
636 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700637 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400638 'Scripts have changed since %s was allowed.' % (self._hook_type,))
639
Mike Frysingerf7c51602019-06-18 17:23:39 -0400640 @staticmethod
641 def _ExtractInterpFromShebang(data):
642 """Extract the interpreter used in the shebang.
643
644 Try to locate the interpreter the script is using (ignoring `env`).
645
646 Args:
647 data: The file content of the script.
648
649 Returns:
650 The basename of the main script interpreter, or None if a shebang is not
651 used or could not be parsed out.
652 """
653 firstline = data.splitlines()[:1]
654 if not firstline:
655 return None
656
657 # The format here can be tricky.
658 shebang = firstline[0].strip()
659 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
660 if not m:
661 return None
662
663 # If the using `env`, find the target program.
664 interp = m.group(1)
665 if os.path.basename(interp) == 'env':
666 interp = m.group(2)
667
668 return interp
669
670 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
671 """Execute the hook script through |interp|.
672
673 Note: Support for this feature should be dropped ~Jun 2021.
674
675 Args:
676 interp: The Python program to run.
677 context: Basic Python context to execute the hook inside.
678 kwargs: Arbitrary arguments to pass to the hook script.
679
680 Raises:
681 HookError: When the hooks failed for any reason.
682 """
683 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
684 script = """
685import json, os, sys
686path = '''%(path)s'''
687kwargs = json.loads('''%(kwargs)s''')
688context = json.loads('''%(context)s''')
689sys.path.insert(0, os.path.dirname(path))
690data = open(path).read()
691exec(compile(data, path, 'exec'), context)
692context['main'](**kwargs)
693""" % {
694 'path': self._script_fullpath,
695 'kwargs': json.dumps(kwargs),
696 'context': json.dumps(context),
697 }
698
699 # We pass the script via stdin to avoid OS argv limits. It also makes
700 # unhandled exception tracebacks less verbose/confusing for users.
701 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
702 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
703 proc.communicate(input=script.encode('utf-8'))
704 if proc.returncode:
705 raise HookError('Failed to run %s hook.' % (self._hook_type,))
706
707 def _ExecuteHookViaImport(self, data, context, **kwargs):
708 """Execute the hook code in |data| directly.
709
710 Args:
711 data: The code of the hook to execute.
712 context: Basic Python context to execute the hook inside.
713 kwargs: Arbitrary arguments to pass to the hook script.
714
715 Raises:
716 HookError: When the hooks failed for any reason.
717 """
718 # Exec, storing global context in the context dict. We catch exceptions
719 # and convert to a HookError w/ just the failing traceback.
720 try:
721 exec(compile(data, self._script_fullpath, 'exec'), context)
722 except Exception:
723 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
724 (traceback.format_exc(), self._hook_type))
725
726 # Running the script should have defined a main() function.
727 if 'main' not in context:
728 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
729
730 # Call the main function in the hook. If the hook should cause the
731 # build to fail, it will raise an Exception. We'll catch that convert
732 # to a HookError w/ just the failing traceback.
733 try:
734 context['main'](**kwargs)
735 except Exception:
736 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
737 'above.' % (traceback.format_exc(), self._hook_type))
738
Doug Anderson37282b42011-03-04 11:54:18 -0800739 def _ExecuteHook(self, **kwargs):
740 """Actually execute the given hook.
741
742 This will run the hook's 'main' function in our python interpreter.
743
744 Args:
745 kwargs: Keyword arguments to pass to the hook. These are often specific
746 to the hook type. For instance, pre-upload hooks will contain
747 a project_list.
748 """
749 # Keep sys.path and CWD stashed away so that we can always restore them
750 # upon function exit.
751 orig_path = os.getcwd()
752 orig_syspath = sys.path
753
754 try:
755 # Always run hooks with CWD as topdir.
756 os.chdir(self._topdir)
757
758 # Put the hook dir as the first item of sys.path so hooks can do
759 # relative imports. We want to replace the repo dir as [0] so
760 # hooks can't import repo files.
761 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
762
Mike Frysingerf7c51602019-06-18 17:23:39 -0400763 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500764 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800765
Doug Anderson37282b42011-03-04 11:54:18 -0800766 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
767 # We don't actually want hooks to define their main with this argument--
768 # it's there to remind them that their hook should always take **kwargs.
769 # For instance, a pre-upload hook should be defined like:
770 # def main(project_list, **kwargs):
771 #
772 # This allows us to later expand the API without breaking old hooks.
773 kwargs = kwargs.copy()
774 kwargs['hook_should_take_kwargs'] = True
775
Mike Frysingerf7c51602019-06-18 17:23:39 -0400776 # See what version of python the hook has been written against.
777 data = open(self._script_fullpath).read()
778 interp = self._ExtractInterpFromShebang(data)
779 reexec = False
780 if interp:
781 prog = os.path.basename(interp)
782 if prog.startswith('python2') and sys.version_info.major != 2:
783 reexec = True
784 elif prog.startswith('python3') and sys.version_info.major == 2:
785 reexec = True
786
787 # Attempt to execute the hooks through the requested version of Python.
788 if reexec:
789 try:
790 self._ExecuteHookViaReexec(interp, context, **kwargs)
791 except OSError as e:
792 if e.errno == errno.ENOENT:
793 # We couldn't find the interpreter, so fallback to importing.
794 reexec = False
795 else:
796 raise
797
798 # Run the hook by importing directly.
799 if not reexec:
800 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800801 finally:
802 # Restore sys.path and CWD.
803 sys.path = orig_syspath
804 os.chdir(orig_path)
805
806 def Run(self, user_allows_all_hooks, **kwargs):
807 """Run the hook.
808
809 If the hook doesn't exist (because there is no hooks project or because
810 this particular hook is not enabled), this is a no-op.
811
812 Args:
813 user_allows_all_hooks: If True, we will never prompt about running the
814 hook--we'll just assume it's OK to run it.
815 kwargs: Keyword arguments to pass to the hook. These are often specific
816 to the hook type. For instance, pre-upload hooks will contain
817 a project_list.
818
819 Raises:
820 HookError: If there was a problem finding the hook or the user declined
821 to run a required hook (from _CheckForHookApproval).
822 """
823 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700824 if ((not self._hooks_project) or (self._hook_type not in
825 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800826 return
827
828 # Bail with a nice error if we can't find the hook.
829 if not os.path.isfile(self._script_fullpath):
830 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
831
832 # Make sure the user is OK with running the hook.
833 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
834 return
835
836 # Run the hook with the same version of python we're using.
837 self._ExecuteHook(**kwargs)
838
839
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700840class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600841 # These objects can be shared between several working trees.
842 shareable_files = ['description', 'info']
843 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
844 # These objects can only be used by a single working tree.
845 working_tree_files = ['config', 'packed-refs', 'shallow']
846 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700847
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848 def __init__(self,
849 manifest,
850 name,
851 remote,
852 gitdir,
David James8d201162013-10-11 17:03:19 -0700853 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700854 worktree,
855 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700856 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800857 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100858 rebase=True,
859 groups=None,
860 sync_c=False,
861 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900862 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100863 clone_depth=None,
864 upstream=None,
865 parent=None,
866 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900867 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700868 optimized_fetch=False,
869 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800870 """Init a Project object.
871
872 Args:
873 manifest: The XmlManifest object.
874 name: The `name` attribute of manifest.xml's project element.
875 remote: RemoteSpec object specifying its remote's properties.
876 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700877 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800878 worktree: Absolute path of git working tree.
879 relpath: Relative path of git working tree to repo's top directory.
880 revisionExpr: The `revision` attribute of manifest.xml's project element.
881 revisionId: git commit id for checking out.
882 rebase: The `rebase` attribute of manifest.xml's project element.
883 groups: The `groups` attribute of manifest.xml's project element.
884 sync_c: The `sync-c` attribute of manifest.xml's project element.
885 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900886 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800887 upstream: The `upstream` attribute of manifest.xml's project element.
888 parent: The parent Project object.
889 is_derived: False if the project was explicitly defined in the manifest;
890 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400891 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900892 optimized_fetch: If True, when a project is set to a sha1 revision, only
893 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700894 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800895 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700896 self.manifest = manifest
897 self.name = name
898 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800899 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700900 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800901 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700902 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800903 else:
904 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700905 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700906 self.revisionExpr = revisionExpr
907
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700908 if revisionId is None \
909 and revisionExpr \
910 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700911 self.revisionId = revisionExpr
912 else:
913 self.revisionId = revisionId
914
Mike Pontillod3153822012-02-28 11:53:24 -0800915 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700916 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700917 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800918 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900919 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900920 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700921 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800922 self.parent = parent
923 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900924 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800925 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800926
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700927 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500929 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500930 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700931 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
932 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700933
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800934 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700935 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800936 else:
937 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700938 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700939 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700940 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400941 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700942 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700943
Doug Anderson37282b42011-03-04 11:54:18 -0800944 # This will be filled in if a project is later identified to be the
945 # project containing repo hooks.
946 self.enabled_repo_hooks = []
947
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700948 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800949 def Derived(self):
950 return self.is_derived
951
952 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700954 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700955
956 @property
957 def CurrentBranch(self):
958 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400959
960 The branch name omits the 'refs/heads/' prefix.
961 None is returned if the project is on a detached HEAD, or if the work_git is
962 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700963 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400964 try:
965 b = self.work_git.GetHead()
966 except NoManifestException:
967 # If the local checkout is in a bad state, don't barf. Let the callers
968 # process this like the head is unreadable.
969 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970 if b.startswith(R_HEADS):
971 return b[len(R_HEADS):]
972 return None
973
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700974 def IsRebaseInProgress(self):
975 w = self.worktree
976 g = os.path.join(w, '.git')
977 return os.path.exists(os.path.join(g, 'rebase-apply')) \
978 or os.path.exists(os.path.join(g, 'rebase-merge')) \
979 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200980
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 def IsDirty(self, consider_untracked=True):
982 """Is the working directory modified in some way?
983 """
984 self.work_git.update_index('-q',
985 '--unmerged',
986 '--ignore-missing',
987 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900988 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989 return True
990 if self.work_git.DiffZ('diff-files'):
991 return True
992 if consider_untracked and self.work_git.LsOthers():
993 return True
994 return False
995
996 _userident_name = None
997 _userident_email = None
998
999 @property
1000 def UserName(self):
1001 """Obtain the user's personal name.
1002 """
1003 if self._userident_name is None:
1004 self._LoadUserIdentity()
1005 return self._userident_name
1006
1007 @property
1008 def UserEmail(self):
1009 """Obtain the user's email address. This is very likely
1010 to be their Gerrit login.
1011 """
1012 if self._userident_email is None:
1013 self._LoadUserIdentity()
1014 return self._userident_email
1015
1016 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +09001017 u = self.bare_git.var('GIT_COMMITTER_IDENT')
1018 m = re.compile("^(.*) <([^>]*)> ").match(u)
1019 if m:
1020 self._userident_name = m.group(1)
1021 self._userident_email = m.group(2)
1022 else:
1023 self._userident_name = ''
1024 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001025
1026 def GetRemote(self, name):
1027 """Get the configuration for a single remote.
1028 """
1029 return self.config.GetRemote(name)
1030
1031 def GetBranch(self, name):
1032 """Get the configuration for a single branch.
1033 """
1034 return self.config.GetBranch(name)
1035
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001036 def GetBranches(self):
1037 """Get all existing local branches.
1038 """
1039 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001040 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001041 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001042
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301043 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001044 if name.startswith(R_HEADS):
1045 name = name[len(R_HEADS):]
1046 b = self.GetBranch(name)
1047 b.current = name == current
1048 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +09001049 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001050 heads[name] = b
1051
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301052 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001053 if name.startswith(R_PUB):
1054 name = name[len(R_PUB):]
1055 b = heads.get(name)
1056 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001057 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -07001058
1059 return heads
1060
Colin Cross5acde752012-03-28 20:15:45 -07001061 def MatchesGroups(self, manifest_groups):
1062 """Returns true if the manifest groups specified at init should cause
1063 this project to be synced.
1064 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -07001065 All projects are implicitly labelled with "all".
Conley Owens971de8e2012-04-16 10:36:08 -07001066
1067 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -07001068 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -07001069 manifest_groups: "-group1,group2"
1070 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -05001071
1072 The special manifest group "default" will match any project that
1073 does not have the special project group "notdefault"
Colin Cross5acde752012-03-28 20:15:45 -07001074 """
David Holmer0a1c6a12012-11-14 19:19:00 -05001075 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001076 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001077 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -05001078 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -07001079
Conley Owens971de8e2012-04-16 10:36:08 -07001080 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001081 for group in expanded_manifest_groups:
1082 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001083 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -07001084 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -07001085 matched = True
Colin Cross5acde752012-03-28 20:15:45 -07001086
Conley Owens971de8e2012-04-16 10:36:08 -07001087 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001089# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001090 def UncommitedFiles(self, get_all=True):
1091 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001093 Args:
1094 get_all: a boolean, if True - get information about all different
1095 uncommitted files. If False - return as soon as any kind of
1096 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001097 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001098 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001099 self.work_git.update_index('-q',
1100 '--unmerged',
1101 '--ignore-missing',
1102 '--refresh')
1103 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001104 details.append("rebase in progress")
1105 if not get_all:
1106 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001107
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001108 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1109 if changes:
1110 details.extend(changes)
1111 if not get_all:
1112 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001113
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001114 changes = self.work_git.DiffZ('diff-files').keys()
1115 if changes:
1116 details.extend(changes)
1117 if not get_all:
1118 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001119
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001120 changes = self.work_git.LsOthers()
1121 if changes:
1122 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001123
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001124 return details
1125
1126 def HasChanges(self):
1127 """Returns true if there are uncommitted changes.
1128 """
1129 if self.UncommitedFiles(get_all=False):
1130 return True
1131 else:
1132 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001133
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001134 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001135 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001136
1137 Args:
Rostislav Krasny0bcc2d22020-01-25 14:49:14 +02001138 output_redir: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001139 quiet: If True then only print the project name. Do not print
1140 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001142 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001143 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001144 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001145 print(file=output_redir)
1146 print('project %s/' % self.relpath, file=output_redir)
1147 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001148 return
1149
1150 self.work_git.update_index('-q',
1151 '--unmerged',
1152 '--ignore-missing',
1153 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001154 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1156 df = self.work_git.DiffZ('diff-files')
1157 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001158 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001159 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001160
1161 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001162 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001163 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001164 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001165
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001166 if quiet:
1167 out.nl()
1168 return 'DIRTY'
1169
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170 branch = self.CurrentBranch
1171 if branch is None:
1172 out.nobranch('(*** NO BRANCH ***)')
1173 else:
1174 out.branch('branch %s', branch)
1175 out.nl()
1176
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001177 if rb:
1178 out.important('prior sync failed; rebase still in progress')
1179 out.nl()
1180
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001181 paths = list()
1182 paths.extend(di.keys())
1183 paths.extend(df.keys())
1184 paths.extend(do)
1185
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301186 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001187 try:
1188 i = di[p]
1189 except KeyError:
1190 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001192 try:
1193 f = df[p]
1194 except KeyError:
1195 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001196
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001197 if i:
1198 i_status = i.status.upper()
1199 else:
1200 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001202 if f:
1203 f_status = f.status.lower()
1204 else:
1205 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001206
1207 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001208 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001209 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210 else:
1211 line = ' %s%s\t%s' % (i_status, f_status, p)
1212
1213 if i and not f:
1214 out.added('%s', line)
1215 elif (i and f) or (not i and f):
1216 out.changed('%s', line)
1217 elif not i and not f:
1218 out.untracked('%s', line)
1219 else:
1220 out.write('%s', line)
1221 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001222
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001223 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224
pelyad67872d2012-03-28 14:49:58 +03001225 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001226 """Prints the status of the repository to stdout.
1227 """
1228 out = DiffColoring(self.config)
1229 cmd = ['diff']
1230 if out.is_on:
1231 cmd.append('--color')
1232 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001233 if absolute_paths:
1234 cmd.append('--src-prefix=a/%s/' % self.relpath)
1235 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001236 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001237 try:
1238 p = GitCommand(self,
1239 cmd,
1240 capture_stdout=True,
1241 capture_stderr=True)
1242 except GitError as e:
1243 out.nl()
1244 out.project('project %s/' % self.relpath)
1245 out.nl()
1246 out.fail('%s', str(e))
1247 out.nl()
1248 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249 has_diff = False
1250 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001251 if not hasattr(line, 'encode'):
1252 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001253 if not has_diff:
1254 out.nl()
1255 out.project('project %s/' % self.relpath)
1256 out.nl()
1257 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001258 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001259 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001260
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001261# Publish / Upload ##
David Pursehouse8a68ff92012-09-24 12:15:13 +09001262 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001263 """Was the branch published (uploaded) for code review?
1264 If so, returns the SHA-1 hash of the last published
1265 state for the branch.
1266 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001267 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001268 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001269 try:
1270 return self.bare_git.rev_parse(key)
1271 except GitError:
1272 return None
1273 else:
1274 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001275 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001276 except KeyError:
1277 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001278
David Pursehouse8a68ff92012-09-24 12:15:13 +09001279 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001280 """Prunes any stale published refs.
1281 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001282 if all_refs is None:
1283 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001284 heads = set()
1285 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301286 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001287 if name.startswith(R_HEADS):
1288 heads.add(name)
1289 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001290 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001291
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301292 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001293 n = name[len(R_PUB):]
1294 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001295 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001296
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001297 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298 """List any branches which can be uploaded for review.
1299 """
1300 heads = {}
1301 pubed = {}
1302
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301303 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001305 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001307 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001308
1309 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301310 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001311 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001312 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001313 if selected_branch and branch != selected_branch:
1314 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001315
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001316 rb = self.GetUploadableBranch(branch)
1317 if rb:
1318 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319 return ready
1320
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001321 def GetUploadableBranch(self, branch_name):
1322 """Get a single uploadable branch, or None.
1323 """
1324 branch = self.GetBranch(branch_name)
1325 base = branch.LocalMerge
1326 if branch.LocalMerge:
1327 rb = ReviewableBranch(self, branch, base)
1328 if rb.commits:
1329 return rb
1330 return None
1331
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001332 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001333 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001334 auto_topic=False,
Bryan Jacobsf609f912013-05-06 13:36:24 -04001335 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001336 private=False,
Vadim Bendebury75bcd242018-10-31 13:48:01 -07001337 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001338 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001339 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001340 validate_certs=True,
1341 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342 """Uploads the named branch for code review.
1343 """
1344 if branch is None:
1345 branch = self.CurrentBranch
1346 if branch is None:
1347 raise GitError('not currently on a branch')
1348
1349 branch = self.GetBranch(branch)
1350 if not branch.LocalMerge:
1351 raise GitError('branch %s does not track a remote' % branch.name)
1352 if not branch.remote.review:
1353 raise GitError('remote %s has no review url' % branch.remote.name)
1354
Bryan Jacobsf609f912013-05-06 13:36:24 -04001355 if dest_branch is None:
1356 dest_branch = self.dest_branch
1357 if dest_branch is None:
1358 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001359 if not dest_branch.startswith(R_HEADS):
1360 dest_branch = R_HEADS + dest_branch
1361
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001362 if not branch.remote.projectname:
1363 branch.remote.projectname = self.name
1364 branch.remote.Save()
1365
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001366 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001367 if url is None:
1368 raise UploadError('review not configured')
1369 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001370
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001371 if url.startswith('ssh://'):
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001372 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001373
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001374 for push_option in (push_options or []):
1375 cmd.append('-o')
1376 cmd.append(push_option)
1377
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001378 cmd.append(url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001379
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001380 if dest_branch.startswith(R_HEADS):
1381 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001382
1383 upload_type = 'for'
1384 if draft:
1385 upload_type = 'drafts'
1386
1387 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1388 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001389 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001390 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001391 opts += ['topic=' + branch.name]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001392
David Pursehousef25a3702018-11-14 19:01:22 -08001393 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001394 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendebury75bcd242018-10-31 13:48:01 -07001395 if notify:
1396 opts += ['notify=' + notify]
Jonathan Nieder81df7e12018-11-05 13:21:52 -08001397 if private:
1398 opts += ['private']
1399 if wip:
1400 opts += ['wip']
1401 if opts:
1402 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001403 cmd.append(ref_spec)
1404
Anthony King7bdac712014-07-16 12:56:40 +01001405 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001406 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
1408 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1409 self.bare_git.UpdateRef(R_PUB + branch.name,
1410 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001411 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001412
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001413# Sync ##
Julien Campergue335f5ef2013-10-16 11:02:35 +02001414 def _ExtractArchive(self, tarpath, path=None):
1415 """Extract the given tar on its current location
1416
1417 Args:
1418 - tarpath: The path to the actual tar file
1419
1420 """
1421 try:
1422 with tarfile.open(tarpath, 'r') as tar:
1423 tar.extractall(path=path)
1424 return True
1425 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001426 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001427 return False
1428
Ningning Xiac2fbc782016-08-22 14:24:39 -07001429 def CachePopulate(self, cache_dir, url):
1430 """Populate cache in the cache_dir.
1431
1432 Args:
1433 cache_dir: Directory to cache git files from Google Storage.
1434 url: Git url of current repository.
1435
1436 Raises:
1437 CacheApplyError if it fails to populate the git cache.
1438 """
1439 cmd = ['cache', 'populate', '--ignore_locks', '-v',
1440 '--cache-dir', cache_dir, url]
1441
1442 if GitCommand(self, cmd, cwd=cache_dir).Wait() != 0:
1443 raise CacheApplyError('Failed to populate cache. cache_dir: %s '
1444 'url: %s' % (cache_dir, url))
1445
1446 def CacheExists(self, cache_dir, url):
1447 """Check the existence of the cache files.
1448
1449 Args:
1450 cache_dir: Directory to cache git files.
1451 url: Git url of current repository.
1452
1453 Raises:
1454 CacheApplyError if the cache files do not exist.
1455 """
1456 cmd = ['cache', 'exists', '--quiet', '--cache-dir', cache_dir, url]
1457
1458 exist = GitCommand(self, cmd, cwd=self.gitdir, capture_stdout=True)
1459 if exist.Wait() != 0:
1460 raise CacheApplyError('Failed to execute git cache exists cmd. '
1461 'cache_dir: %s url: %s' % (cache_dir, url))
1462
1463 if not exist.stdout or not exist.stdout.strip():
1464 raise CacheApplyError('Failed to find cache. cache_dir: %s '
1465 'url: %s' % (cache_dir, url))
1466 return exist.stdout.strip()
1467
1468 def CacheApply(self, cache_dir):
1469 """Apply git cache files populated from Google Storage buckets.
1470
1471 Args:
1472 cache_dir: Directory to cache git files.
1473
1474 Raises:
1475 CacheApplyError if it fails to apply git caches.
1476 """
1477 remote = self.GetRemote(self.remote.name)
1478
1479 self.CachePopulate(cache_dir, remote.url)
1480
1481 mirror_dir = self.CacheExists(cache_dir, remote.url)
1482
1483 refspec = RefSpec(True, 'refs/heads/*',
1484 'refs/remotes/%s/*' % remote.name)
1485
1486 fetch_cache_cmd = ['fetch', mirror_dir, str(refspec)]
1487 if GitCommand(self, fetch_cache_cmd, self.gitdir).Wait() != 0:
1488 raise CacheApplyError('Failed to fetch refs %s from %s' %
1489 (mirror_dir, str(refspec)))
1490
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001491 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001492 quiet=False,
1493 is_new=None,
1494 current_branch_only=False,
1495 force_sync=False,
1496 clone_bundle=True,
1497 no_tags=False,
1498 archive=False,
1499 optimized_fetch=False,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001500 prune=False,
Mike Frysingerde72f6a2018-12-13 03:20:31 -05001501 submodules=False,
Mike Frysinger39ba6312019-07-27 12:45:51 -04001502 clone_filter=None,
Ningning Xiac2fbc782016-08-22 14:24:39 -07001503 cache_dir=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001504 """Perform only the network IO portion of the sync process.
1505 Local working directory/branch state is not affected.
1506 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001507 if archive and not isinstance(self, MetaProject):
1508 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001509 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001510 return False
1511
1512 name = self.relpath.replace('\\', '/')
1513 name = name.replace('/', '_')
1514 tarpath = '%s.tar' % name
1515 topdir = self.manifest.topdir
1516
1517 try:
1518 self._FetchArchive(tarpath, cwd=topdir)
1519 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001520 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001521 return False
1522
1523 # From now on, we only need absolute tarpath
1524 tarpath = os.path.join(topdir, tarpath)
1525
1526 if not self._ExtractArchive(tarpath, path=topdir):
1527 return False
1528 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001529 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001530 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001531 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001532 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001533 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001534 if is_new is None:
1535 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001536 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001537 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001538 else:
1539 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001540 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001541
1542 if is_new:
1543 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1544 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001545 with open(alt) as fd:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001546 # This works for both absolute and relative alternate directories.
1547 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001548 except IOError:
1549 alt_dir = None
1550 else:
1551 alt_dir = None
1552
Ningning Xiac2fbc782016-08-22 14:24:39 -07001553 applied_cache = False
1554 # If cache_dir is provided, and it's a new repository without
1555 # alternative_dir, bootstrap this project repo with the git
1556 # cache files.
1557 if cache_dir is not None and is_new and alt_dir is None:
1558 try:
1559 self.CacheApply(cache_dir)
1560 applied_cache = True
1561 is_new = False
1562 except CacheApplyError as e:
1563 _error('Could not apply git cache: %s', e)
1564 _error('Please check if you have the right GS credentials.')
1565 _error('Please check if the cache files exist in GS.')
1566
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001567 if clone_bundle \
Ningning Xiac2fbc782016-08-22 14:24:39 -07001568 and not applied_cache \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001569 and alt_dir is None \
1570 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001571 is_new = False
1572
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001573 if not current_branch_only:
1574 if self.sync_c:
1575 current_branch_only = True
1576 elif not self.manifest._loaded:
1577 # Manifest cannot check defaults until it syncs.
1578 current_branch_only = False
1579 elif self.manifest.default.sync_c:
1580 current_branch_only = True
1581
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001582 if not no_tags:
1583 if not self.sync_tags:
1584 no_tags = True
1585
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001586 if self.clone_depth:
1587 depth = self.clone_depth
1588 else:
1589 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1590
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001591 need_to_fetch = not (optimized_fetch and
1592 (ID_RE.match(self.revisionExpr) and
Zac Livingstone4332262017-06-16 08:56:09 -06001593 self._CheckForImmutableRevision()))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001594 if (need_to_fetch and
1595 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1596 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001597 no_tags=no_tags, prune=prune, depth=depth,
Xin Li745be2e2019-06-03 11:24:30 -07001598 submodules=submodules, force_sync=force_sync,
1599 clone_filter=clone_filter)):
Anthony King7bdac712014-07-16 12:56:40 +01001600 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001601
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001602 mp = self.manifest.manifestProject
1603 dissociate = mp.config.GetBoolean('repo.dissociate')
1604 if dissociate:
1605 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1606 if os.path.exists(alternates_file):
1607 cmd = ['repack', '-a', '-d']
1608 if GitCommand(self, cmd, bare=True).Wait() != 0:
1609 return False
1610 platform_utils.remove(alternates_file)
1611
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001612 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001613 self._InitMRef()
1614 else:
1615 self._InitMirrorHead()
1616 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001617 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001618 except OSError:
1619 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001620 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001621
1622 def PostRepoUpgrade(self):
1623 self._InitHooks()
1624
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001625 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001626 if self.manifest.isGitcClient:
1627 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001628 for copyfile in self.copyfiles:
1629 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001630 for linkfile in self.linkfiles:
1631 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001632
Julien Camperguedd654222014-01-09 16:21:37 +01001633 def GetCommitRevisionId(self):
1634 """Get revisionId of a commit.
1635
1636 Use this method instead of GetRevisionId to get the id of the commit rather
1637 than the id of the current git object (for example, a tag)
1638
1639 """
1640 if not self.revisionExpr.startswith(R_TAGS):
1641 return self.GetRevisionId(self._allrefs)
1642
1643 try:
1644 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1645 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001646 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1647 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001648
David Pursehouse8a68ff92012-09-24 12:15:13 +09001649 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001650 if self.revisionId:
1651 return self.revisionId
1652
1653 rem = self.GetRemote(self.remote.name)
1654 rev = rem.ToLocal(self.revisionExpr)
1655
David Pursehouse8a68ff92012-09-24 12:15:13 +09001656 if all_refs is not None and rev in all_refs:
1657 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001658
1659 try:
1660 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1661 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001662 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1663 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001664
Martin Kellye4e94d22017-03-21 16:05:12 -07001665 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001666 """Perform only the local IO portion of the sync process.
1667 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001668 """
Mike Frysingerb610b852019-11-11 05:10:03 -05001669 if not os.path.exists(self.gitdir):
1670 syncbuf.fail(self,
1671 'Cannot checkout %s due to missing network sync; Run '
1672 '`repo sync -n %s` first.' %
1673 (self.name, self.name))
1674 return
1675
Martin Kellye4e94d22017-03-21 16:05:12 -07001676 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001677 all_refs = self.bare_ref.all
1678 self.CleanPublishedCache(all_refs)
1679 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001680
David Pursehouse1d947b32012-10-25 12:23:11 +09001681 def _doff():
1682 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001683 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001684
Martin Kellye4e94d22017-03-21 16:05:12 -07001685 def _dosubmodules():
1686 self._SyncSubmodules(quiet=True)
1687
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001688 head = self.work_git.GetHead()
1689 if head.startswith(R_HEADS):
1690 branch = head[len(R_HEADS):]
1691 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001692 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001693 except KeyError:
1694 head = None
1695 else:
1696 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001698 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699 # Currently on a detached HEAD. The user is assumed to
1700 # not have any local modifications worth worrying about.
1701 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001702 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001703 syncbuf.fail(self, _PriorSyncFailedError())
1704 return
1705
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001706 if head == revid:
1707 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001708 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001709 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001710 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001711 # The copy/linkfile config may have changed.
1712 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001713 return
1714 else:
1715 lost = self._revlist(not_rev(revid), HEAD)
1716 if lost:
1717 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001718
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001719 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001720 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001721 if submodules:
1722 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001723 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001724 syncbuf.fail(self, e)
1725 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001726 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001727 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001728
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001729 if head == revid:
1730 # No changes; don't do anything further.
1731 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001732 # The copy/linkfile config may have changed.
1733 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001734 return
1735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001736 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001737
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001738 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001739 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001740 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001741 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001742 syncbuf.info(self,
1743 "leaving %s; does not track upstream",
1744 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001745 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001746 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001747 if submodules:
1748 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001749 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001750 syncbuf.fail(self, e)
1751 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001752 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001753 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001754
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001755 upstream_gain = self._revlist(not_rev(HEAD), revid)
Mike Frysinger2ba5a1e2019-09-23 17:42:23 -04001756
1757 # See if we can perform a fast forward merge. This can happen if our
1758 # branch isn't in the exact same state as we last published.
1759 try:
1760 self.work_git.merge_base('--is-ancestor', HEAD, revid)
1761 # Skip the published logic.
1762 pub = False
1763 except GitError:
1764 pub = self.WasPublished(branch.name, all_refs)
1765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001766 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001767 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768 if not_merged:
1769 if upstream_gain:
1770 # The user has published this branch and some of those
1771 # commits are not yet merged upstream. We do not want
1772 # to rewrite the published commits so we punt.
1773 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001774 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001775 "branch %s is published (but not merged) and is now "
1776 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001777 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001778 elif pub == head:
1779 # All published commits are merged, and thus we are a
1780 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001781 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001782 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001783 if submodules:
1784 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001785 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001786
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001787 # Examine the local commits not in the remote. Find the
1788 # last one attributed to this user, if any.
1789 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001790 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001791 last_mine = None
1792 cnt_mine = 0
1793 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001794 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001795 if committer_email == self.UserEmail:
1796 last_mine = commit_id
1797 cnt_mine += 1
1798
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001799 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001800 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001801
1802 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001803 syncbuf.fail(self, _DirtyError())
1804 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001805
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001806 # If the upstream switched on us, warn the user.
1807 #
1808 if branch.merge != self.revisionExpr:
1809 if branch.merge and self.revisionExpr:
1810 syncbuf.info(self,
1811 'manifest switched %s...%s',
1812 branch.merge,
1813 self.revisionExpr)
1814 elif branch.merge:
1815 syncbuf.info(self,
1816 'manifest no longer tracks %s',
1817 branch.merge)
1818
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001819 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001820 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001821 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001822 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001823 syncbuf.info(self,
1824 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001825 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001826
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001827 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001828 if not ID_RE.match(self.revisionExpr):
1829 # in case of manifest sync the revisionExpr might be a SHA1
1830 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001831 if not branch.merge.startswith('refs/'):
1832 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001833 branch.Save()
1834
Mike Pontillod3153822012-02-28 11:53:24 -08001835 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001836 def _docopyandlink():
1837 self._CopyAndLinkFiles()
1838
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001839 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001840 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001841 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001842 if submodules:
1843 syncbuf.later2(self, _dosubmodules)
1844 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001845 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001846 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001847 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001848 if submodules:
1849 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001850 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001851 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001852 syncbuf.fail(self, e)
1853 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001854 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001855 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001856 if submodules:
1857 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001858
Mike Frysingere6a202f2019-08-02 15:57:57 -04001859 def AddCopyFile(self, src, dest, topdir):
1860 """Mark |src| for copying to |dest| (relative to |topdir|).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861
Mike Frysingere6a202f2019-08-02 15:57:57 -04001862 No filesystem changes occur here. Actual copying happens later on.
1863
1864 Paths should have basic validation run on them before being queued.
1865 Further checking will be handled when the actual copy happens.
1866 """
1867 self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest))
1868
1869 def AddLinkFile(self, src, dest, topdir):
1870 """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|.
1871
1872 No filesystem changes occur here. Actual linking happens later on.
1873
1874 Paths should have basic validation run on them before being queued.
1875 Further checking will be handled when the actual link happens.
1876 """
1877 self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001878
James W. Mills24c13082012-04-12 15:04:13 -05001879 def AddAnnotation(self, name, value, keep):
1880 self.annotations.append(_Annotation(name, value, keep))
1881
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001882 def DownloadPatchSet(self, change_id, patch_id):
1883 """Download a single patch set of a single change to FETCH_HEAD.
1884 """
1885 remote = self.GetRemote(self.remote.name)
1886
1887 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001888 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001889 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001890 if GitCommand(self, cmd, bare=True).Wait() != 0:
1891 return None
1892 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001893 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001894 change_id,
1895 patch_id,
1896 self.bare_git.rev_parse('FETCH_HEAD'))
1897
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001898# Branch Management ##
Mike Frysingerf914edc2020-02-09 03:01:56 -05001899 def GetHeadPath(self):
1900 """Return the full path to the HEAD ref."""
1901 dotgit = os.path.join(self.worktree, '.git')
1902 if os.path.isfile(dotgit):
1903 # Git worktrees use a "gitdir:" syntax to point to the scratch space.
1904 with open(dotgit) as fp:
1905 setting = fp.read()
1906 assert setting.startswith('gitdir:')
1907 gitdir = setting.split(':', 1)[1].strip()
1908 dotgit = os.path.join(self.worktree, gitdir)
1909 return os.path.join(dotgit, HEAD)
1910
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001911 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001912 """Create a new branch off the manifest's revision.
1913 """
Simran Basib9a1b732015-08-20 12:19:28 -07001914 if not branch_merge:
1915 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001916 head = self.work_git.GetHead()
1917 if head == (R_HEADS + name):
1918 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001919
David Pursehouse8a68ff92012-09-24 12:15:13 +09001920 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001921 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001922 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001923 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001924 capture_stdout=True,
1925 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001926
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001927 branch = self.GetBranch(name)
1928 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001929 branch.merge = branch_merge
1930 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1931 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001932
1933 if revision is None:
1934 revid = self.GetRevisionId(all_refs)
1935 else:
1936 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001937
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001938 if head.startswith(R_HEADS):
1939 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001940 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001941 except KeyError:
1942 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001943 if revid and head and revid == head:
1944 ref = os.path.join(self.gitdir, R_HEADS + name)
1945 try:
1946 os.makedirs(os.path.dirname(ref))
1947 except OSError:
1948 pass
1949 _lwrite(ref, '%s\n' % revid)
Mike Frysingerf914edc2020-02-09 03:01:56 -05001950 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001951 branch.Save()
1952 return True
1953
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001954 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001955 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001956 capture_stdout=True,
1957 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001958 branch.Save()
1959 return True
1960 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001961
Wink Saville02d79452009-04-10 13:01:24 -07001962 def CheckoutBranch(self, name):
1963 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001964
1965 Args:
1966 name: The name of the branch to checkout.
1967
1968 Returns:
1969 True if the checkout succeeded; False if it didn't; None if the branch
1970 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001971 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001972 rev = R_HEADS + name
1973 head = self.work_git.GetHead()
1974 if head == rev:
1975 # Already on the branch
1976 #
1977 return True
Wink Saville02d79452009-04-10 13:01:24 -07001978
David Pursehouse8a68ff92012-09-24 12:15:13 +09001979 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001980 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001981 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001982 except KeyError:
1983 # Branch does not exist in this project
1984 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001985 return None
Wink Saville02d79452009-04-10 13:01:24 -07001986
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001987 if head.startswith(R_HEADS):
1988 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001989 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001990 except KeyError:
1991 head = None
1992
1993 if head == revid:
1994 # Same revision; just update HEAD to point to the new
1995 # target branch, but otherwise take no other action.
1996 #
Mike Frysingerf914edc2020-02-09 03:01:56 -05001997 _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name))
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001998 return True
1999
2000 return GitCommand(self,
2001 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01002002 capture_stdout=True,
2003 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07002004
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002005 def AbandonBranch(self, name):
2006 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07002007
2008 Args:
2009 name: The name of the branch to abandon.
2010
2011 Returns:
2012 True if the abandon succeeded; False if it didn't; None if the branch
2013 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002014 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002015 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09002016 all_refs = self.bare_ref.all
2017 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07002018 # Doesn't exist
2019 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002020
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002021 head = self.work_git.GetHead()
2022 if head == rev:
2023 # We can't destroy the branch while we are sitting
2024 # on it. Switch to a detached HEAD.
2025 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09002026 head = all_refs[head]
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002027
David Pursehouse8a68ff92012-09-24 12:15:13 +09002028 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002029 if head == revid:
Mike Frysingerf914edc2020-02-09 03:01:56 -05002030 _lwrite(self.GetHeadPath(), '%s\n' % revid)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002031 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002032 self._Checkout(revid, quiet=True)
Shawn O. Pearce552ac892009-04-18 15:15:24 -07002033
2034 return GitCommand(self,
2035 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01002036 capture_stdout=True,
2037 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08002038
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002039 def PruneHeads(self):
2040 """Prune any topic branches already merged into upstream.
2041 """
2042 cb = self.CurrentBranch
2043 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002044 left = self._allrefs
2045 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002046 if name.startswith(R_HEADS):
2047 name = name[len(R_HEADS):]
2048 if cb is None or name != cb:
2049 kill.append(name)
2050
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002051 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002052 if cb is not None \
2053 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01002054 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002055 self.work_git.DetachHead(HEAD)
2056 kill.append(cb)
2057
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002058 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002059 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002060
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002061 try:
2062 self.bare_git.DetachHead(rev)
2063
2064 b = ['branch', '-d']
2065 b.extend(kill)
2066 b = GitCommand(self, b, bare=True,
2067 capture_stdout=True,
2068 capture_stderr=True)
2069 b.Wait()
2070 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08002071 if ID_RE.match(old):
2072 self.bare_git.DetachHead(old)
2073 else:
2074 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002075 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002076
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08002077 for branch in kill:
2078 if (R_HEADS + branch) not in left:
2079 self.CleanPublishedCache()
2080 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002081
2082 if cb and cb not in kill:
2083 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08002084 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002085
2086 kept = []
2087 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01002088 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002089 branch = self.GetBranch(branch)
2090 base = branch.LocalMerge
2091 if not base:
2092 base = rev
2093 kept.append(ReviewableBranch(self, branch, base))
2094 return kept
2095
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002096# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002097 def GetRegisteredSubprojects(self):
2098 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002099
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002100 def rec(subprojects):
2101 if not subprojects:
2102 return
2103 result.extend(subprojects)
2104 for p in subprojects:
2105 rec(p.subprojects)
2106 rec(self.subprojects)
2107 return result
2108
2109 def _GetSubmodules(self):
2110 # Unfortunately we cannot call `git submodule status --recursive` here
2111 # because the working tree might not exist yet, and it cannot be used
2112 # without a working tree in its current implementation.
2113
2114 def get_submodules(gitdir, rev):
2115 # Parse .gitmodules for submodule sub_paths and sub_urls
2116 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
2117 if not sub_paths:
2118 return []
2119 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
2120 # revision of submodule repository
2121 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
2122 submodules = []
2123 for sub_path, sub_url in zip(sub_paths, sub_urls):
2124 try:
2125 sub_rev = sub_revs[sub_path]
2126 except KeyError:
2127 # Ignore non-exist submodules
2128 continue
2129 submodules.append((sub_rev, sub_path, sub_url))
2130 return submodules
2131
Sebastian Schuberth41a26832019-03-11 13:53:38 +01002132 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
2133 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002134
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002135 def parse_gitmodules(gitdir, rev):
2136 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
2137 try:
Anthony King7bdac712014-07-16 12:56:40 +01002138 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2139 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002140 except GitError:
2141 return [], []
2142 if p.Wait() != 0:
2143 return [], []
2144
2145 gitmodules_lines = []
2146 fd, temp_gitmodules_path = tempfile.mkstemp()
2147 try:
Mike Frysinger163d42e2020-02-11 03:35:24 -05002148 os.write(fd, p.stdout.encode('utf-8'))
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002149 os.close(fd)
2150 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01002151 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2152 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002153 if p.Wait() != 0:
2154 return [], []
2155 gitmodules_lines = p.stdout.split('\n')
2156 except GitError:
2157 return [], []
2158 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08002159 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002160
2161 names = set()
2162 paths = {}
2163 urls = {}
2164 for line in gitmodules_lines:
2165 if not line:
2166 continue
2167 m = re_path.match(line)
2168 if m:
2169 names.add(m.group(1))
2170 paths[m.group(1)] = m.group(2)
2171 continue
2172 m = re_url.match(line)
2173 if m:
2174 names.add(m.group(1))
2175 urls[m.group(1)] = m.group(2)
2176 continue
2177 names = sorted(names)
2178 return ([paths.get(name, '') for name in names],
2179 [urls.get(name, '') for name in names])
2180
2181 def git_ls_tree(gitdir, rev, paths):
2182 cmd = ['ls-tree', rev, '--']
2183 cmd.extend(paths)
2184 try:
Anthony King7bdac712014-07-16 12:56:40 +01002185 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
2186 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002187 except GitError:
2188 return []
2189 if p.Wait() != 0:
2190 return []
2191 objects = {}
2192 for line in p.stdout.split('\n'):
2193 if not line.strip():
2194 continue
2195 object_rev, object_path = line.split()[2:4]
2196 objects[object_path] = object_rev
2197 return objects
2198
2199 try:
2200 rev = self.GetRevisionId()
2201 except GitError:
2202 return []
2203 return get_submodules(self.gitdir, rev)
2204
2205 def GetDerivedSubprojects(self):
2206 result = []
2207 if not self.Exists:
2208 # If git repo does not exist yet, querying its submodules will
2209 # mess up its states; so return here.
2210 return result
2211 for rev, path, url in self._GetSubmodules():
2212 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002213 relpath, worktree, gitdir, objdir = \
2214 self.manifest.GetSubprojectPaths(self, name, path)
2215 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002216 if project:
2217 result.extend(project.GetDerivedSubprojects())
2218 continue
David James8d201162013-10-11 17:03:19 -07002219
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002220 if url.startswith('..'):
2221 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002222 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002223 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002224 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002225 review=self.remote.review,
2226 revision=self.remote.revision)
2227 subproject = Project(manifest=self.manifest,
2228 name=name,
2229 remote=remote,
2230 gitdir=gitdir,
2231 objdir=objdir,
2232 worktree=worktree,
2233 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002234 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002235 revisionId=rev,
2236 rebase=self.rebase,
2237 groups=self.groups,
2238 sync_c=self.sync_c,
2239 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002240 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002241 parent=self,
2242 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002243 result.append(subproject)
2244 result.extend(subproject.GetDerivedSubprojects())
2245 return result
2246
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002247# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002248 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002249 try:
2250 # if revision (sha or tag) is not present then following function
2251 # throws an error.
2252 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2253 return True
2254 except GitError:
2255 # There is no such persistent revision. We have to fetch it.
2256 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002257
Julien Campergue335f5ef2013-10-16 11:02:35 +02002258 def _FetchArchive(self, tarpath, cwd=None):
2259 cmd = ['archive', '-v', '-o', tarpath]
2260 cmd.append('--remote=%s' % self.remote.url)
2261 cmd.append('--prefix=%s/' % self.relpath)
2262 cmd.append(self.revisionExpr)
2263
2264 command = GitCommand(self, cmd, cwd=cwd,
2265 capture_stdout=True,
2266 capture_stderr=True)
2267
2268 if command.Wait() != 0:
2269 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2270
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002271 def _RemoteFetch(self, name=None,
2272 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002273 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002274 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002275 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002276 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002277 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002278 depth=None,
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002279 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002280 force_sync=False,
2281 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002282
2283 is_sha1 = False
2284 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002285 # The depth should not be used when fetching to a mirror because
2286 # it will result in a shallow repository that cannot be cloned or
2287 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002288 # The repo project should also never be synced with partial depth.
2289 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2290 depth = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002291
Shawn Pearce69e04d82014-01-29 12:48:54 -08002292 if depth:
2293 current_branch_only = True
2294
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002295 if ID_RE.match(self.revisionExpr) is not None:
2296 is_sha1 = True
2297
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002298 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002299 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002300 # this is a tag and its sha1 value should never change
2301 tag_name = self.revisionExpr[len(R_TAGS):]
2302
2303 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002304 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002305 if not quiet:
2306 print('Skipped fetching project %s (already have persistent ref)'
2307 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002308 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002309 if is_sha1 and not depth:
2310 # When syncing a specific commit and --depth is not set:
2311 # * if upstream is explicitly specified and is not a sha1, fetch only
2312 # upstream as users expect only upstream to be fetch.
2313 # Note: The commit might not be in upstream in which case the sync
2314 # will fail.
2315 # * otherwise, fetch all branches to make sure we end up with the
2316 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002317 if self.upstream:
2318 current_branch_only = not ID_RE.match(self.upstream)
2319 else:
2320 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002322 if not name:
2323 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002324
2325 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002326 remote = self.GetRemote(name)
2327 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002328 ssh_proxy = True
2329
Shawn O. Pearce88443382010-10-08 10:02:09 +02002330 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002331 if alt_dir and 'objects' == os.path.basename(alt_dir):
2332 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002333 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2334 remote = self.GetRemote(name)
2335
David Pursehouse8a68ff92012-09-24 12:15:13 +09002336 all_refs = self.bare_ref.all
2337 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002338 tmp = set()
2339
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302340 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002341 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002342 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002343 all_refs[r] = ref_id
2344 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002345 continue
2346
David Pursehouse8a68ff92012-09-24 12:15:13 +09002347 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002348 continue
2349
David Pursehouse8a68ff92012-09-24 12:15:13 +09002350 r = 'refs/_alt/%s' % ref_id
2351 all_refs[r] = ref_id
2352 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002353 tmp.add(r)
2354
heping3d7bbc92017-04-12 19:51:47 +08002355 tmp_packed_lines = []
2356 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002357
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302358 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002359 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002360 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002361 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002362 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002363
heping3d7bbc92017-04-12 19:51:47 +08002364 tmp_packed = ''.join(tmp_packed_lines)
2365 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002366 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002367 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002368 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002369
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002370 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002371
Xin Li745be2e2019-06-03 11:24:30 -07002372 if clone_filter:
2373 git_require((2, 19, 0), fail=True, msg='partial clones')
2374 cmd.append('--filter=%s' % clone_filter)
2375 self.config.SetString('extensions.partialclone', self.remote.name)
2376
Conley Owensf97e8382015-01-21 11:12:46 -08002377 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002378 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002379 else:
2380 # If this repo has shallow objects, then we don't know which refs have
2381 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2382 # do this with projects that don't have shallow objects, since it is less
2383 # efficient.
2384 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2385 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002386
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002387 if quiet:
2388 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002389 if not self.worktree:
2390 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002391 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002392
Mike Frysinger9d0e84c2019-03-18 21:27:54 -04002393 if force_sync:
2394 cmd.append('--force')
2395
David Pursehouse74cfd272015-10-14 10:50:15 +09002396 if prune:
2397 cmd.append('--prune')
2398
Martin Kellye4e94d22017-03-21 16:05:12 -07002399 if submodules:
2400 cmd.append('--recurse-submodules=on-demand')
2401
Kuang-che Wu6856f982019-11-25 12:37:55 +08002402 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002403 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002404 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002405 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002406 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002407 spec.append('tag')
2408 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002409
Chirayu Desaif7b64e32020-02-04 17:50:57 +05302410 if self.manifest.IsMirror and not current_branch_only:
2411 branch = None
2412 else:
2413 branch = self.revisionExpr
Kuang-che Wu6856f982019-11-25 12:37:55 +08002414 if (not self.manifest.IsMirror and is_sha1 and depth
David Pursehouseabdf7502020-02-12 14:58:39 +09002415 and git_require((1, 8, 3))):
Kuang-che Wu6856f982019-11-25 12:37:55 +08002416 # Shallow checkout of a specific commit, fetch from that commit and not
2417 # the heads only as the commit might be deeper in the history.
2418 spec.append(branch)
2419 else:
2420 if is_sha1:
2421 branch = self.upstream
2422 if branch is not None and branch.strip():
2423 if not branch.startswith('refs/'):
2424 branch = R_HEADS + branch
2425 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
2426
2427 # If mirroring repo and we cannot deduce the tag or branch to fetch, fetch
2428 # whole repo.
2429 if self.manifest.IsMirror and not spec:
2430 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
2431
2432 # If using depth then we should not get all the tags since they may
2433 # be outside of the depth.
2434 if no_tags or depth:
2435 cmd.append('--no-tags')
2436 else:
2437 cmd.append('--tags')
2438 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
2439
Conley Owens80b87fe2014-05-09 17:13:44 -07002440 cmd.extend(spec)
2441
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002442 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002443 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002444 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002445 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002446 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002447 ok = True
2448 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002449 # If needed, run the 'git remote prune' the first time through the loop
2450 elif (not _i and
2451 "error:" in gitcmd.stderr and
2452 "git remote prune" in gitcmd.stderr):
2453 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002454 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002455 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002456 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002457 break
2458 continue
Brian Harring14a66742012-09-28 20:21:57 -07002459 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002460 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2461 # in sha1 mode, we just tried sync'ing from the upstream field; it
2462 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002463 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002464 elif ret < 0:
2465 # Git died with a signal, exit immediately
2466 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002467 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002468
2469 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002470 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002471 if old_packed != '':
2472 _lwrite(packed_refs, old_packed)
2473 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002474 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002475 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002476
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002477 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002478 # We just synced the upstream given branch; verify we
2479 # got what we wanted, else trigger a second run of all
2480 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002481 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002482 if current_branch_only and depth:
2483 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002484 return self._RemoteFetch(name=name,
2485 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002486 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002487 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002488 else:
2489 # Avoid infinite recursion: sync all branches with depth set to None
2490 return self._RemoteFetch(name=name, current_branch_only=False,
2491 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002492 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002493
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002494 return ok
Shawn O. Pearce88443382010-10-08 10:02:09 +02002495
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002496 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002497 if initial and \
2498 (self.manifest.manifestProject.config.GetString('repo.depth') or
2499 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002500 return False
2501
2502 remote = self.GetRemote(self.remote.name)
2503 bundle_url = remote.url + '/clone.bundle'
2504 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002505 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2506 'persistent-http',
2507 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002508 return False
2509
2510 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2511 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
2512
2513 exist_dst = os.path.exists(bundle_dst)
2514 exist_tmp = os.path.exists(bundle_tmp)
2515
2516 if not initial and not exist_dst and not exist_tmp:
2517 return False
2518
2519 if not exist_dst:
2520 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2521 if not exist_dst:
2522 return False
2523
2524 cmd = ['fetch']
2525 if quiet:
2526 cmd.append('--quiet')
2527 if not self.worktree:
2528 cmd.append('--update-head-ok')
2529 cmd.append(bundle_dst)
2530 for f in remote.fetch:
2531 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002532 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002533
2534 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002535 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002536 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002537 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002538 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002539 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002540
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002541 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002542 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002543 platform_utils.remove(dstPath)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002544
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002545 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002546 if quiet:
2547 cmd += ['--silent']
2548 if os.path.exists(tmpPath):
2549 size = os.stat(tmpPath).st_size
2550 if size >= 1024:
2551 cmd += ['--continue-at', '%d' % (size,)]
2552 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002553 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002554 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002555 if cookiefile:
Mike Frysingerdc1d0e02020-02-09 16:20:06 -05002556 cmd += ['--cookie', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002557 if proxy:
2558 cmd += ['--proxy', proxy]
2559 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2560 cmd += ['--proxy', os.environ['http_proxy']]
2561 if srcUrl.startswith('persistent-https'):
2562 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2563 elif srcUrl.startswith('persistent-http'):
2564 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002565 cmd += [srcUrl]
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002566
Dave Borowitz137d0132015-01-02 11:12:54 -08002567 if IsTrace():
2568 Trace('%s', ' '.join(cmd))
2569 try:
2570 proc = subprocess.Popen(cmd)
2571 except OSError:
2572 return False
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002573
Dave Borowitz137d0132015-01-02 11:12:54 -08002574 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002575
Dave Borowitz137d0132015-01-02 11:12:54 -08002576 if curlret == 22:
2577 # From curl man page:
2578 # 22: HTTP page not retrieved. The requested url was not found or
2579 # returned another error with the HTTP error code being 400 or above.
2580 # This return code only appears if -f, --fail is used.
2581 if not quiet:
2582 print("Server does not provide clone.bundle; ignoring.",
2583 file=sys.stderr)
2584 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002585
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002586 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002587 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002588 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002589 return True
2590 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002591 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002592 return False
2593 else:
2594 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002595
Kris Giesingc8d882a2014-12-23 13:02:32 -08002596 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002597 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002598 with open(path, 'rb') as f:
2599 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002600 return True
2601 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002602 if not quiet:
2603 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002604 return False
2605 except OSError:
2606 return False
2607
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 def _Checkout(self, rev, quiet=False):
2609 cmd = ['checkout']
2610 if quiet:
2611 cmd.append('-q')
2612 cmd.append(rev)
2613 cmd.append('--')
2614 if GitCommand(self, cmd).Wait() != 0:
2615 if self._allrefs:
2616 raise GitError('%s checkout %s ' % (self.name, rev))
2617
Anthony King7bdac712014-07-16 12:56:40 +01002618 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002619 cmd = ['cherry-pick']
2620 cmd.append(rev)
2621 cmd.append('--')
2622 if GitCommand(self, cmd).Wait() != 0:
2623 if self._allrefs:
2624 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2625
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302626 def _LsRemote(self, refs):
2627 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302628 p = GitCommand(self, cmd, capture_stdout=True)
2629 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002630 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302631 return None
2632
Anthony King7bdac712014-07-16 12:56:40 +01002633 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002634 cmd = ['revert']
2635 cmd.append('--no-edit')
2636 cmd.append(rev)
2637 cmd.append('--')
2638 if GitCommand(self, cmd).Wait() != 0:
2639 if self._allrefs:
2640 raise GitError('%s revert %s ' % (self.name, rev))
2641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002642 def _ResetHard(self, rev, quiet=True):
2643 cmd = ['reset', '--hard']
2644 if quiet:
2645 cmd.append('-q')
2646 cmd.append(rev)
2647 if GitCommand(self, cmd).Wait() != 0:
2648 raise GitError('%s reset --hard %s ' % (self.name, rev))
2649
Martin Kellye4e94d22017-03-21 16:05:12 -07002650 def _SyncSubmodules(self, quiet=True):
2651 cmd = ['submodule', 'update', '--init', '--recursive']
2652 if quiet:
2653 cmd.append('-q')
2654 if GitCommand(self, cmd).Wait() != 0:
2655 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2656
Anthony King7bdac712014-07-16 12:56:40 +01002657 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002658 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002659 if onto is not None:
2660 cmd.extend(['--onto', onto])
2661 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002662 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002663 raise GitError('%s rebase %s ' % (self.name, upstream))
2664
Pierre Tardy3d125942012-05-04 12:18:12 +02002665 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002666 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002667 if ffonly:
2668 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002669 if GitCommand(self, cmd).Wait() != 0:
2670 raise GitError('%s merge %s ' % (self.name, head))
2671
Kevin Degiabaa7f32014-11-12 11:27:45 -07002672 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002673 init_git_dir = not os.path.exists(self.gitdir)
2674 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002675 try:
2676 # Initialize the bare repository, which contains all of the objects.
2677 if init_obj_dir:
2678 os.makedirs(self.objdir)
2679 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002680
Kevin Degib1a07b82015-07-27 13:33:43 -06002681 # If we have a separate directory to hold refs, initialize it as well.
2682 if self.objdir != self.gitdir:
2683 if init_git_dir:
2684 os.makedirs(self.gitdir)
2685
2686 if init_obj_dir or init_git_dir:
2687 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2688 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002689 try:
2690 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2691 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002692 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002693 print("Retrying clone after deleting %s" %
2694 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002695 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002696 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2697 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002698 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002699 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002700 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
David Pursehouse145e35b2020-02-12 15:40:47 +09002701 except Exception:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002702 raise e
2703 raise e
Kevin Degib1a07b82015-07-27 13:33:43 -06002704
Kevin Degi384b3c52014-10-16 16:02:58 -06002705 if init_git_dir:
Kevin Degib1a07b82015-07-27 13:33:43 -06002706 mp = self.manifest.manifestProject
2707 ref_dir = mp.config.GetString('repo.reference') or ''
Kevin Degi384b3c52014-10-16 16:02:58 -06002708
Kevin Degib1a07b82015-07-27 13:33:43 -06002709 if ref_dir or mirror_git:
2710 if not mirror_git:
2711 mirror_git = os.path.join(ref_dir, self.name + '.git')
2712 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2713 self.relpath + '.git')
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002714
Kevin Degib1a07b82015-07-27 13:33:43 -06002715 if os.path.exists(mirror_git):
2716 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002717
Kevin Degib1a07b82015-07-27 13:33:43 -06002718 elif os.path.exists(repo_git):
2719 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002720
Kevin Degib1a07b82015-07-27 13:33:43 -06002721 else:
2722 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002723
Kevin Degib1a07b82015-07-27 13:33:43 -06002724 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002725 if not os.path.isabs(ref_dir):
2726 # The alternate directory is relative to the object database.
2727 ref_dir = os.path.relpath(ref_dir,
2728 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002729 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2730 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002731
Kevin Degib1a07b82015-07-27 13:33:43 -06002732 self._UpdateHooks()
2733
2734 m = self.manifest.manifestProject.config
2735 for key in ['user.name', 'user.email']:
2736 if m.Has(key, include_defaults=False):
2737 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002738 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002739 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002740 if self.manifest.IsMirror:
2741 self.config.SetString('core.bare', 'true')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002742 else:
Kevin Degib1a07b82015-07-27 13:33:43 -06002743 self.config.SetString('core.bare', None)
2744 except Exception:
2745 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002746 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002747 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002748 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002749 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002750
Jimmie Westera0444582012-10-24 13:44:42 +02002751 def _UpdateHooks(self):
2752 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002753 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002754
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002755 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002756 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002757 if not os.path.exists(hooks):
2758 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002759 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002760 name = os.path.basename(stock_hook)
2761
Victor Boivie65e0f352011-04-18 11:23:29 +02002762 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002763 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002764 # Don't install a Gerrit Code Review hook if this
2765 # project does not appear to use it for reviews.
2766 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002767 # Since the manifest project is one of those, but also
2768 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002769 continue
2770
2771 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002772 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002773 continue
2774 if os.path.exists(dst):
2775 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002776 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002777 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002778 _warn("%s: Not replacing locally modified %s hook",
2779 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002780 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002781 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002782 platform_utils.symlink(
2783 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002784 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002785 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002786 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002787 else:
2788 raise
2789
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002790 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002791 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002792 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002793 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002794 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002795 remote.review = self.remote.review
2796 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002797
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002798 if self.worktree:
2799 remote.ResetFetch(mirror=False)
2800 else:
2801 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002802 remote.Save()
2803
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002804 def _InitMRef(self):
2805 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002806 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002807
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002808 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002809 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002810
2811 def _InitAnyMRef(self, ref):
2812 cur = self.bare_ref.symref(ref)
2813
2814 if self.revisionId:
2815 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2816 msg = 'manifest set to %s' % self.revisionId
2817 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002818 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002819 else:
2820 remote = self.GetRemote(self.remote.name)
2821 dst = remote.ToLocal(self.revisionExpr)
2822 if cur != dst:
2823 msg = 'manifest set to %s' % self.revisionExpr
2824 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002825
Kevin Degi384b3c52014-10-16 16:02:58 -06002826 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002827 symlink_files = self.shareable_files[:]
2828 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002829 if share_refs:
2830 symlink_files += self.working_tree_files
2831 symlink_dirs += self.working_tree_dirs
2832 to_symlink = symlink_files + symlink_dirs
2833 for name in set(to_symlink):
Mike Frysingered4f2112020-02-11 23:06:29 -05002834 # Try to self-heal a bit in simple cases.
2835 dst_path = os.path.join(destdir, name)
2836 src_path = os.path.join(srcdir, name)
2837
2838 if name in self.working_tree_dirs:
2839 # If the dir is missing under .repo/projects/, create it.
2840 if not os.path.exists(src_path):
2841 os.makedirs(src_path)
2842
2843 elif name in self.working_tree_files:
2844 # If it's a file under the checkout .git/ and the .repo/projects/ has
2845 # nothing, move the file under the .repo/projects/ tree.
2846 if not os.path.exists(src_path) and os.path.isfile(dst_path):
2847 platform_utils.rename(dst_path, src_path)
2848
2849 # If the path exists under the .repo/projects/ and there's no symlink
2850 # under the checkout .git/, recreate the symlink.
2851 if name in self.working_tree_dirs or name in self.working_tree_files:
2852 if os.path.exists(src_path) and not os.path.exists(dst_path):
2853 platform_utils.symlink(
2854 os.path.relpath(src_path, os.path.dirname(dst_path)), dst_path)
2855
2856 dst = platform_utils.realpath(dst_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002857 if os.path.lexists(dst):
Mike Frysingered4f2112020-02-11 23:06:29 -05002858 src = platform_utils.realpath(src_path)
Kevin Degi384b3c52014-10-16 16:02:58 -06002859 # Fail if the links are pointing to the wrong place
2860 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002861 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002862 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002863 'work tree. If you\'re comfortable with the '
2864 'possibility of losing the work tree\'s git metadata,'
2865 ' use `repo sync --force-sync {0}` to '
2866 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002867
David James8d201162013-10-11 17:03:19 -07002868 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2869 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2870
2871 Args:
2872 gitdir: The bare git repository. Must already be initialized.
2873 dotgit: The repository you would like to initialize.
2874 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2875 Only one work tree can store refs under a given |gitdir|.
2876 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2877 This saves you the effort of initializing |dotgit| yourself.
2878 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002879 symlink_files = self.shareable_files[:]
2880 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002881 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002882 symlink_files += self.working_tree_files
2883 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002884 to_symlink = symlink_files + symlink_dirs
2885
2886 to_copy = []
2887 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002888 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002889
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002890 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002891 for name in set(to_copy).union(to_symlink):
2892 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002893 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002894 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002895
Kevin Degi384b3c52014-10-16 16:02:58 -06002896 if os.path.lexists(dst):
2897 continue
David James8d201162013-10-11 17:03:19 -07002898
2899 # If the source dir doesn't exist, create an empty dir.
2900 if name in symlink_dirs and not os.path.lexists(src):
2901 os.makedirs(src)
2902
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002903 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002904 platform_utils.symlink(
2905 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002906 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002907 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002908 shutil.copytree(src, dst)
2909 elif os.path.isfile(src):
2910 shutil.copy(src, dst)
2911
Conley Owens80b87fe2014-05-09 17:13:44 -07002912 # If the source file doesn't exist, ensure the destination
2913 # file doesn't either.
2914 if name in symlink_files and not os.path.lexists(src):
2915 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002916 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002917 except OSError:
2918 pass
2919
David James8d201162013-10-11 17:03:19 -07002920 except OSError as e:
2921 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002922 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002923 else:
2924 raise
2925
Martin Kellye4e94d22017-03-21 16:05:12 -07002926 def _InitWorkTree(self, force_sync=False, submodules=False):
Mike Frysingerf4545122019-11-11 04:34:16 -05002927 realdotgit = os.path.join(self.worktree, '.git')
2928 tmpdotgit = realdotgit + '.tmp'
2929 init_dotgit = not os.path.exists(realdotgit)
2930 if init_dotgit:
2931 dotgit = tmpdotgit
2932 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2933 os.makedirs(tmpdotgit)
2934 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2935 copy_all=False)
2936 else:
2937 dotgit = realdotgit
2938
Kevin Degib1a07b82015-07-27 13:33:43 -06002939 try:
Mike Frysingerf4545122019-11-11 04:34:16 -05002940 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2941 except GitError as e:
2942 if force_sync and not init_dotgit:
2943 try:
2944 platform_utils.rmtree(dotgit)
2945 return self._InitWorkTree(force_sync=False, submodules=submodules)
David Pursehouse145e35b2020-02-12 15:40:47 +09002946 except Exception:
Mike Frysingerf4545122019-11-11 04:34:16 -05002947 raise e
2948 raise e
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002949
Mike Frysingerf4545122019-11-11 04:34:16 -05002950 if init_dotgit:
2951 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId())
Kevin Degi384b3c52014-10-16 16:02:58 -06002952
Mike Frysingerf4545122019-11-11 04:34:16 -05002953 # Now that the .git dir is fully set up, move it to its final home.
2954 platform_utils.rename(tmpdotgit, realdotgit)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002955
Mike Frysingerf4545122019-11-11 04:34:16 -05002956 # Finish checking out the worktree.
2957 cmd = ['read-tree', '--reset', '-u']
2958 cmd.append('-v')
2959 cmd.append(HEAD)
2960 if GitCommand(self, cmd).Wait() != 0:
2961 raise GitError('Cannot initialize work tree for ' + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002962
Mike Frysingerf4545122019-11-11 04:34:16 -05002963 if submodules:
2964 self._SyncSubmodules(quiet=True)
2965 self._CopyAndLinkFiles()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002966
Renaud Paquay788e9622017-01-27 11:41:12 -08002967 def _get_symlink_error_message(self):
2968 if platform_utils.isWindows():
2969 return ('Unable to create symbolic link. Please re-run the command as '
2970 'Administrator, or see '
2971 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2972 'for other options.')
2973 return 'filesystem must support symlinks'
2974
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002975 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002976 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002977
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002978 def _revlist(self, *args, **kw):
2979 a = []
2980 a.extend(args)
2981 a.append('--')
2982 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002983
2984 @property
2985 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002986 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002987
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002988 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002989 """Get logs between two revisions of this project."""
2990 comp = '..'
2991 if rev1:
2992 revs = [rev1]
2993 if rev2:
2994 revs.extend([comp, rev2])
2995 cmd = ['log', ''.join(revs)]
2996 out = DiffColoring(self.config)
2997 if out.is_on and color:
2998 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002999 if pretty_format is not None:
3000 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003001 if oneline:
3002 cmd.append('--oneline')
3003
3004 try:
3005 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
3006 if log.Wait() == 0:
3007 return log.stdout
3008 except GitError:
3009 # worktree may not exist if groups changed for example. In that case,
3010 # try in gitdir instead.
3011 if not os.path.exists(self.worktree):
3012 return self.bare_git.log(*cmd[1:])
3013 else:
3014 raise
3015 return None
3016
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003017 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
3018 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01003019 """Get the list of logs from this revision to given revisionId"""
3020 logs = {}
3021 selfId = self.GetRevisionId(self._allrefs)
3022 toId = toProject.GetRevisionId(toProject._allrefs)
3023
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02003024 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
3025 pretty_format=pretty_format)
3026 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
3027 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01003028 return logs
3029
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003030 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003031
David James8d201162013-10-11 17:03:19 -07003032 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003033 self._project = project
3034 self._bare = bare
David James8d201162013-10-11 17:03:19 -07003035 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003036
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003037 def LsOthers(self):
3038 p = GitCommand(self._project,
3039 ['ls-files',
3040 '-z',
3041 '--others',
3042 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01003043 bare=False,
David James8d201162013-10-11 17:03:19 -07003044 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003045 capture_stdout=True,
3046 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003047 if p.Wait() == 0:
3048 out = p.stdout
3049 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003050 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09003051 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003052 return []
3053
3054 def DiffZ(self, name, *args):
3055 cmd = [name]
3056 cmd.append('-z')
Eli Ribble7b4f0192019-05-02 18:21:42 -07003057 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003058 cmd.extend(args)
3059 p = GitCommand(self._project,
3060 cmd,
David James8d201162013-10-11 17:03:19 -07003061 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003062 bare=False,
3063 capture_stdout=True,
3064 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003065 try:
3066 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04003067 if not hasattr(out, 'encode'):
3068 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003069 r = {}
3070 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09003071 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003072 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003073 try:
Anthony King2cd1f042014-05-05 21:24:05 +01003074 info = next(out)
3075 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07003076 except StopIteration:
3077 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003078
3079 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003081 def __init__(self, path, omode, nmode, oid, nid, state):
3082 self.path = path
3083 self.src_path = None
3084 self.old_mode = omode
3085 self.new_mode = nmode
3086 self.old_id = oid
3087 self.new_id = nid
3088
3089 if len(state) == 1:
3090 self.status = state
3091 self.level = None
3092 else:
3093 self.status = state[:1]
3094 self.level = state[1:]
3095 while self.level.startswith('0'):
3096 self.level = self.level[1:]
3097
3098 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09003099 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003100 if info.status in ('R', 'C'):
3101 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01003102 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003103 r[info.path] = info
3104 return r
3105 finally:
3106 p.Wait()
3107
3108 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003109 if self._bare:
3110 path = os.path.join(self._project.gitdir, HEAD)
3111 else:
Mike Frysingerf914edc2020-02-09 03:01:56 -05003112 path = self._project.GetHeadPath()
Conley Owens75ee0572012-11-15 17:33:11 -08003113 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05003114 with open(path) as fd:
3115 line = fd.readline()
Dan Sandler53e902a2014-03-09 13:20:02 -04003116 except IOError as e:
3117 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07003118 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303119 line = line.decode()
3120 except AttributeError:
3121 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07003122 if line.startswith('ref: '):
3123 return line[5:-1]
3124 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003125
3126 def SetHead(self, ref, message=None):
3127 cmdv = []
3128 if message is not None:
3129 cmdv.extend(['-m', message])
3130 cmdv.append(HEAD)
3131 cmdv.append(ref)
3132 self.symbolic_ref(*cmdv)
3133
3134 def DetachHead(self, new, message=None):
3135 cmdv = ['--no-deref']
3136 if message is not None:
3137 cmdv.extend(['-m', message])
3138 cmdv.append(HEAD)
3139 cmdv.append(new)
3140 self.update_ref(*cmdv)
3141
3142 def UpdateRef(self, name, new, old=None,
3143 message=None,
3144 detach=False):
3145 cmdv = []
3146 if message is not None:
3147 cmdv.extend(['-m', message])
3148 if detach:
3149 cmdv.append('--no-deref')
3150 cmdv.append(name)
3151 cmdv.append(new)
3152 if old is not None:
3153 cmdv.append(old)
3154 self.update_ref(*cmdv)
3155
3156 def DeleteRef(self, name, old=None):
3157 if not old:
3158 old = self.rev_parse(name)
3159 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07003160 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003161
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07003162 def rev_list(self, *args, **kw):
3163 if 'format' in kw:
3164 cmdv = ['log', '--pretty=format:%s' % kw['format']]
3165 else:
3166 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003167 cmdv.extend(args)
3168 p = GitCommand(self._project,
3169 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003170 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003171 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003172 capture_stdout=True,
3173 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003174 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003175 raise GitError('%s rev-list %s: %s' %
3176 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04003177 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003178
3179 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08003180 """Allow arbitrary git commands using pythonic syntax.
3181
3182 This allows you to do things like:
3183 git_obj.rev_parse('HEAD')
3184
3185 Since we don't have a 'rev_parse' method defined, the __getattr__ will
3186 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07003187 Any other positional arguments will be passed to the git command, and the
3188 following keyword arguments are supported:
3189 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08003190
3191 Args:
3192 name: The name of the git command to call. Any '_' characters will
3193 be replaced with '-'.
3194
3195 Returns:
3196 A callable object that will try to call git with the named command.
3197 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003198 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003199
Dave Borowitz091f8932012-10-23 17:01:04 -07003200 def runner(*args, **kwargs):
3201 cmdv = []
3202 config = kwargs.pop('config', None)
3203 for k in kwargs:
3204 raise TypeError('%s() got an unexpected keyword argument %r'
3205 % (name, k))
3206 if config is not None:
Chirayu Desai217ea7d2013-03-01 19:14:38 +05303207 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07003208 cmdv.append('-c')
3209 cmdv.append('%s=%s' % (k, v))
3210 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003211 cmdv.extend(args)
3212 p = GitCommand(self._project,
3213 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01003214 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07003215 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003216 capture_stdout=True,
3217 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003218 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003219 raise GitError('%s %s: %s' %
3220 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003221 r = p.stdout
3222 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3223 return r[:-1]
3224 return r
3225 return runner
3226
3227
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003228class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003229
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003230 def __str__(self):
3231 return 'prior sync failed; rebase still in progress'
3232
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003233
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003234class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003235
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003236 def __str__(self):
3237 return 'contains uncommitted changes'
3238
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003239
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003240class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003241
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003242 def __init__(self, project, text):
3243 self.project = project
3244 self.text = text
3245
3246 def Print(self, syncbuf):
3247 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3248 syncbuf.out.nl()
3249
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003250
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003251class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003252
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003253 def __init__(self, project, why):
3254 self.project = project
3255 self.why = why
3256
3257 def Print(self, syncbuf):
3258 syncbuf.out.fail('error: %s/: %s',
3259 self.project.relpath,
3260 str(self.why))
3261 syncbuf.out.nl()
3262
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003263
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003264class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003265
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003266 def __init__(self, project, action):
3267 self.project = project
3268 self.action = action
3269
3270 def Run(self, syncbuf):
3271 out = syncbuf.out
3272 out.project('project %s/', self.project.relpath)
3273 out.nl()
3274 try:
3275 self.action()
3276 out.nl()
3277 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003278 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003279 out.nl()
3280 return False
3281
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003282
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003283class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003284
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003285 def __init__(self, config):
3286 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003287 self.project = self.printer('header', attr='bold')
3288 self.info = self.printer('info')
3289 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003290
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003291
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003292class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003293
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003294 def __init__(self, config, detach_head=False):
3295 self._messages = []
3296 self._failures = []
3297 self._later_queue1 = []
3298 self._later_queue2 = []
3299
3300 self.out = _SyncColoring(config)
3301 self.out.redirect(sys.stderr)
3302
3303 self.detach_head = detach_head
3304 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003305 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003306
3307 def info(self, project, fmt, *args):
3308 self._messages.append(_InfoMessage(project, fmt % args))
3309
3310 def fail(self, project, err=None):
3311 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003312 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003313
3314 def later1(self, project, what):
3315 self._later_queue1.append(_Later(project, what))
3316
3317 def later2(self, project, what):
3318 self._later_queue2.append(_Later(project, what))
3319
3320 def Finish(self):
3321 self._PrintMessages()
3322 self._RunLater()
3323 self._PrintMessages()
3324 return self.clean
3325
David Rileye0684ad2017-04-05 00:02:59 -07003326 def Recently(self):
3327 recent_clean = self.recent_clean
3328 self.recent_clean = True
3329 return recent_clean
3330
3331 def _MarkUnclean(self):
3332 self.clean = False
3333 self.recent_clean = False
3334
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003335 def _RunLater(self):
3336 for q in ['_later_queue1', '_later_queue2']:
3337 if not self._RunQueue(q):
3338 return
3339
3340 def _RunQueue(self, queue):
3341 for m in getattr(self, queue):
3342 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003343 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003344 return False
3345 setattr(self, queue, [])
3346 return True
3347
3348 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003349 if self._messages or self._failures:
3350 if os.isatty(2):
3351 self.out.write(progress.CSI_ERASE_LINE)
3352 self.out.write('\r')
3353
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003354 for m in self._messages:
3355 m.Print(self)
3356 for m in self._failures:
3357 m.Print(self)
3358
3359 self._messages = []
3360 self._failures = []
3361
3362
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003363class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003364
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003365 """A special project housed under .repo.
3366 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003367
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003368 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003369 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003370 manifest=manifest,
3371 name=name,
3372 gitdir=gitdir,
3373 objdir=gitdir,
3374 worktree=worktree,
3375 remote=RemoteSpec('origin'),
3376 relpath='.repo/%s' % name,
3377 revisionExpr='refs/heads/master',
3378 revisionId=None,
3379 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003380
3381 def PreSync(self):
3382 if self.Exists:
3383 cb = self.CurrentBranch
3384 if cb:
3385 base = self.GetBranch(cb).merge
3386 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003387 self.revisionExpr = base
3388 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003389
Martin Kelly224a31a2017-07-10 14:46:25 -07003390 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003391 """ Prepare MetaProject for manifest branch switch
3392 """
3393
3394 # detach and delete manifest branch, allowing a new
3395 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003396 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003397 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003398 syncbuf.Finish()
3399
3400 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003401 ['update-ref', '-d', 'refs/heads/default'],
3402 capture_stdout=True,
3403 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003404
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003405 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003406 def LastFetch(self):
3407 try:
3408 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3409 return os.path.getmtime(fh)
3410 except OSError:
3411 return 0
3412
3413 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003414 def HasChanges(self):
3415 """Has the remote received new commits not yet checked out?
3416 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003417 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003418 return False
3419
David Pursehouse8a68ff92012-09-24 12:15:13 +09003420 all_refs = self.bare_ref.all
3421 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003422 head = self.work_git.GetHead()
3423 if head.startswith(R_HEADS):
3424 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003425 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003426 except KeyError:
3427 head = None
3428
3429 if revid == head:
3430 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003431 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003432 return True
3433 return False