blob: 51160a94b95a9cf7bd27a110daab5e59029ebbb5 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
2#
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Shawn O. Pearce438ee1c2008-11-03 09:59:36 -080018import errno
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import filecmp
Wink Saville4c426ef2015-06-03 08:05:17 -070020import glob
Mike Frysingerf7c51602019-06-18 17:23:39 -040021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070023import random
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import re
25import shutil
26import stat
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -070027import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070028import sys
Julien Campergue335f5ef2013-10-16 11:02:35 +020029import tarfile
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +080030import tempfile
Shawn O. Pearcec325dc32011-10-03 08:30:24 -070031import time
Dave Borowitz137d0132015-01-02 11:12:54 -080032import traceback
Shawn O. Pearcedf5ee522011-10-11 14:05:21 -070033
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034from color import Coloring
Dave Borowitzb42b4742012-10-31 12:27:27 -070035from git_command import GitCommand, git_require
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070036from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \
37 ID_RE
Kevin Degiabaa7f32014-11-12 11:27:45 -070038from error import GitError, HookError, UploadError, DownloadError
Shawn O. Pearce559b8462009-03-02 12:56:08 -080039from error import ManifestInvalidRevisionError
Conley Owens75ee0572012-11-15 17:33:11 -080040from error import NoManifestException
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070041import platform_utils
Mike Frysinger70d861f2019-08-26 15:22:36 -040042import progress
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070044
Shawn O. Pearced237b692009-04-17 18:49:50 -070045from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
David Pursehouse59bbb582013-05-17 10:49:33 +090047from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040048if is_python3():
49 import urllib.parse
50else:
51 import imp
52 import urlparse
53 urllib = imp.new_module('urllib')
54 urllib.parse = urlparse
Chirayu Desai217ea7d2013-03-01 19:14:38 +053055 input = raw_input
Chirayu Desai217ea7d2013-03-01 19:14:38 +053056
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070057
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070058def _lwrite(path, content):
59 lock = '%s.lock' % path
60
Chirayu Desai303a82f2014-08-19 22:57:17 +053061 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070062 try:
63 fd.write(content)
64 finally:
65 fd.close()
66
67 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070068 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070069 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080070 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070071 raise
72
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070073
Shawn O. Pearce48244782009-04-16 08:25:57 -070074def _error(fmt, *args):
75 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070076 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070077
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070078
David Pursehousef33929d2015-08-24 14:39:14 +090079def _warn(fmt, *args):
80 msg = fmt % args
81 print('warn: %s' % msg, file=sys.stderr)
82
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070083
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070084def not_rev(r):
85 return '^' + r
86
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070087
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080088def sq(r):
89 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080090
Jonathan Nieder93719792015-03-17 11:29:58 -070091_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070092
93
Jonathan Nieder93719792015-03-17 11:29:58 -070094def _ProjectHooks():
95 """List the hooks present in the 'hooks' directory.
96
97 These hooks are project hooks and are copied to the '.git/hooks' directory
98 of all subprojects.
99
100 This function caches the list of hooks (based on the contents of the
101 'repo/hooks' directory) on the first call.
102
103 Returns:
104 A list of absolute paths to all of the files in the hooks directory.
105 """
106 global _project_hook_list
107 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700108 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700109 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700110 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700111 return _project_hook_list
112
113
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700114class DownloadedChange(object):
115 _commit_cache = None
116
117 def __init__(self, project, base, change_id, ps_id, commit):
118 self.project = project
119 self.base = base
120 self.change_id = change_id
121 self.ps_id = ps_id
122 self.commit = commit
123
124 @property
125 def commits(self):
126 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700127 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
128 '--abbrev-commit',
129 '--pretty=oneline',
130 '--reverse',
131 '--date-order',
132 not_rev(self.base),
133 self.commit,
134 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700135 return self._commit_cache
136
137
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700138class ReviewableBranch(object):
139 _commit_cache = None
140
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:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700153 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
154 '--abbrev-commit',
155 '--pretty=oneline',
156 '--reverse',
157 '--date-order',
158 not_rev(self.base),
159 R_HEADS + self.name,
160 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 return self._commit_cache
162
163 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800164 def unabbrev_commits(self):
165 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700166 for commit in self.project.bare_git.rev_list(not_rev(self.base),
167 R_HEADS + self.name,
168 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800169 r[commit[0:8]] = commit
170 return r
171
172 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700174 return self.project.bare_git.log('--pretty=format:%cd',
175 '-n', '1',
176 R_HEADS + self.name,
177 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700179 def UploadForReview(self, people,
180 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000181 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200182 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700183 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200184 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200185 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800186 validate_certs=True,
187 push_options=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800188 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700189 people,
Brian Harring435370c2012-07-28 15:37:04 -0700190 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000191 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200192 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700193 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200194 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200195 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800196 validate_certs=validate_certs,
197 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700199 def GetPublishedRefs(self):
200 refs = {}
201 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700202 self.branch.remote.SshReviewUrl(self.project.UserEmail),
203 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700204 for line in output.split('\n'):
205 try:
206 (sha, ref) = line.split()
207 refs[sha] = ref
208 except ValueError:
209 pass
210
211 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700213
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700215
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 def __init__(self, config):
217 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100218 self.project = self.printer('header', attr='bold')
219 self.branch = self.printer('header', attr='bold')
220 self.nobranch = self.printer('nobranch', fg='red')
221 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
Anthony King7bdac712014-07-16 12:56:40 +0100223 self.added = self.printer('added', fg='green')
224 self.changed = self.printer('changed', fg='red')
225 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226
227
228class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700229
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230 def __init__(self, config):
231 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100232 self.project = self.printer('header', attr='bold')
Mike Frysinger0a9265e2019-09-30 23:59:27 -0400233 self.fail = self.printer('fail', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700235
Anthony King7bdac712014-07-16 12:56:40 +0100236class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700237
James W. Mills24c13082012-04-12 15:04:13 -0500238 def __init__(self, name, value, keep):
239 self.name = name
240 self.value = value
241 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700243
Anthony King7bdac712014-07-16 12:56:40 +0100244class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700245
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800246 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700247 self.src = src
248 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800249 self.abs_src = abssrc
250 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700251
252 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800253 src = self.abs_src
254 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 # copy file if it does not exist or is out of date
256 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
257 try:
258 # remove existing file first, since it might be read-only
259 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800260 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400261 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200262 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700263 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200264 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700265 shutil.copy(src, dest)
266 # make the file read-only
267 mode = os.stat(dest)[stat.ST_MODE]
268 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
269 os.chmod(dest, mode)
270 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700271 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700272
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700273
Anthony King7bdac712014-07-16 12:56:40 +0100274class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700275
Wink Saville4c426ef2015-06-03 08:05:17 -0700276 def __init__(self, git_worktree, src, dest, relsrc, absdest):
277 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500278 self.src = src
279 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700280 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500281 self.abs_dest = absdest
282
Wink Saville4c426ef2015-06-03 08:05:17 -0700283 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500284 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700285 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500286 try:
287 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800288 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800289 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500290 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700291 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700292 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500293 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700294 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500295 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700296 _error('Cannot link file %s to %s', relSrc, absDest)
297
298 def _Link(self):
299 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
300 on the src linking all of the files in the source in to the destination
301 directory.
302 """
303 # We use the absSrc to handle the situation where the current directory
304 # is not the root of the repo
305 absSrc = os.path.join(self.git_worktree, self.src)
306 if os.path.exists(absSrc):
307 # Entity exists so just a simple one to one link operation
308 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
309 else:
310 # Entity doesn't exist assume there is a wild card
311 absDestDir = self.abs_dest
Renaud Paquaybed8b622018-09-27 10:46:58 -0700312 if os.path.exists(absDestDir) and not platform_utils.isdir(absDestDir):
Wink Saville4c426ef2015-06-03 08:05:17 -0700313 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700314 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700315 else:
316 absSrcFiles = glob.glob(absSrc)
317 for absSrcFile in absSrcFiles:
318 # Create a releative path from source dir to destination dir
319 absSrcDir = os.path.dirname(absSrcFile)
320 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
321
322 # Get the source file name
323 srcFile = os.path.basename(absSrcFile)
324
325 # Now form the final full paths to srcFile. They will be
326 # absolute for the desintaiton and relative for the srouce.
327 absDest = os.path.join(absDestDir, srcFile)
328 relSrc = os.path.join(relSrcDir, srcFile)
329 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500330
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700331
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700332class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700333
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700334 def __init__(self,
335 name,
Anthony King7bdac712014-07-16 12:56:40 +0100336 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700337 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100338 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700339 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700340 orig_name=None,
341 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700342 self.name = name
343 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700344 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700345 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100346 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700347 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700348 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700349
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700350
Doug Anderson37282b42011-03-04 11:54:18 -0800351class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700352
Doug Anderson37282b42011-03-04 11:54:18 -0800353 """A RepoHook contains information about a script to run as a hook.
354
355 Hooks are used to run a python script before running an upload (for instance,
356 to run presubmit checks). Eventually, we may have hooks for other actions.
357
358 This shouldn't be confused with files in the 'repo/hooks' directory. Those
359 files are copied into each '.git/hooks' folder for each project. Repo-level
360 hooks are associated instead with repo actions.
361
362 Hooks are always python. When a hook is run, we will load the hook into the
363 interpreter and execute its main() function.
364 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700365
Doug Anderson37282b42011-03-04 11:54:18 -0800366 def __init__(self,
367 hook_type,
368 hooks_project,
369 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400370 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800371 abort_if_user_denies=False):
372 """RepoHook constructor.
373
374 Params:
375 hook_type: A string representing the type of hook. This is also used
376 to figure out the name of the file containing the hook. For
377 example: 'pre-upload'.
378 hooks_project: The project containing the repo hooks. If you have a
379 manifest, this is manifest.repo_hooks_project. OK if this is None,
380 which will make the hook a no-op.
381 topdir: Repo's top directory (the one containing the .repo directory).
382 Scripts will run with CWD as this directory. If you have a manifest,
383 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400384 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800385 abort_if_user_denies: If True, we'll throw a HookError() if the user
386 doesn't allow us to run the hook.
387 """
388 self._hook_type = hook_type
389 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400390 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800391 self._topdir = topdir
392 self._abort_if_user_denies = abort_if_user_denies
393
394 # Store the full path to the script for convenience.
395 if self._hooks_project:
396 self._script_fullpath = os.path.join(self._hooks_project.worktree,
397 self._hook_type + '.py')
398 else:
399 self._script_fullpath = None
400
401 def _GetHash(self):
402 """Return a hash of the contents of the hooks directory.
403
404 We'll just use git to do this. This hash has the property that if anything
405 changes in the directory we will return a different has.
406
407 SECURITY CONSIDERATION:
408 This hash only represents the contents of files in the hook directory, not
409 any other files imported or called by hooks. Changes to imported files
410 can change the script behavior without affecting the hash.
411
412 Returns:
413 A string representing the hash. This will always be ASCII so that it can
414 be printed to the user easily.
415 """
416 assert self._hooks_project, "Must have hooks to calculate their hash."
417
418 # We will use the work_git object rather than just calling GetRevisionId().
419 # That gives us a hash of the latest checked in version of the files that
420 # the user will actually be executing. Specifically, GetRevisionId()
421 # doesn't appear to change even if a user checks out a different version
422 # of the hooks repo (via git checkout) nor if a user commits their own revs.
423 #
424 # NOTE: Local (non-committed) changes will not be factored into this hash.
425 # I think this is OK, since we're really only worried about warning the user
426 # about upstream changes.
427 return self._hooks_project.work_git.rev_parse('HEAD')
428
429 def _GetMustVerb(self):
430 """Return 'must' if the hook is required; 'should' if not."""
431 if self._abort_if_user_denies:
432 return 'must'
433 else:
434 return 'should'
435
436 def _CheckForHookApproval(self):
437 """Check to see whether this hook has been approved.
438
Mike Frysinger40252c22016-08-15 21:23:44 -0400439 We'll accept approval of manifest URLs if they're using secure transports.
440 This way the user can say they trust the manifest hoster. For insecure
441 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800442
443 Note that we ask permission for each individual hook even though we use
444 the hash of all hooks when detecting changes. We'd like the user to be
445 able to approve / deny each hook individually. We only use the hash of all
446 hooks because there is no other easy way to detect changes to local imports.
447
448 Returns:
449 True if this hook is approved to run; False otherwise.
450
451 Raises:
452 HookError: Raised if the user doesn't approve and abort_if_user_denies
453 was passed to the consturctor.
454 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400455 if self._ManifestUrlHasSecureScheme():
456 return self._CheckForHookApprovalManifest()
457 else:
458 return self._CheckForHookApprovalHash()
459
460 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
461 changed_prompt):
462 """Check for approval for a particular attribute and hook.
463
464 Args:
465 subkey: The git config key under [repo.hooks.<hook_type>] to store the
466 last approved string.
467 new_val: The new value to compare against the last approved one.
468 main_prompt: Message to display to the user to ask for approval.
469 changed_prompt: Message explaining why we're re-asking for approval.
470
471 Returns:
472 True if this hook is approved to run; False otherwise.
Doug Anderson37282b42011-03-04 11:54:18 -0800473
Mike Frysinger40252c22016-08-15 21:23:44 -0400474 Raises:
475 HookError: Raised if the user doesn't approve and abort_if_user_denies
476 was passed to the consturctor.
477 """
478 hooks_config = self._hooks_project.config
479 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800480
Mike Frysinger40252c22016-08-15 21:23:44 -0400481 # Get the last value that the user approved for this hook; may be None.
482 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800483
Mike Frysinger40252c22016-08-15 21:23:44 -0400484 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800485 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400486 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800487 # Approval matched. We're done.
488 return True
489 else:
490 # Give the user a reason why we're prompting, since they last told
491 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400492 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800493 else:
494 prompt = ''
495
496 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
497 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400498 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530499 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900500 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800501
502 # User is doing a one-time approval.
503 if response in ('y', 'yes'):
504 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400505 elif response == 'always':
506 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800507 return True
508
509 # For anything else, we'll assume no approval.
510 if self._abort_if_user_denies:
511 raise HookError('You must allow the %s hook or use --no-verify.' %
512 self._hook_type)
513
514 return False
515
Mike Frysinger40252c22016-08-15 21:23:44 -0400516 def _ManifestUrlHasSecureScheme(self):
517 """Check if the URI for the manifest is a secure transport."""
518 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
519 parse_results = urllib.parse.urlparse(self._manifest_url)
520 return parse_results.scheme in secure_schemes
521
522 def _CheckForHookApprovalManifest(self):
523 """Check whether the user has approved this manifest host.
524
525 Returns:
526 True if this hook is approved to run; False otherwise.
527 """
528 return self._CheckForHookApprovalHelper(
529 'approvedmanifest',
530 self._manifest_url,
531 'Run hook scripts from %s' % (self._manifest_url,),
532 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
533
534 def _CheckForHookApprovalHash(self):
535 """Check whether the user has approved the hooks repo.
536
537 Returns:
538 True if this hook is approved to run; False otherwise.
539 """
540 prompt = ('Repo %s run the script:\n'
541 ' %s\n'
542 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700543 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400544 return self._CheckForHookApprovalHelper(
545 'approvedhash',
546 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700547 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400548 'Scripts have changed since %s was allowed.' % (self._hook_type,))
549
Mike Frysingerf7c51602019-06-18 17:23:39 -0400550 @staticmethod
551 def _ExtractInterpFromShebang(data):
552 """Extract the interpreter used in the shebang.
553
554 Try to locate the interpreter the script is using (ignoring `env`).
555
556 Args:
557 data: The file content of the script.
558
559 Returns:
560 The basename of the main script interpreter, or None if a shebang is not
561 used or could not be parsed out.
562 """
563 firstline = data.splitlines()[:1]
564 if not firstline:
565 return None
566
567 # The format here can be tricky.
568 shebang = firstline[0].strip()
569 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
570 if not m:
571 return None
572
573 # If the using `env`, find the target program.
574 interp = m.group(1)
575 if os.path.basename(interp) == 'env':
576 interp = m.group(2)
577
578 return interp
579
580 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
581 """Execute the hook script through |interp|.
582
583 Note: Support for this feature should be dropped ~Jun 2021.
584
585 Args:
586 interp: The Python program to run.
587 context: Basic Python context to execute the hook inside.
588 kwargs: Arbitrary arguments to pass to the hook script.
589
590 Raises:
591 HookError: When the hooks failed for any reason.
592 """
593 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
594 script = """
595import json, os, sys
596path = '''%(path)s'''
597kwargs = json.loads('''%(kwargs)s''')
598context = json.loads('''%(context)s''')
599sys.path.insert(0, os.path.dirname(path))
600data = open(path).read()
601exec(compile(data, path, 'exec'), context)
602context['main'](**kwargs)
603""" % {
604 'path': self._script_fullpath,
605 'kwargs': json.dumps(kwargs),
606 'context': json.dumps(context),
607 }
608
609 # We pass the script via stdin to avoid OS argv limits. It also makes
610 # unhandled exception tracebacks less verbose/confusing for users.
611 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
612 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
613 proc.communicate(input=script.encode('utf-8'))
614 if proc.returncode:
615 raise HookError('Failed to run %s hook.' % (self._hook_type,))
616
617 def _ExecuteHookViaImport(self, data, context, **kwargs):
618 """Execute the hook code in |data| directly.
619
620 Args:
621 data: The code of the hook to execute.
622 context: Basic Python context to execute the hook inside.
623 kwargs: Arbitrary arguments to pass to the hook script.
624
625 Raises:
626 HookError: When the hooks failed for any reason.
627 """
628 # Exec, storing global context in the context dict. We catch exceptions
629 # and convert to a HookError w/ just the failing traceback.
630 try:
631 exec(compile(data, self._script_fullpath, 'exec'), context)
632 except Exception:
633 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
634 (traceback.format_exc(), self._hook_type))
635
636 # Running the script should have defined a main() function.
637 if 'main' not in context:
638 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
639
640 # Call the main function in the hook. If the hook should cause the
641 # build to fail, it will raise an Exception. We'll catch that convert
642 # to a HookError w/ just the failing traceback.
643 try:
644 context['main'](**kwargs)
645 except Exception:
646 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
647 'above.' % (traceback.format_exc(), self._hook_type))
648
Doug Anderson37282b42011-03-04 11:54:18 -0800649 def _ExecuteHook(self, **kwargs):
650 """Actually execute the given hook.
651
652 This will run the hook's 'main' function in our python interpreter.
653
654 Args:
655 kwargs: Keyword arguments to pass to the hook. These are often specific
656 to the hook type. For instance, pre-upload hooks will contain
657 a project_list.
658 """
659 # Keep sys.path and CWD stashed away so that we can always restore them
660 # upon function exit.
661 orig_path = os.getcwd()
662 orig_syspath = sys.path
663
664 try:
665 # Always run hooks with CWD as topdir.
666 os.chdir(self._topdir)
667
668 # Put the hook dir as the first item of sys.path so hooks can do
669 # relative imports. We want to replace the repo dir as [0] so
670 # hooks can't import repo files.
671 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
672
Mike Frysingerf7c51602019-06-18 17:23:39 -0400673 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500674 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800675
Doug Anderson37282b42011-03-04 11:54:18 -0800676 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
677 # We don't actually want hooks to define their main with this argument--
678 # it's there to remind them that their hook should always take **kwargs.
679 # For instance, a pre-upload hook should be defined like:
680 # def main(project_list, **kwargs):
681 #
682 # This allows us to later expand the API without breaking old hooks.
683 kwargs = kwargs.copy()
684 kwargs['hook_should_take_kwargs'] = True
685
Mike Frysingerf7c51602019-06-18 17:23:39 -0400686 # See what version of python the hook has been written against.
687 data = open(self._script_fullpath).read()
688 interp = self._ExtractInterpFromShebang(data)
689 reexec = False
690 if interp:
691 prog = os.path.basename(interp)
692 if prog.startswith('python2') and sys.version_info.major != 2:
693 reexec = True
694 elif prog.startswith('python3') and sys.version_info.major == 2:
695 reexec = True
696
697 # Attempt to execute the hooks through the requested version of Python.
698 if reexec:
699 try:
700 self._ExecuteHookViaReexec(interp, context, **kwargs)
701 except OSError as e:
702 if e.errno == errno.ENOENT:
703 # We couldn't find the interpreter, so fallback to importing.
704 reexec = False
705 else:
706 raise
707
708 # Run the hook by importing directly.
709 if not reexec:
710 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800711 finally:
712 # Restore sys.path and CWD.
713 sys.path = orig_syspath
714 os.chdir(orig_path)
715
716 def Run(self, user_allows_all_hooks, **kwargs):
717 """Run the hook.
718
719 If the hook doesn't exist (because there is no hooks project or because
720 this particular hook is not enabled), this is a no-op.
721
722 Args:
723 user_allows_all_hooks: If True, we will never prompt about running the
724 hook--we'll just assume it's OK to run it.
725 kwargs: Keyword arguments to pass to the hook. These are often specific
726 to the hook type. For instance, pre-upload hooks will contain
727 a project_list.
728
729 Raises:
730 HookError: If there was a problem finding the hook or the user declined
731 to run a required hook (from _CheckForHookApproval).
732 """
733 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700734 if ((not self._hooks_project) or (self._hook_type not in
735 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800736 return
737
738 # Bail with a nice error if we can't find the hook.
739 if not os.path.isfile(self._script_fullpath):
740 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
741
742 # Make sure the user is OK with running the hook.
743 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
744 return
745
746 # Run the hook with the same version of python we're using.
747 self._ExecuteHook(**kwargs)
748
749
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700750class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600751 # These objects can be shared between several working trees.
752 shareable_files = ['description', 'info']
753 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
754 # These objects can only be used by a single working tree.
755 working_tree_files = ['config', 'packed-refs', 'shallow']
756 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700757
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 def __init__(self,
759 manifest,
760 name,
761 remote,
762 gitdir,
David James8d201162013-10-11 17:03:19 -0700763 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700764 worktree,
765 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700766 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800767 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100768 rebase=True,
769 groups=None,
770 sync_c=False,
771 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900772 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100773 clone_depth=None,
774 upstream=None,
775 parent=None,
776 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900777 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700778 optimized_fetch=False,
779 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800780 """Init a Project object.
781
782 Args:
783 manifest: The XmlManifest object.
784 name: The `name` attribute of manifest.xml's project element.
785 remote: RemoteSpec object specifying its remote's properties.
786 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700787 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800788 worktree: Absolute path of git working tree.
789 relpath: Relative path of git working tree to repo's top directory.
790 revisionExpr: The `revision` attribute of manifest.xml's project element.
791 revisionId: git commit id for checking out.
792 rebase: The `rebase` attribute of manifest.xml's project element.
793 groups: The `groups` attribute of manifest.xml's project element.
794 sync_c: The `sync-c` attribute of manifest.xml's project element.
795 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900796 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800797 upstream: The `upstream` attribute of manifest.xml's project element.
798 parent: The parent Project object.
799 is_derived: False if the project was explicitly defined in the manifest;
800 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400801 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900802 optimized_fetch: If True, when a project is set to a sha1 revision, only
803 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700804 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800805 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700806 self.manifest = manifest
807 self.name = name
808 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800809 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700810 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800811 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700812 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800813 else:
814 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700816 self.revisionExpr = revisionExpr
817
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700818 if revisionId is None \
819 and revisionExpr \
820 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700821 self.revisionId = revisionExpr
822 else:
823 self.revisionId = revisionId
824
Mike Pontillod3153822012-02-28 11:53:24 -0800825 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700826 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700827 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800828 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900829 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900830 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700831 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800832 self.parent = parent
833 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900834 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800835 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800836
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700837 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700838 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500839 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500840 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700841 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
842 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700843
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800844 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700845 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800846 else:
847 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700848 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700849 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700850 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400851 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700852 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700853
Doug Anderson37282b42011-03-04 11:54:18 -0800854 # This will be filled in if a project is later identified to be the
855 # project containing repo hooks.
856 self.enabled_repo_hooks = []
857
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700858 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800859 def Derived(self):
860 return self.is_derived
861
862 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700864 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700865
866 @property
867 def CurrentBranch(self):
868 """Obtain the name of the currently checked out branch.
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400869
870 The branch name omits the 'refs/heads/' prefix.
871 None is returned if the project is on a detached HEAD, or if the work_git is
872 otheriwse inaccessible (e.g. an incomplete sync).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700873 """
Mike Frysingerc8290ad2019-10-01 01:07:11 -0400874 try:
875 b = self.work_git.GetHead()
876 except NoManifestException:
877 # If the local checkout is in a bad state, don't barf. Let the callers
878 # process this like the head is unreadable.
879 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700880 if b.startswith(R_HEADS):
881 return b[len(R_HEADS):]
882 return None
883
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700884 def IsRebaseInProgress(self):
885 w = self.worktree
886 g = os.path.join(w, '.git')
887 return os.path.exists(os.path.join(g, 'rebase-apply')) \
888 or os.path.exists(os.path.join(g, 'rebase-merge')) \
889 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200890
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700891 def IsDirty(self, consider_untracked=True):
892 """Is the working directory modified in some way?
893 """
894 self.work_git.update_index('-q',
895 '--unmerged',
896 '--ignore-missing',
897 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900898 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700899 return True
900 if self.work_git.DiffZ('diff-files'):
901 return True
902 if consider_untracked and self.work_git.LsOthers():
903 return True
904 return False
905
906 _userident_name = None
907 _userident_email = None
908
909 @property
910 def UserName(self):
911 """Obtain the user's personal name.
912 """
913 if self._userident_name is None:
914 self._LoadUserIdentity()
915 return self._userident_name
916
917 @property
918 def UserEmail(self):
919 """Obtain the user's email address. This is very likely
920 to be their Gerrit login.
921 """
922 if self._userident_email is None:
923 self._LoadUserIdentity()
924 return self._userident_email
925
926 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900927 u = self.bare_git.var('GIT_COMMITTER_IDENT')
928 m = re.compile("^(.*) <([^>]*)> ").match(u)
929 if m:
930 self._userident_name = m.group(1)
931 self._userident_email = m.group(2)
932 else:
933 self._userident_name = ''
934 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935
936 def GetRemote(self, name):
937 """Get the configuration for a single remote.
938 """
939 return self.config.GetRemote(name)
940
941 def GetBranch(self, name):
942 """Get the configuration for a single branch.
943 """
944 return self.config.GetBranch(name)
945
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700946 def GetBranches(self):
947 """Get all existing local branches.
948 """
949 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700951 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700952
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530953 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700954 if name.startswith(R_HEADS):
955 name = name[len(R_HEADS):]
956 b = self.GetBranch(name)
957 b.current = name == current
958 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900959 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700960 heads[name] = b
961
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530962 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700963 if name.startswith(R_PUB):
964 name = name[len(R_PUB):]
965 b = heads.get(name)
966 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900967 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700968
969 return heads
970
Colin Cross5acde752012-03-28 20:15:45 -0700971 def MatchesGroups(self, manifest_groups):
972 """Returns true if the manifest groups specified at init should cause
973 this project to be synced.
974 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700975 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700976
Conley Owens971de8e2012-04-16 10:36:08 -0700977 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700978 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700979 manifest_groups: "-group1,group2"
980 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500981
982 The special manifest group "default" will match any project that
983 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700984 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500985 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700986 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700987 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500988 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700989
Conley Owens971de8e2012-04-16 10:36:08 -0700990 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700991 for group in expanded_manifest_groups:
992 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700993 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700994 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700995 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700996
Conley Owens971de8e2012-04-16 10:36:08 -0700997 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700998
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700999# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001000 def UncommitedFiles(self, get_all=True):
1001 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001003 Args:
1004 get_all: a boolean, if True - get information about all different
1005 uncommitted files. If False - return as soon as any kind of
1006 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001007 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001008 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001009 self.work_git.update_index('-q',
1010 '--unmerged',
1011 '--ignore-missing',
1012 '--refresh')
1013 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001014 details.append("rebase in progress")
1015 if not get_all:
1016 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001017
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001018 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1019 if changes:
1020 details.extend(changes)
1021 if not get_all:
1022 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001023
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001024 changes = self.work_git.DiffZ('diff-files').keys()
1025 if changes:
1026 details.extend(changes)
1027 if not get_all:
1028 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001029
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001030 changes = self.work_git.LsOthers()
1031 if changes:
1032 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001033
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001034 return details
1035
1036 def HasChanges(self):
1037 """Returns true if there are uncommitted changes.
1038 """
1039 if self.UncommitedFiles(get_all=False):
1040 return True
1041 else:
1042 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001043
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001044 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001045 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001046
1047 Args:
1048 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001049 quiet: If True then only print the project name. Do not print
1050 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001051 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001052 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001053 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001054 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001055 print(file=output_redir)
1056 print('project %s/' % self.relpath, file=output_redir)
1057 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001058 return
1059
1060 self.work_git.update_index('-q',
1061 '--unmerged',
1062 '--ignore-missing',
1063 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001064 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001065 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1066 df = self.work_git.DiffZ('diff-files')
1067 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001068 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001069 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070
1071 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001072 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001073 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001074 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001076 if quiet:
1077 out.nl()
1078 return 'DIRTY'
1079
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080 branch = self.CurrentBranch
1081 if branch is None:
1082 out.nobranch('(*** NO BRANCH ***)')
1083 else:
1084 out.branch('branch %s', branch)
1085 out.nl()
1086
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001087 if rb:
1088 out.important('prior sync failed; rebase still in progress')
1089 out.nl()
1090
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091 paths = list()
1092 paths.extend(di.keys())
1093 paths.extend(df.keys())
1094 paths.extend(do)
1095
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301096 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001097 try:
1098 i = di[p]
1099 except KeyError:
1100 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001101
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001102 try:
1103 f = df[p]
1104 except KeyError:
1105 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001106
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001107 if i:
1108 i_status = i.status.upper()
1109 else:
1110 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001112 if f:
1113 f_status = f.status.lower()
1114 else:
1115 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
1117 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001118 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001119 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001120 else:
1121 line = ' %s%s\t%s' % (i_status, f_status, p)
1122
1123 if i and not f:
1124 out.added('%s', line)
1125 elif (i and f) or (not i and f):
1126 out.changed('%s', line)
1127 elif not i and not f:
1128 out.untracked('%s', line)
1129 else:
1130 out.write('%s', line)
1131 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001132
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001133 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001134
pelyad67872d2012-03-28 14:49:58 +03001135 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001136 """Prints the status of the repository to stdout.
1137 """
1138 out = DiffColoring(self.config)
1139 cmd = ['diff']
1140 if out.is_on:
1141 cmd.append('--color')
1142 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001143 if absolute_paths:
1144 cmd.append('--src-prefix=a/%s/' % self.relpath)
1145 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 cmd.append('--')
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001147 try:
1148 p = GitCommand(self,
1149 cmd,
1150 capture_stdout=True,
1151 capture_stderr=True)
1152 except GitError as e:
1153 out.nl()
1154 out.project('project %s/' % self.relpath)
1155 out.nl()
1156 out.fail('%s', str(e))
1157 out.nl()
1158 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001159 has_diff = False
1160 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001161 if not hasattr(line, 'encode'):
1162 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001163 if not has_diff:
1164 out.nl()
1165 out.project('project %s/' % self.relpath)
1166 out.nl()
1167 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001168 print(line[:-1])
Mike Frysinger0a9265e2019-09-30 23:59:27 -04001169 return p.Wait() == 0
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001170
1171
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001172# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173
David Pursehouse8a68ff92012-09-24 12:15:13 +09001174 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001175 """Was the branch published (uploaded) for code review?
1176 If so, returns the SHA-1 hash of the last published
1177 state for the branch.
1178 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001179 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001180 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001181 try:
1182 return self.bare_git.rev_parse(key)
1183 except GitError:
1184 return None
1185 else:
1186 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001187 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001188 except KeyError:
1189 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001190
David Pursehouse8a68ff92012-09-24 12:15:13 +09001191 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001192 """Prunes any stale published refs.
1193 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001194 if all_refs is None:
1195 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001196 heads = set()
1197 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301198 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199 if name.startswith(R_HEADS):
1200 heads.add(name)
1201 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001202 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301204 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001205 n = name[len(R_PUB):]
1206 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001207 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001208
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001209 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210 """List any branches which can be uploaded for review.
1211 """
1212 heads = {}
1213 pubed = {}
1214
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301215 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001216 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001217 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001218 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001219 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001220
1221 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301222 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001223 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001224 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001225 if selected_branch and branch != selected_branch:
1226 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001228 rb = self.GetUploadableBranch(branch)
1229 if rb:
1230 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001231 return ready
1232
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001233 def GetUploadableBranch(self, branch_name):
1234 """Get a single uploadable branch, or None.
1235 """
1236 branch = self.GetBranch(branch_name)
1237 base = branch.LocalMerge
1238 if branch.LocalMerge:
1239 rb = ReviewableBranch(self, branch, base)
1240 if rb.commits:
1241 return rb
1242 return None
1243
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001244 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001245 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001246 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001247 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001248 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001249 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001250 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001251 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001252 validate_certs=True,
1253 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254 """Uploads the named branch for code review.
1255 """
1256 if branch is None:
1257 branch = self.CurrentBranch
1258 if branch is None:
1259 raise GitError('not currently on a branch')
1260
1261 branch = self.GetBranch(branch)
1262 if not branch.LocalMerge:
1263 raise GitError('branch %s does not track a remote' % branch.name)
1264 if not branch.remote.review:
1265 raise GitError('remote %s has no review url' % branch.remote.name)
1266
Bryan Jacobsf609f912013-05-06 13:36:24 -04001267 if dest_branch is None:
1268 dest_branch = self.dest_branch
1269 if dest_branch is None:
1270 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001271 if not dest_branch.startswith(R_HEADS):
1272 dest_branch = R_HEADS + dest_branch
1273
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001274 if not branch.remote.projectname:
1275 branch.remote.projectname = self.name
1276 branch.remote.Save()
1277
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001278 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001279 if url is None:
1280 raise UploadError('review not configured')
1281 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001282
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001283 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001284 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001285
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001286 for push_option in (push_options or []):
1287 cmd.append('-o')
1288 cmd.append(push_option)
1289
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001290 cmd.append(url)
1291
1292 if dest_branch.startswith(R_HEADS):
1293 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001294
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001295 upload_type = 'for'
1296 if draft:
1297 upload_type = 'drafts'
1298
1299 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1300 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001301 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001302 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001303 opts += ['topic=' + branch.name]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001304
David Pursehousef25a3702018-11-14 19:01:22 -08001305 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001306 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001307 if notify:
1308 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001309 if private:
1310 opts += ['private']
1311 if wip:
1312 opts += ['wip']
1313 if opts:
1314 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001315 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001316
Anthony King7bdac712014-07-16 12:56:40 +01001317 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001318 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001319
1320 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1321 self.bare_git.UpdateRef(R_PUB + branch.name,
1322 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001323 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
1325
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001326# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001327
Julien Campergue335f5ef2013-10-16 11:02:35 +02001328 def _ExtractArchive(self, tarpath, path=None):
1329 """Extract the given tar on its current location
1330
1331 Args:
1332 - tarpath: The path to the actual tar file
1333
1334 """
1335 try:
1336 with tarfile.open(tarpath, 'r') as tar:
1337 tar.extractall(path=path)
1338 return True
1339 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001340 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001341 return False
1342
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001343 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001344 quiet=False,
1345 is_new=None,
1346 current_branch_only=False,
1347 force_sync=False,
1348 clone_bundle=True,
1349 no_tags=False,
1350 archive=False,
1351 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001352 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001353 submodules=False,
1354 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001355 """Perform only the network IO portion of the sync process.
1356 Local working directory/branch state is not affected.
1357 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001358 if archive and not isinstance(self, MetaProject):
1359 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001360 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001361 return False
1362
1363 name = self.relpath.replace('\\', '/')
1364 name = name.replace('/', '_')
1365 tarpath = '%s.tar' % name
1366 topdir = self.manifest.topdir
1367
1368 try:
1369 self._FetchArchive(tarpath, cwd=topdir)
1370 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001371 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001372 return False
1373
1374 # From now on, we only need absolute tarpath
1375 tarpath = os.path.join(topdir, tarpath)
1376
1377 if not self._ExtractArchive(tarpath, path=topdir):
1378 return False
1379 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001380 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001381 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001382 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001383 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001384 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001385 if is_new is None:
1386 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001387 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001388 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001389 else:
1390 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001391 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001392
1393 if is_new:
1394 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1395 try:
Renaud Paquay2a4be942016-11-01 13:48:15 -07001396 fd = open(alt)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001397 try:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001398 # This works for both absolute and relative alternate directories.
1399 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001400 finally:
1401 fd.close()
1402 except IOError:
1403 alt_dir = None
1404 else:
1405 alt_dir = None
1406
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001407 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001408 and alt_dir is None \
1409 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001410 is_new = False
1411
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001412 if not current_branch_only:
1413 if self.sync_c:
1414 current_branch_only = True
1415 elif not self.manifest._loaded:
1416 # Manifest cannot check defaults until it syncs.
1417 current_branch_only = False
1418 elif self.manifest.default.sync_c:
1419 current_branch_only = True
1420
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001421 if not no_tags:
1422 if not self.sync_tags:
1423 no_tags = True
1424
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001425 if self.clone_depth:
1426 depth = self.clone_depth
1427 else:
1428 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1429
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001430 need_to_fetch = not (optimized_fetch and
1431 (ID_RE.match(self.revisionExpr) and
Zac Livingstone4332262017-06-16 08:56:09 -06001432 self._CheckForImmutableRevision()))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001433 if (need_to_fetch and
1434 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1435 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001436 no_tags=no_tags, prune=prune, depth=depth,
Xin Li745be2e2019-06-03 11:24:30 -07001437 submodules=submodules, force_sync=force_sync,
1438 clone_filter=clone_filter)):
Anthony King7bdac712014-07-16 12:56:40 +01001439 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001440
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001441 mp = self.manifest.manifestProject
1442 dissociate = mp.config.GetBoolean('repo.dissociate')
1443 if dissociate:
1444 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1445 if os.path.exists(alternates_file):
1446 cmd = ['repack', '-a', '-d']
1447 if GitCommand(self, cmd, bare=True).Wait() != 0:
1448 return False
1449 platform_utils.remove(alternates_file)
1450
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001451 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001452 self._InitMRef()
1453 else:
1454 self._InitMirrorHead()
1455 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001456 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001457 except OSError:
1458 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001459 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001460
1461 def PostRepoUpgrade(self):
1462 self._InitHooks()
1463
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001464 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001465 if self.manifest.isGitcClient:
1466 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001467 for copyfile in self.copyfiles:
1468 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001469 for linkfile in self.linkfiles:
1470 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001471
Julien Camperguedd654222014-01-09 16:21:37 +01001472 def GetCommitRevisionId(self):
1473 """Get revisionId of a commit.
1474
1475 Use this method instead of GetRevisionId to get the id of the commit rather
1476 than the id of the current git object (for example, a tag)
1477
1478 """
1479 if not self.revisionExpr.startswith(R_TAGS):
1480 return self.GetRevisionId(self._allrefs)
1481
1482 try:
1483 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1484 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001485 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1486 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001487
David Pursehouse8a68ff92012-09-24 12:15:13 +09001488 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001489 if self.revisionId:
1490 return self.revisionId
1491
1492 rem = self.GetRemote(self.remote.name)
1493 rev = rem.ToLocal(self.revisionExpr)
1494
David Pursehouse8a68ff92012-09-24 12:15:13 +09001495 if all_refs is not None and rev in all_refs:
1496 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001497
1498 try:
1499 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1500 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001501 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1502 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001503
Martin Kellye4e94d22017-03-21 16:05:12 -07001504 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001505 """Perform only the local IO portion of the sync process.
1506 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001507 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001508 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001509 all_refs = self.bare_ref.all
1510 self.CleanPublishedCache(all_refs)
1511 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001512
David Pursehouse1d947b32012-10-25 12:23:11 +09001513 def _doff():
1514 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001515 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001516
Martin Kellye4e94d22017-03-21 16:05:12 -07001517 def _dosubmodules():
1518 self._SyncSubmodules(quiet=True)
1519
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001520 head = self.work_git.GetHead()
1521 if head.startswith(R_HEADS):
1522 branch = head[len(R_HEADS):]
1523 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001524 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001525 except KeyError:
1526 head = None
1527 else:
1528 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001529
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001530 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001531 # Currently on a detached HEAD. The user is assumed to
1532 # not have any local modifications worth worrying about.
1533 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001534 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001535 syncbuf.fail(self, _PriorSyncFailedError())
1536 return
1537
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001538 if head == revid:
1539 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001540 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001541 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001542 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001543 # The copy/linkfile config may have changed.
1544 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001545 return
1546 else:
1547 lost = self._revlist(not_rev(revid), HEAD)
1548 if lost:
1549 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001550
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001552 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001553 if submodules:
1554 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001555 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001556 syncbuf.fail(self, e)
1557 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001558 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001559 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001561 if head == revid:
1562 # No changes; don't do anything further.
1563 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001564 # The copy/linkfile config may have changed.
1565 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001566 return
1567
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001568 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001570 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001571 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001572 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001573 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001574 syncbuf.info(self,
1575 "leaving %s; does not track upstream",
1576 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001577 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001578 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001579 if submodules:
1580 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001581 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001582 syncbuf.fail(self, e)
1583 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001584 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001585 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001586
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001587 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001588 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001589 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001590 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001591 if not_merged:
1592 if upstream_gain:
1593 # The user has published this branch and some of those
1594 # commits are not yet merged upstream. We do not want
1595 # to rewrite the published commits so we punt.
1596 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001597 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001598 "branch %s is published (but not merged) and is now "
1599 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001600 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001601 elif pub == head:
1602 # All published commits are merged, and thus we are a
1603 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001604 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001605 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001606 if submodules:
1607 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001608 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001609
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001610 # Examine the local commits not in the remote. Find the
1611 # last one attributed to this user, if any.
1612 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001613 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001614 last_mine = None
1615 cnt_mine = 0
1616 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001617 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001618 if committer_email == self.UserEmail:
1619 last_mine = commit_id
1620 cnt_mine += 1
1621
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001622 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001623 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001624
1625 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001626 syncbuf.fail(self, _DirtyError())
1627 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001628
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001629 # If the upstream switched on us, warn the user.
1630 #
1631 if branch.merge != self.revisionExpr:
1632 if branch.merge and self.revisionExpr:
1633 syncbuf.info(self,
1634 'manifest switched %s...%s',
1635 branch.merge,
1636 self.revisionExpr)
1637 elif branch.merge:
1638 syncbuf.info(self,
1639 'manifest no longer tracks %s',
1640 branch.merge)
1641
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001642 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001643 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001644 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001645 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001646 syncbuf.info(self,
1647 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001648 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001649
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001650 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001651 if not ID_RE.match(self.revisionExpr):
1652 # in case of manifest sync the revisionExpr might be a SHA1
1653 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001654 if not branch.merge.startswith('refs/'):
1655 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001656 branch.Save()
1657
Mike Pontillod3153822012-02-28 11:53:24 -08001658 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001659 def _docopyandlink():
1660 self._CopyAndLinkFiles()
1661
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001662 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001663 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001664 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001665 if submodules:
1666 syncbuf.later2(self, _dosubmodules)
1667 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001668 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001669 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001670 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001671 if submodules:
1672 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001673 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001674 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001675 syncbuf.fail(self, e)
1676 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001677 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001678 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001679 if submodules:
1680 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001681
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001682 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001683 # dest should already be an absolute path, but src is project relative
1684 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001685 abssrc = os.path.join(self.worktree, src)
1686 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001687
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001688 def AddLinkFile(self, src, dest, absdest):
1689 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001690 # make src relative path to dest
1691 absdestdir = os.path.dirname(absdest)
1692 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001693 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001694
James W. Mills24c13082012-04-12 15:04:13 -05001695 def AddAnnotation(self, name, value, keep):
1696 self.annotations.append(_Annotation(name, value, keep))
1697
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001698 def DownloadPatchSet(self, change_id, patch_id):
1699 """Download a single patch set of a single change to FETCH_HEAD.
1700 """
1701 remote = self.GetRemote(self.remote.name)
1702
1703 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001704 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001705 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001706 if GitCommand(self, cmd, bare=True).Wait() != 0:
1707 return None
1708 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001709 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001710 change_id,
1711 patch_id,
1712 self.bare_git.rev_parse('FETCH_HEAD'))
1713
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001714
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001715# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001716
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001717 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001718 """Create a new branch off the manifest's revision.
1719 """
Simran Basib9a1b732015-08-20 12:19:28 -07001720 if not branch_merge:
1721 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001722 head = self.work_git.GetHead()
1723 if head == (R_HEADS + name):
1724 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001725
David Pursehouse8a68ff92012-09-24 12:15:13 +09001726 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001727 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001728 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001729 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001730 capture_stdout=True,
1731 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001732
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001733 branch = self.GetBranch(name)
1734 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001735 branch.merge = branch_merge
1736 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1737 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001738
1739 if revision is None:
1740 revid = self.GetRevisionId(all_refs)
1741 else:
1742 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001743
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001744 if head.startswith(R_HEADS):
1745 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001746 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001747 except KeyError:
1748 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001749 if revid and head and revid == head:
1750 ref = os.path.join(self.gitdir, R_HEADS + name)
1751 try:
1752 os.makedirs(os.path.dirname(ref))
1753 except OSError:
1754 pass
1755 _lwrite(ref, '%s\n' % revid)
1756 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1757 'ref: %s%s\n' % (R_HEADS, name))
1758 branch.Save()
1759 return True
1760
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001761 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001762 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001763 capture_stdout=True,
1764 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001765 branch.Save()
1766 return True
1767 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001768
Wink Saville02d79452009-04-10 13:01:24 -07001769 def CheckoutBranch(self, name):
1770 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001771
1772 Args:
1773 name: The name of the branch to checkout.
1774
1775 Returns:
1776 True if the checkout succeeded; False if it didn't; None if the branch
1777 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001778 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001779 rev = R_HEADS + name
1780 head = self.work_git.GetHead()
1781 if head == rev:
1782 # Already on the branch
1783 #
1784 return True
Wink Saville02d79452009-04-10 13:01:24 -07001785
David Pursehouse8a68ff92012-09-24 12:15:13 +09001786 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001787 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001788 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001789 except KeyError:
1790 # Branch does not exist in this project
1791 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001792 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001793
1794 if head.startswith(R_HEADS):
1795 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001796 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001797 except KeyError:
1798 head = None
1799
1800 if head == revid:
1801 # Same revision; just update HEAD to point to the new
1802 # target branch, but otherwise take no other action.
1803 #
1804 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1805 'ref: %s%s\n' % (R_HEADS, name))
1806 return True
Wink Saville02d79452009-04-10 13:01:24 -07001807
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001808 return GitCommand(self,
1809 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001810 capture_stdout=True,
1811 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001812
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001813 def AbandonBranch(self, name):
1814 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001815
1816 Args:
1817 name: The name of the branch to abandon.
1818
1819 Returns:
1820 True if the abandon succeeded; False if it didn't; None if the branch
1821 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001822 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001823 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001824 all_refs = self.bare_ref.all
1825 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001826 # Doesn't exist
1827 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001828
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001829 head = self.work_git.GetHead()
1830 if head == rev:
1831 # We can't destroy the branch while we are sitting
1832 # on it. Switch to a detached HEAD.
1833 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001834 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001835
David Pursehouse8a68ff92012-09-24 12:15:13 +09001836 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001837 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001838 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1839 '%s\n' % revid)
1840 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001841 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001842
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001843 return GitCommand(self,
1844 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001845 capture_stdout=True,
1846 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001847
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001848 def PruneHeads(self):
1849 """Prune any topic branches already merged into upstream.
1850 """
1851 cb = self.CurrentBranch
1852 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001853 left = self._allrefs
1854 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001855 if name.startswith(R_HEADS):
1856 name = name[len(R_HEADS):]
1857 if cb is None or name != cb:
1858 kill.append(name)
1859
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001860 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001861 if cb is not None \
1862 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001863 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001864 self.work_git.DetachHead(HEAD)
1865 kill.append(cb)
1866
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001867 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001868 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001869
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001870 try:
1871 self.bare_git.DetachHead(rev)
1872
1873 b = ['branch', '-d']
1874 b.extend(kill)
1875 b = GitCommand(self, b, bare=True,
1876 capture_stdout=True,
1877 capture_stderr=True)
1878 b.Wait()
1879 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001880 if ID_RE.match(old):
1881 self.bare_git.DetachHead(old)
1882 else:
1883 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001884 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001885
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001886 for branch in kill:
1887 if (R_HEADS + branch) not in left:
1888 self.CleanPublishedCache()
1889 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001890
1891 if cb and cb not in kill:
1892 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001893 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001894
1895 kept = []
1896 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001897 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001898 branch = self.GetBranch(branch)
1899 base = branch.LocalMerge
1900 if not base:
1901 base = rev
1902 kept.append(ReviewableBranch(self, branch, base))
1903 return kept
1904
1905
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001906# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001907
1908 def GetRegisteredSubprojects(self):
1909 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001910
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001911 def rec(subprojects):
1912 if not subprojects:
1913 return
1914 result.extend(subprojects)
1915 for p in subprojects:
1916 rec(p.subprojects)
1917 rec(self.subprojects)
1918 return result
1919
1920 def _GetSubmodules(self):
1921 # Unfortunately we cannot call `git submodule status --recursive` here
1922 # because the working tree might not exist yet, and it cannot be used
1923 # without a working tree in its current implementation.
1924
1925 def get_submodules(gitdir, rev):
1926 # Parse .gitmodules for submodule sub_paths and sub_urls
1927 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1928 if not sub_paths:
1929 return []
1930 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1931 # revision of submodule repository
1932 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1933 submodules = []
1934 for sub_path, sub_url in zip(sub_paths, sub_urls):
1935 try:
1936 sub_rev = sub_revs[sub_path]
1937 except KeyError:
1938 # Ignore non-exist submodules
1939 continue
1940 submodules.append((sub_rev, sub_path, sub_url))
1941 return submodules
1942
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001943 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1944 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001945
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001946 def parse_gitmodules(gitdir, rev):
1947 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1948 try:
Anthony King7bdac712014-07-16 12:56:40 +01001949 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1950 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001951 except GitError:
1952 return [], []
1953 if p.Wait() != 0:
1954 return [], []
1955
1956 gitmodules_lines = []
1957 fd, temp_gitmodules_path = tempfile.mkstemp()
1958 try:
1959 os.write(fd, p.stdout)
1960 os.close(fd)
1961 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001962 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1963 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001964 if p.Wait() != 0:
1965 return [], []
1966 gitmodules_lines = p.stdout.split('\n')
1967 except GitError:
1968 return [], []
1969 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001970 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001971
1972 names = set()
1973 paths = {}
1974 urls = {}
1975 for line in gitmodules_lines:
1976 if not line:
1977 continue
1978 m = re_path.match(line)
1979 if m:
1980 names.add(m.group(1))
1981 paths[m.group(1)] = m.group(2)
1982 continue
1983 m = re_url.match(line)
1984 if m:
1985 names.add(m.group(1))
1986 urls[m.group(1)] = m.group(2)
1987 continue
1988 names = sorted(names)
1989 return ([paths.get(name, '') for name in names],
1990 [urls.get(name, '') for name in names])
1991
1992 def git_ls_tree(gitdir, rev, paths):
1993 cmd = ['ls-tree', rev, '--']
1994 cmd.extend(paths)
1995 try:
Anthony King7bdac712014-07-16 12:56:40 +01001996 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1997 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001998 except GitError:
1999 return []
2000 if p.Wait() != 0:
2001 return []
2002 objects = {}
2003 for line in p.stdout.split('\n'):
2004 if not line.strip():
2005 continue
2006 object_rev, object_path = line.split()[2:4]
2007 objects[object_path] = object_rev
2008 return objects
2009
2010 try:
2011 rev = self.GetRevisionId()
2012 except GitError:
2013 return []
2014 return get_submodules(self.gitdir, rev)
2015
2016 def GetDerivedSubprojects(self):
2017 result = []
2018 if not self.Exists:
2019 # If git repo does not exist yet, querying its submodules will
2020 # mess up its states; so return here.
2021 return result
2022 for rev, path, url in self._GetSubmodules():
2023 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002024 relpath, worktree, gitdir, objdir = \
2025 self.manifest.GetSubprojectPaths(self, name, path)
2026 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002027 if project:
2028 result.extend(project.GetDerivedSubprojects())
2029 continue
David James8d201162013-10-11 17:03:19 -07002030
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002031 if url.startswith('..'):
2032 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002033 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002034 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002035 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002036 review=self.remote.review,
2037 revision=self.remote.revision)
2038 subproject = Project(manifest=self.manifest,
2039 name=name,
2040 remote=remote,
2041 gitdir=gitdir,
2042 objdir=objdir,
2043 worktree=worktree,
2044 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002045 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002046 revisionId=rev,
2047 rebase=self.rebase,
2048 groups=self.groups,
2049 sync_c=self.sync_c,
2050 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002051 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002052 parent=self,
2053 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002054 result.append(subproject)
2055 result.extend(subproject.GetDerivedSubprojects())
2056 return result
2057
2058
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002059# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002060 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002061 try:
2062 # if revision (sha or tag) is not present then following function
2063 # throws an error.
2064 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2065 return True
2066 except GitError:
2067 # There is no such persistent revision. We have to fetch it.
2068 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002069
Julien Campergue335f5ef2013-10-16 11:02:35 +02002070 def _FetchArchive(self, tarpath, cwd=None):
2071 cmd = ['archive', '-v', '-o', tarpath]
2072 cmd.append('--remote=%s' % self.remote.url)
2073 cmd.append('--prefix=%s/' % self.relpath)
2074 cmd.append(self.revisionExpr)
2075
2076 command = GitCommand(self, cmd, cwd=cwd,
2077 capture_stdout=True,
2078 capture_stderr=True)
2079
2080 if command.Wait() != 0:
2081 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2082
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002083 def _RemoteFetch(self, name=None,
2084 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002085 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002086 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002087 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002088 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002089 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002090 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002091 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002092 force_sync=False,
2093 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002094
2095 is_sha1 = False
2096 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002097 # The depth should not be used when fetching to a mirror because
2098 # it will result in a shallow repository that cannot be cloned or
2099 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002100 # The repo project should also never be synced with partial depth.
2101 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2102 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002103
Shawn Pearce69e04d82014-01-29 12:48:54 -08002104 if depth:
2105 current_branch_only = True
2106
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002107 if ID_RE.match(self.revisionExpr) is not None:
2108 is_sha1 = True
2109
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002110 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002111 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002112 # this is a tag and its sha1 value should never change
2113 tag_name = self.revisionExpr[len(R_TAGS):]
2114
2115 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002116 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002117 if not quiet:
2118 print('Skipped fetching project %s (already have persistent ref)'
2119 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002120 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002121 if is_sha1 and not depth:
2122 # When syncing a specific commit and --depth is not set:
2123 # * if upstream is explicitly specified and is not a sha1, fetch only
2124 # upstream as users expect only upstream to be fetch.
2125 # Note: The commit might not be in upstream in which case the sync
2126 # will fail.
2127 # * otherwise, fetch all branches to make sure we end up with the
2128 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002129 if self.upstream:
2130 current_branch_only = not ID_RE.match(self.upstream)
2131 else:
2132 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002133
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002134 if not name:
2135 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002136
2137 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002138 remote = self.GetRemote(name)
2139 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002140 ssh_proxy = True
2141
Shawn O. Pearce88443382010-10-08 10:02:09 +02002142 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002143 if alt_dir and 'objects' == os.path.basename(alt_dir):
2144 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002145 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2146 remote = self.GetRemote(name)
2147
David Pursehouse8a68ff92012-09-24 12:15:13 +09002148 all_refs = self.bare_ref.all
2149 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002150 tmp = set()
2151
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302152 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002153 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002154 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002155 all_refs[r] = ref_id
2156 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002157 continue
2158
David Pursehouse8a68ff92012-09-24 12:15:13 +09002159 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002160 continue
2161
David Pursehouse8a68ff92012-09-24 12:15:13 +09002162 r = 'refs/_alt/%s' % ref_id
2163 all_refs[r] = ref_id
2164 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002165 tmp.add(r)
2166
heping3d7bbc92017-04-12 19:51:47 +08002167 tmp_packed_lines = []
2168 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002169
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302170 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002171 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002172 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002173 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002174 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002175
heping3d7bbc92017-04-12 19:51:47 +08002176 tmp_packed = ''.join(tmp_packed_lines)
2177 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002178 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002179 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002180 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002181
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002182 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002183
Xin Li745be2e2019-06-03 11:24:30 -07002184 if clone_filter:
2185 git_require((2, 19, 0), fail=True, msg='partial clones')
2186 cmd.append('--filter=%s' % clone_filter)
2187 self.config.SetString('extensions.partialclone', self.remote.name)
2188
Conley Owensf97e8382015-01-21 11:12:46 -08002189 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002190 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002191 else:
2192 # If this repo has shallow objects, then we don't know which refs have
2193 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2194 # do this with projects that don't have shallow objects, since it is less
2195 # efficient.
2196 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2197 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002198
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002199 if quiet:
2200 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002201 if not self.worktree:
2202 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002203 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002204
Xin Lia2cd6ae2019-09-16 10:55:41 -07002205 spec = []
2206
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002207 # If using depth then we should not get all the tags since they may
2208 # be outside of the depth.
2209 if no_tags or depth:
2210 cmd.append('--no-tags')
2211 else:
2212 cmd.append('--tags')
Xin Lia2cd6ae2019-09-16 10:55:41 -07002213 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002214
Mike Frysingere57f1142019-03-18 21:27:54 -04002215 if force_sync:
2216 cmd.append('--force')
2217
David Pursehouse74cfd272015-10-14 10:50:15 +09002218 if prune:
2219 cmd.append('--prune')
2220
Martin Kellye4e94d22017-03-21 16:05:12 -07002221 if submodules:
2222 cmd.append('--recurse-submodules=on-demand')
2223
Brian Harring14a66742012-09-28 20:21:57 -07002224 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002225 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002226 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002227 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002228 spec.append('tag')
2229 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002230
David Pursehouse403b64e2015-04-27 10:41:33 +09002231 if not self.manifest.IsMirror:
2232 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002233 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002234 # Shallow checkout of a specific commit, fetch from that commit and not
2235 # the heads only as the commit might be deeper in the history.
2236 spec.append(branch)
2237 else:
2238 if is_sha1:
2239 branch = self.upstream
2240 if branch is not None and branch.strip():
2241 if not branch.startswith('refs/'):
2242 branch = R_HEADS + branch
2243 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002244 cmd.extend(spec)
2245
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002246 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002247 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002248 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002249 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002250 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002251 ok = True
2252 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002253 # If needed, run the 'git remote prune' the first time through the loop
2254 elif (not _i and
2255 "error:" in gitcmd.stderr and
2256 "git remote prune" in gitcmd.stderr):
2257 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002258 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002259 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002260 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002261 break
2262 continue
Brian Harring14a66742012-09-28 20:21:57 -07002263 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002264 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2265 # in sha1 mode, we just tried sync'ing from the upstream field; it
2266 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002267 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002268 elif ret < 0:
2269 # Git died with a signal, exit immediately
2270 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002271 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002272
2273 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002274 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002275 if old_packed != '':
2276 _lwrite(packed_refs, old_packed)
2277 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002278 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002279 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002280
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002281 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002282 # We just synced the upstream given branch; verify we
2283 # got what we wanted, else trigger a second run of all
2284 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002285 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002286 if current_branch_only and depth:
2287 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002288 return self._RemoteFetch(name=name,
2289 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002290 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002291 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002292 else:
2293 # Avoid infinite recursion: sync all branches with depth set to None
2294 return self._RemoteFetch(name=name, current_branch_only=False,
2295 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002296 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002297
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002298 return ok
2299
2300 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002301 if initial and \
2302 (self.manifest.manifestProject.config.GetString('repo.depth') or
2303 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002304 return False
2305
2306 remote = self.GetRemote(self.remote.name)
2307 bundle_url = remote.url + '/clone.bundle'
2308 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002309 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2310 'persistent-http',
2311 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002312 return False
2313
2314 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2315 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002316
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002317 exist_dst = os.path.exists(bundle_dst)
2318 exist_tmp = os.path.exists(bundle_tmp)
2319
2320 if not initial and not exist_dst and not exist_tmp:
2321 return False
2322
2323 if not exist_dst:
2324 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2325 if not exist_dst:
2326 return False
2327
2328 cmd = ['fetch']
2329 if quiet:
2330 cmd.append('--quiet')
2331 if not self.worktree:
2332 cmd.append('--update-head-ok')
2333 cmd.append(bundle_dst)
2334 for f in remote.fetch:
2335 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002336 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002337
2338 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002339 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002340 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002341 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002342 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002343 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002344
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002345 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002346 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002347 platform_utils.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002348
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002349 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002350 if quiet:
2351 cmd += ['--silent']
2352 if os.path.exists(tmpPath):
2353 size = os.stat(tmpPath).st_size
2354 if size >= 1024:
2355 cmd += ['--continue-at', '%d' % (size,)]
2356 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002357 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002358 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002359 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002360 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002361 if proxy:
2362 cmd += ['--proxy', proxy]
2363 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2364 cmd += ['--proxy', os.environ['http_proxy']]
2365 if srcUrl.startswith('persistent-https'):
2366 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2367 elif srcUrl.startswith('persistent-http'):
2368 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002369 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002370
Dave Borowitz137d0132015-01-02 11:12:54 -08002371 if IsTrace():
2372 Trace('%s', ' '.join(cmd))
2373 try:
2374 proc = subprocess.Popen(cmd)
2375 except OSError:
2376 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002377
Dave Borowitz137d0132015-01-02 11:12:54 -08002378 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002379
Dave Borowitz137d0132015-01-02 11:12:54 -08002380 if curlret == 22:
2381 # From curl man page:
2382 # 22: HTTP page not retrieved. The requested url was not found or
2383 # returned another error with the HTTP error code being 400 or above.
2384 # This return code only appears if -f, --fail is used.
2385 if not quiet:
2386 print("Server does not provide clone.bundle; ignoring.",
2387 file=sys.stderr)
2388 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002389
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002390 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002391 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002392 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002393 return True
2394 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002395 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002396 return False
2397 else:
2398 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002399
Kris Giesingc8d882a2014-12-23 13:02:32 -08002400 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002401 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002402 with open(path, 'rb') as f:
2403 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002404 return True
2405 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002406 if not quiet:
2407 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002408 return False
2409 except OSError:
2410 return False
2411
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002412 def _Checkout(self, rev, quiet=False):
2413 cmd = ['checkout']
2414 if quiet:
2415 cmd.append('-q')
2416 cmd.append(rev)
2417 cmd.append('--')
2418 if GitCommand(self, cmd).Wait() != 0:
2419 if self._allrefs:
2420 raise GitError('%s checkout %s ' % (self.name, rev))
2421
Anthony King7bdac712014-07-16 12:56:40 +01002422 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002423 cmd = ['cherry-pick']
2424 cmd.append(rev)
2425 cmd.append('--')
2426 if GitCommand(self, cmd).Wait() != 0:
2427 if self._allrefs:
2428 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2429
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302430 def _LsRemote(self, refs):
2431 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302432 p = GitCommand(self, cmd, capture_stdout=True)
2433 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002434 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302435 return None
2436
Anthony King7bdac712014-07-16 12:56:40 +01002437 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002438 cmd = ['revert']
2439 cmd.append('--no-edit')
2440 cmd.append(rev)
2441 cmd.append('--')
2442 if GitCommand(self, cmd).Wait() != 0:
2443 if self._allrefs:
2444 raise GitError('%s revert %s ' % (self.name, rev))
2445
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002446 def _ResetHard(self, rev, quiet=True):
2447 cmd = ['reset', '--hard']
2448 if quiet:
2449 cmd.append('-q')
2450 cmd.append(rev)
2451 if GitCommand(self, cmd).Wait() != 0:
2452 raise GitError('%s reset --hard %s ' % (self.name, rev))
2453
Martin Kellye4e94d22017-03-21 16:05:12 -07002454 def _SyncSubmodules(self, quiet=True):
2455 cmd = ['submodule', 'update', '--init', '--recursive']
2456 if quiet:
2457 cmd.append('-q')
2458 if GitCommand(self, cmd).Wait() != 0:
2459 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2460
Anthony King7bdac712014-07-16 12:56:40 +01002461 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002462 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002463 if onto is not None:
2464 cmd.extend(['--onto', onto])
2465 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002466 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002467 raise GitError('%s rebase %s ' % (self.name, upstream))
2468
Pierre Tardy3d125942012-05-04 12:18:12 +02002469 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002470 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002471 if ffonly:
2472 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002473 if GitCommand(self, cmd).Wait() != 0:
2474 raise GitError('%s merge %s ' % (self.name, head))
2475
Kevin Degiabaa7f32014-11-12 11:27:45 -07002476 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002477 init_git_dir = not os.path.exists(self.gitdir)
2478 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002479 try:
2480 # Initialize the bare repository, which contains all of the objects.
2481 if init_obj_dir:
2482 os.makedirs(self.objdir)
2483 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002484
Kevin Degib1a07b82015-07-27 13:33:43 -06002485 # If we have a separate directory to hold refs, initialize it as well.
2486 if self.objdir != self.gitdir:
2487 if init_git_dir:
2488 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002489
Kevin Degib1a07b82015-07-27 13:33:43 -06002490 if init_obj_dir or init_git_dir:
2491 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2492 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002493 try:
2494 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2495 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002496 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002497 print("Retrying clone after deleting %s" %
2498 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002499 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002500 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2501 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002502 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002503 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002504 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2505 except:
2506 raise e
2507 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002508
Kevin Degib1a07b82015-07-27 13:33:43 -06002509 if init_git_dir:
2510 mp = self.manifest.manifestProject
2511 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002512
Kevin Degib1a07b82015-07-27 13:33:43 -06002513 if ref_dir or mirror_git:
2514 if not mirror_git:
2515 mirror_git = os.path.join(ref_dir, self.name + '.git')
2516 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2517 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002518
Kevin Degib1a07b82015-07-27 13:33:43 -06002519 if os.path.exists(mirror_git):
2520 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002521
Kevin Degib1a07b82015-07-27 13:33:43 -06002522 elif os.path.exists(repo_git):
2523 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002524
Kevin Degib1a07b82015-07-27 13:33:43 -06002525 else:
2526 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002527
Kevin Degib1a07b82015-07-27 13:33:43 -06002528 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002529 if not os.path.isabs(ref_dir):
2530 # The alternate directory is relative to the object database.
2531 ref_dir = os.path.relpath(ref_dir,
2532 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002533 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2534 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002535
Kevin Degib1a07b82015-07-27 13:33:43 -06002536 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002537
Kevin Degib1a07b82015-07-27 13:33:43 -06002538 m = self.manifest.manifestProject.config
2539 for key in ['user.name', 'user.email']:
2540 if m.Has(key, include_defaults=False):
2541 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002542 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002543 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002544 if self.manifest.IsMirror:
2545 self.config.SetString('core.bare', 'true')
2546 else:
2547 self.config.SetString('core.bare', None)
2548 except Exception:
2549 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002550 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002551 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002552 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002553 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002554
Jimmie Westera0444582012-10-24 13:44:42 +02002555 def _UpdateHooks(self):
2556 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002557 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002558
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002559 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002560 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002561 if not os.path.exists(hooks):
2562 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002563 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002564 name = os.path.basename(stock_hook)
2565
Victor Boivie65e0f352011-04-18 11:23:29 +02002566 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002567 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002568 # Don't install a Gerrit Code Review hook if this
2569 # project does not appear to use it for reviews.
2570 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002571 # Since the manifest project is one of those, but also
2572 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002573 continue
2574
2575 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002576 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002577 continue
2578 if os.path.exists(dst):
2579 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002580 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002581 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002582 _warn("%s: Not replacing locally modified %s hook",
2583 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002584 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002585 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002586 platform_utils.symlink(
2587 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002588 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002589 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002590 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002591 else:
2592 raise
2593
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002594 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002595 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002596 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002597 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002598 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002599 remote.review = self.remote.review
2600 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002601
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002602 if self.worktree:
2603 remote.ResetFetch(mirror=False)
2604 else:
2605 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002606 remote.Save()
2607
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002608 def _InitMRef(self):
2609 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002610 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002611
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002612 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002613 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002614
2615 def _InitAnyMRef(self, ref):
2616 cur = self.bare_ref.symref(ref)
2617
2618 if self.revisionId:
2619 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2620 msg = 'manifest set to %s' % self.revisionId
2621 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002622 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002623 else:
2624 remote = self.GetRemote(self.remote.name)
2625 dst = remote.ToLocal(self.revisionExpr)
2626 if cur != dst:
2627 msg = 'manifest set to %s' % self.revisionExpr
2628 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002629
Kevin Degi384b3c52014-10-16 16:02:58 -06002630 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002631 symlink_files = self.shareable_files[:]
2632 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002633 if share_refs:
2634 symlink_files += self.working_tree_files
2635 symlink_dirs += self.working_tree_dirs
2636 to_symlink = symlink_files + symlink_dirs
2637 for name in set(to_symlink):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002638 dst = platform_utils.realpath(os.path.join(destdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002639 if os.path.lexists(dst):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002640 src = platform_utils.realpath(os.path.join(srcdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002641 # Fail if the links are pointing to the wrong place
2642 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002643 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002644 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002645 'work tree. If you\'re comfortable with the '
2646 'possibility of losing the work tree\'s git metadata,'
2647 ' use `repo sync --force-sync {0}` to '
2648 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002649
David James8d201162013-10-11 17:03:19 -07002650 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2651 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2652
2653 Args:
2654 gitdir: The bare git repository. Must already be initialized.
2655 dotgit: The repository you would like to initialize.
2656 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2657 Only one work tree can store refs under a given |gitdir|.
2658 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2659 This saves you the effort of initializing |dotgit| yourself.
2660 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002661 symlink_files = self.shareable_files[:]
2662 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002663 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002664 symlink_files += self.working_tree_files
2665 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002666 to_symlink = symlink_files + symlink_dirs
2667
2668 to_copy = []
2669 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002670 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002671
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002672 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002673 for name in set(to_copy).union(to_symlink):
2674 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002675 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002676 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002677
Kevin Degi384b3c52014-10-16 16:02:58 -06002678 if os.path.lexists(dst):
2679 continue
David James8d201162013-10-11 17:03:19 -07002680
2681 # If the source dir doesn't exist, create an empty dir.
2682 if name in symlink_dirs and not os.path.lexists(src):
2683 os.makedirs(src)
2684
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002685 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002686 platform_utils.symlink(
2687 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002688 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002689 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002690 shutil.copytree(src, dst)
2691 elif os.path.isfile(src):
2692 shutil.copy(src, dst)
2693
Conley Owens80b87fe2014-05-09 17:13:44 -07002694 # If the source file doesn't exist, ensure the destination
2695 # file doesn't either.
2696 if name in symlink_files and not os.path.lexists(src):
2697 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002698 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002699 except OSError:
2700 pass
2701
David James8d201162013-10-11 17:03:19 -07002702 except OSError as e:
2703 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002704 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002705 else:
2706 raise
2707
Martin Kellye4e94d22017-03-21 16:05:12 -07002708 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002709 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002710 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002711 try:
2712 if init_dotgit:
2713 os.makedirs(dotgit)
2714 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2715 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002716
Kevin Degiabaa7f32014-11-12 11:27:45 -07002717 try:
2718 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2719 except GitError as e:
2720 if force_sync:
2721 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -07002722 platform_utils.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002723 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002724 except:
2725 raise e
2726 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002727
Kevin Degib1a07b82015-07-27 13:33:43 -06002728 if init_dotgit:
2729 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002730
Kevin Degib1a07b82015-07-27 13:33:43 -06002731 cmd = ['read-tree', '--reset', '-u']
2732 cmd.append('-v')
2733 cmd.append(HEAD)
2734 if GitCommand(self, cmd).Wait() != 0:
Mikhail Naganovb5548382019-05-09 12:59:49 -07002735 raise GitError("cannot initialize work tree for " + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002736
Martin Kellye4e94d22017-03-21 16:05:12 -07002737 if submodules:
2738 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002739 self._CopyAndLinkFiles()
2740 except Exception:
2741 if init_dotgit:
Renaud Paquaya65adf72016-11-03 10:37:53 -07002742 platform_utils.rmtree(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002743 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002744
Renaud Paquay788e9622017-01-27 11:41:12 -08002745 def _get_symlink_error_message(self):
2746 if platform_utils.isWindows():
2747 return ('Unable to create symbolic link. Please re-run the command as '
2748 'Administrator, or see '
2749 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2750 'for other options.')
2751 return 'filesystem must support symlinks'
2752
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002753 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002754 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002755
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002756 def _revlist(self, *args, **kw):
2757 a = []
2758 a.extend(args)
2759 a.append('--')
2760 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002761
2762 @property
2763 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002764 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002765
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002766 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002767 """Get logs between two revisions of this project."""
2768 comp = '..'
2769 if rev1:
2770 revs = [rev1]
2771 if rev2:
2772 revs.extend([comp, rev2])
2773 cmd = ['log', ''.join(revs)]
2774 out = DiffColoring(self.config)
2775 if out.is_on and color:
2776 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002777 if pretty_format is not None:
2778 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002779 if oneline:
2780 cmd.append('--oneline')
2781
2782 try:
2783 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2784 if log.Wait() == 0:
2785 return log.stdout
2786 except GitError:
2787 # worktree may not exist if groups changed for example. In that case,
2788 # try in gitdir instead.
2789 if not os.path.exists(self.worktree):
2790 return self.bare_git.log(*cmd[1:])
2791 else:
2792 raise
2793 return None
2794
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002795 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2796 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002797 """Get the list of logs from this revision to given revisionId"""
2798 logs = {}
2799 selfId = self.GetRevisionId(self._allrefs)
2800 toId = toProject.GetRevisionId(toProject._allrefs)
2801
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002802 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2803 pretty_format=pretty_format)
2804 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2805 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002806 return logs
2807
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002808 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002809
David James8d201162013-10-11 17:03:19 -07002810 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002811 self._project = project
2812 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002813 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002814
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002815 def LsOthers(self):
2816 p = GitCommand(self._project,
2817 ['ls-files',
2818 '-z',
2819 '--others',
2820 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002821 bare=False,
David James8d201162013-10-11 17:03:19 -07002822 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002823 capture_stdout=True,
2824 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002825 if p.Wait() == 0:
2826 out = p.stdout
2827 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002828 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002829 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002830 return []
2831
2832 def DiffZ(self, name, *args):
2833 cmd = [name]
2834 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002835 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002836 cmd.extend(args)
2837 p = GitCommand(self._project,
2838 cmd,
David James8d201162013-10-11 17:03:19 -07002839 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002840 bare=False,
2841 capture_stdout=True,
2842 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002843 try:
2844 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002845 if not hasattr(out, 'encode'):
2846 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002847 r = {}
2848 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002849 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002850 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002851 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002852 info = next(out)
2853 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002854 except StopIteration:
2855 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002856
2857 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002858
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002859 def __init__(self, path, omode, nmode, oid, nid, state):
2860 self.path = path
2861 self.src_path = None
2862 self.old_mode = omode
2863 self.new_mode = nmode
2864 self.old_id = oid
2865 self.new_id = nid
2866
2867 if len(state) == 1:
2868 self.status = state
2869 self.level = None
2870 else:
2871 self.status = state[:1]
2872 self.level = state[1:]
2873 while self.level.startswith('0'):
2874 self.level = self.level[1:]
2875
2876 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002877 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002878 if info.status in ('R', 'C'):
2879 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002880 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002881 r[info.path] = info
2882 return r
2883 finally:
2884 p.Wait()
2885
2886 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002887 if self._bare:
2888 path = os.path.join(self._project.gitdir, HEAD)
2889 else:
2890 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002891 try:
Renaud Paquay2a4be942016-11-01 13:48:15 -07002892 fd = open(path)
Dan Sandler53e902a2014-03-09 13:20:02 -04002893 except IOError as e:
2894 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002895 try:
Renaud Paquay2a4be942016-11-01 13:48:15 -07002896 line = fd.readline()
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002897 finally:
2898 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302899 try:
2900 line = line.decode()
2901 except AttributeError:
2902 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002903 if line.startswith('ref: '):
2904 return line[5:-1]
2905 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002906
2907 def SetHead(self, ref, message=None):
2908 cmdv = []
2909 if message is not None:
2910 cmdv.extend(['-m', message])
2911 cmdv.append(HEAD)
2912 cmdv.append(ref)
2913 self.symbolic_ref(*cmdv)
2914
2915 def DetachHead(self, new, message=None):
2916 cmdv = ['--no-deref']
2917 if message is not None:
2918 cmdv.extend(['-m', message])
2919 cmdv.append(HEAD)
2920 cmdv.append(new)
2921 self.update_ref(*cmdv)
2922
2923 def UpdateRef(self, name, new, old=None,
2924 message=None,
2925 detach=False):
2926 cmdv = []
2927 if message is not None:
2928 cmdv.extend(['-m', message])
2929 if detach:
2930 cmdv.append('--no-deref')
2931 cmdv.append(name)
2932 cmdv.append(new)
2933 if old is not None:
2934 cmdv.append(old)
2935 self.update_ref(*cmdv)
2936
2937 def DeleteRef(self, name, old=None):
2938 if not old:
2939 old = self.rev_parse(name)
2940 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002941 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002942
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002943 def rev_list(self, *args, **kw):
2944 if 'format' in kw:
2945 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2946 else:
2947 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002948 cmdv.extend(args)
2949 p = GitCommand(self._project,
2950 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002951 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002952 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002953 capture_stdout=True,
2954 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002955 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002956 raise GitError('%s rev-list %s: %s' %
2957 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002958 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002959
2960 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002961 """Allow arbitrary git commands using pythonic syntax.
2962
2963 This allows you to do things like:
2964 git_obj.rev_parse('HEAD')
2965
2966 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2967 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002968 Any other positional arguments will be passed to the git command, and the
2969 following keyword arguments are supported:
2970 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002971
2972 Args:
2973 name: The name of the git command to call. Any '_' characters will
2974 be replaced with '-'.
2975
2976 Returns:
2977 A callable object that will try to call git with the named command.
2978 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002979 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002980
Dave Borowitz091f8932012-10-23 17:01:04 -07002981 def runner(*args, **kwargs):
2982 cmdv = []
2983 config = kwargs.pop('config', None)
2984 for k in kwargs:
2985 raise TypeError('%s() got an unexpected keyword argument %r'
2986 % (name, k))
2987 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002988 if not git_require((1, 7, 2)):
2989 raise ValueError('cannot set config on command line for %s()'
2990 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302991 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002992 cmdv.append('-c')
2993 cmdv.append('%s=%s' % (k, v))
2994 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002995 cmdv.extend(args)
2996 p = GitCommand(self._project,
2997 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002998 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002999 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01003000 capture_stdout=True,
3001 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003002 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003003 raise GitError('%s %s: %s' %
3004 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003005 r = p.stdout
3006 if r.endswith('\n') and r.index('\n') == len(r) - 1:
3007 return r[:-1]
3008 return r
3009 return runner
3010
3011
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003012class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003013
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003014 def __str__(self):
3015 return 'prior sync failed; rebase still in progress'
3016
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003017
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003018class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003019
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003020 def __str__(self):
3021 return 'contains uncommitted changes'
3022
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003023
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003024class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003025
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003026 def __init__(self, project, text):
3027 self.project = project
3028 self.text = text
3029
3030 def Print(self, syncbuf):
3031 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3032 syncbuf.out.nl()
3033
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003034
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003035class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003036
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003037 def __init__(self, project, why):
3038 self.project = project
3039 self.why = why
3040
3041 def Print(self, syncbuf):
3042 syncbuf.out.fail('error: %s/: %s',
3043 self.project.relpath,
3044 str(self.why))
3045 syncbuf.out.nl()
3046
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003047
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003048class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003049
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003050 def __init__(self, project, action):
3051 self.project = project
3052 self.action = action
3053
3054 def Run(self, syncbuf):
3055 out = syncbuf.out
3056 out.project('project %s/', self.project.relpath)
3057 out.nl()
3058 try:
3059 self.action()
3060 out.nl()
3061 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003062 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003063 out.nl()
3064 return False
3065
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003066
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003067class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003068
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003069 def __init__(self, config):
3070 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003071 self.project = self.printer('header', attr='bold')
3072 self.info = self.printer('info')
3073 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003074
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003075
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003076class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003077
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003078 def __init__(self, config, detach_head=False):
3079 self._messages = []
3080 self._failures = []
3081 self._later_queue1 = []
3082 self._later_queue2 = []
3083
3084 self.out = _SyncColoring(config)
3085 self.out.redirect(sys.stderr)
3086
3087 self.detach_head = detach_head
3088 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003089 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003090
3091 def info(self, project, fmt, *args):
3092 self._messages.append(_InfoMessage(project, fmt % args))
3093
3094 def fail(self, project, err=None):
3095 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003096 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003097
3098 def later1(self, project, what):
3099 self._later_queue1.append(_Later(project, what))
3100
3101 def later2(self, project, what):
3102 self._later_queue2.append(_Later(project, what))
3103
3104 def Finish(self):
3105 self._PrintMessages()
3106 self._RunLater()
3107 self._PrintMessages()
3108 return self.clean
3109
David Rileye0684ad2017-04-05 00:02:59 -07003110 def Recently(self):
3111 recent_clean = self.recent_clean
3112 self.recent_clean = True
3113 return recent_clean
3114
3115 def _MarkUnclean(self):
3116 self.clean = False
3117 self.recent_clean = False
3118
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003119 def _RunLater(self):
3120 for q in ['_later_queue1', '_later_queue2']:
3121 if not self._RunQueue(q):
3122 return
3123
3124 def _RunQueue(self, queue):
3125 for m in getattr(self, queue):
3126 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003127 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003128 return False
3129 setattr(self, queue, [])
3130 return True
3131
3132 def _PrintMessages(self):
Mike Frysinger70d861f2019-08-26 15:22:36 -04003133 if self._messages or self._failures:
3134 if os.isatty(2):
3135 self.out.write(progress.CSI_ERASE_LINE)
3136 self.out.write('\r')
3137
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003138 for m in self._messages:
3139 m.Print(self)
3140 for m in self._failures:
3141 m.Print(self)
3142
3143 self._messages = []
3144 self._failures = []
3145
3146
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003147class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003148
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003149 """A special project housed under .repo.
3150 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003151
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003152 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003153 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003154 manifest=manifest,
3155 name=name,
3156 gitdir=gitdir,
3157 objdir=gitdir,
3158 worktree=worktree,
3159 remote=RemoteSpec('origin'),
3160 relpath='.repo/%s' % name,
3161 revisionExpr='refs/heads/master',
3162 revisionId=None,
3163 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003164
3165 def PreSync(self):
3166 if self.Exists:
3167 cb = self.CurrentBranch
3168 if cb:
3169 base = self.GetBranch(cb).merge
3170 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003171 self.revisionExpr = base
3172 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003173
Martin Kelly224a31a2017-07-10 14:46:25 -07003174 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003175 """ Prepare MetaProject for manifest branch switch
3176 """
3177
3178 # detach and delete manifest branch, allowing a new
3179 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003180 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003181 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003182 syncbuf.Finish()
3183
3184 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003185 ['update-ref', '-d', 'refs/heads/default'],
3186 capture_stdout=True,
3187 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003188
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003189 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003190 def LastFetch(self):
3191 try:
3192 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3193 return os.path.getmtime(fh)
3194 except OSError:
3195 return 0
3196
3197 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003198 def HasChanges(self):
3199 """Has the remote received new commits not yet checked out?
3200 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003201 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003202 return False
3203
David Pursehouse8a68ff92012-09-24 12:15:13 +09003204 all_refs = self.bare_ref.all
3205 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003206 head = self.work_git.GetHead()
3207 if head.startswith(R_HEADS):
3208 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003209 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003210 except KeyError:
3211 head = None
3212
3213 if revid == head:
3214 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003215 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003216 return True
3217 return False