blob: 4076bc5d1403ca93489f4b753a9f89f4e96b6aa6 [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 Frysinger8a11f6f2019-08-27 00:26:15 -040042from repo_trace import IsTrace, Trace
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043
Shawn O. Pearced237b692009-04-17 18:49:50 -070044from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070045
David Pursehouse59bbb582013-05-17 10:49:33 +090046from pyversion import is_python3
Mike Frysinger40252c22016-08-15 21:23:44 -040047if is_python3():
48 import urllib.parse
49else:
50 import imp
51 import urlparse
52 urllib = imp.new_module('urllib')
53 urllib.parse = urlparse
Chirayu Desai217ea7d2013-03-01 19:14:38 +053054 input = raw_input
Chirayu Desai217ea7d2013-03-01 19:14:38 +053055
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070056
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070057def _lwrite(path, content):
58 lock = '%s.lock' % path
59
Chirayu Desai303a82f2014-08-19 22:57:17 +053060 fd = open(lock, 'w')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070061 try:
62 fd.write(content)
63 finally:
64 fd.close()
65
66 try:
Renaud Paquayad1abcb2016-11-01 11:34:55 -070067 platform_utils.rename(lock, path)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070068 except OSError:
Renaud Paquay010fed72016-11-11 14:25:29 -080069 platform_utils.remove(lock)
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070070 raise
71
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070072
Shawn O. Pearce48244782009-04-16 08:25:57 -070073def _error(fmt, *args):
74 msg = fmt % args
Sarah Owenscecd1d82012-11-01 22:59:27 -070075 print('error: %s' % msg, file=sys.stderr)
Shawn O. Pearce48244782009-04-16 08:25:57 -070076
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070077
David Pursehousef33929d2015-08-24 14:39:14 +090078def _warn(fmt, *args):
79 msg = fmt % args
80 print('warn: %s' % msg, file=sys.stderr)
81
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070082
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070083def not_rev(r):
84 return '^' + r
85
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070086
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080087def sq(r):
88 return "'" + r.replace("'", "'\''") + "'"
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -080089
Jonathan Nieder93719792015-03-17 11:29:58 -070090_project_hook_list = None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -070091
92
Jonathan Nieder93719792015-03-17 11:29:58 -070093def _ProjectHooks():
94 """List the hooks present in the 'hooks' directory.
95
96 These hooks are project hooks and are copied to the '.git/hooks' directory
97 of all subprojects.
98
99 This function caches the list of hooks (based on the contents of the
100 'repo/hooks' directory) on the first call.
101
102 Returns:
103 A list of absolute paths to all of the files in the hooks directory.
104 """
105 global _project_hook_list
106 if _project_hook_list is None:
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700107 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
Jonathan Nieder93719792015-03-17 11:29:58 -0700108 d = os.path.join(d, 'hooks')
Renaud Paquaybed8b622018-09-27 10:46:58 -0700109 _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)]
Jonathan Nieder93719792015-03-17 11:29:58 -0700110 return _project_hook_list
111
112
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700113class DownloadedChange(object):
114 _commit_cache = None
115
116 def __init__(self, project, base, change_id, ps_id, commit):
117 self.project = project
118 self.base = base
119 self.change_id = change_id
120 self.ps_id = ps_id
121 self.commit = commit
122
123 @property
124 def commits(self):
125 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700126 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
127 '--abbrev-commit',
128 '--pretty=oneline',
129 '--reverse',
130 '--date-order',
131 not_rev(self.base),
132 self.commit,
133 '--')
Shawn O. Pearce632768b2008-10-23 11:58:52 -0700134 return self._commit_cache
135
136
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137class ReviewableBranch(object):
138 _commit_cache = None
139
140 def __init__(self, project, branch, base):
141 self.project = project
142 self.branch = branch
143 self.base = base
144
145 @property
146 def name(self):
147 return self.branch.name
148
149 @property
150 def commits(self):
151 if self._commit_cache is None:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700152 self._commit_cache = self.project.bare_git.rev_list('--abbrev=8',
153 '--abbrev-commit',
154 '--pretty=oneline',
155 '--reverse',
156 '--date-order',
157 not_rev(self.base),
158 R_HEADS + self.name,
159 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160 return self._commit_cache
161
162 @property
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800163 def unabbrev_commits(self):
164 r = dict()
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700165 for commit in self.project.bare_git.rev_list(not_rev(self.base),
166 R_HEADS + self.name,
167 '--'):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800168 r[commit[0:8]] = commit
169 return r
170
171 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 def date(self):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700173 return self.project.bare_git.log('--pretty=format:%cd',
174 '-n', '1',
175 R_HEADS + self.name,
176 '--')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700178 def UploadForReview(self, people,
179 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000180 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200181 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700182 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200183 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200184 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800185 validate_certs=True,
186 push_options=None):
Shawn O. Pearcec99883f2008-11-11 17:12:43 -0800187 self.project.UploadForReview(self.name,
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -0700188 people,
Brian Harring435370c2012-07-28 15:37:04 -0700189 auto_topic=auto_topic,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +0000190 draft=draft,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200191 private=private,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -0700192 notify=notify,
Changcheng Xiao87984c62017-08-02 16:55:03 +0200193 wip=wip,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200194 dest_branch=dest_branch,
Masaya Suzuki305a2d02017-11-13 10:48:34 -0800195 validate_certs=validate_certs,
196 push_options=push_options)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700197
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700198 def GetPublishedRefs(self):
199 refs = {}
200 output = self.project.bare_git.ls_remote(
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700201 self.branch.remote.SshReviewUrl(self.project.UserEmail),
202 'refs/changes/*')
Ficus Kirkpatrickbc7ef672009-05-04 12:45:11 -0700203 for line in output.split('\n'):
204 try:
205 (sha, ref) = line.split()
206 refs[sha] = ref
207 except ValueError:
208 pass
209
210 return refs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700212
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213class StatusColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700214
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700215 def __init__(self, config):
216 Coloring.__init__(self, config, 'status')
Anthony King7bdac712014-07-16 12:56:40 +0100217 self.project = self.printer('header', attr='bold')
218 self.branch = self.printer('header', attr='bold')
219 self.nobranch = self.printer('nobranch', fg='red')
220 self.important = self.printer('important', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221
Anthony King7bdac712014-07-16 12:56:40 +0100222 self.added = self.printer('added', fg='green')
223 self.changed = self.printer('changed', fg='red')
224 self.untracked = self.printer('untracked', fg='red')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225
226
227class DiffColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700228
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 def __init__(self, config):
230 Coloring.__init__(self, config, 'diff')
Anthony King7bdac712014-07-16 12:56:40 +0100231 self.project = self.printer('header', attr='bold')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700233
Anthony King7bdac712014-07-16 12:56:40 +0100234class _Annotation(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700235
James W. Mills24c13082012-04-12 15:04:13 -0500236 def __init__(self, name, value, keep):
237 self.name = name
238 self.value = value
239 self.keep = keep
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700241
Anthony King7bdac712014-07-16 12:56:40 +0100242class _CopyFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700243
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800244 def __init__(self, src, dest, abssrc, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 self.src = src
246 self.dest = dest
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800247 self.abs_src = abssrc
248 self.abs_dest = absdest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700249
250 def _Copy(self):
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -0800251 src = self.abs_src
252 dest = self.abs_dest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253 # copy file if it does not exist or is out of date
254 if not os.path.exists(dest) or not filecmp.cmp(src, dest):
255 try:
256 # remove existing file first, since it might be read-only
257 if os.path.exists(dest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800258 platform_utils.remove(dest)
Matthew Buckett2daf6672009-07-11 09:43:47 -0400259 else:
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200260 dest_dir = os.path.dirname(dest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700261 if not platform_utils.isdir(dest_dir):
Mickaël Salaün2f6ab7f2012-09-30 00:37:55 +0200262 os.makedirs(dest_dir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700263 shutil.copy(src, dest)
264 # make the file read-only
265 mode = os.stat(dest)[stat.ST_MODE]
266 mode = mode & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH)
267 os.chmod(dest, mode)
268 except IOError:
Shawn O. Pearce48244782009-04-16 08:25:57 -0700269 _error('Cannot copy file %s to %s', src, dest)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700270
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700271
Anthony King7bdac712014-07-16 12:56:40 +0100272class _LinkFile(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700273
Wink Saville4c426ef2015-06-03 08:05:17 -0700274 def __init__(self, git_worktree, src, dest, relsrc, absdest):
275 self.git_worktree = git_worktree
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500276 self.src = src
277 self.dest = dest
Colin Cross0184dcc2015-05-05 00:24:54 -0700278 self.src_rel_to_dest = relsrc
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500279 self.abs_dest = absdest
280
Wink Saville4c426ef2015-06-03 08:05:17 -0700281 def __linkIt(self, relSrc, absDest):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500282 # link file if it does not exist or is out of date
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700283 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500284 try:
285 # remove existing file first, since it might be read-only
Dan Willemsene1e0bd12015-11-18 16:49:38 -0800286 if os.path.lexists(absDest):
Renaud Paquay010fed72016-11-11 14:25:29 -0800287 platform_utils.remove(absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500288 else:
Wink Saville4c426ef2015-06-03 08:05:17 -0700289 dest_dir = os.path.dirname(absDest)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700290 if not platform_utils.isdir(dest_dir):
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500291 os.makedirs(dest_dir)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700292 platform_utils.symlink(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500293 except IOError:
Wink Saville4c426ef2015-06-03 08:05:17 -0700294 _error('Cannot link file %s to %s', relSrc, absDest)
295
296 def _Link(self):
297 """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards
298 on the src linking all of the files in the source in to the destination
299 directory.
300 """
301 # We use the absSrc to handle the situation where the current directory
302 # is not the root of the repo
303 absSrc = os.path.join(self.git_worktree, self.src)
304 if os.path.exists(absSrc):
305 # Entity exists so just a simple one to one link operation
306 self.__linkIt(self.src_rel_to_dest, self.abs_dest)
307 else:
308 # Entity doesn't exist assume there is a wild card
309 absDestDir = self.abs_dest
Renaud Paquaybed8b622018-09-27 10:46:58 -0700310 if os.path.exists(absDestDir) and not platform_utils.isdir(absDestDir):
Wink Saville4c426ef2015-06-03 08:05:17 -0700311 _error('Link error: src with wildcard, %s must be a directory',
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700312 absDestDir)
Wink Saville4c426ef2015-06-03 08:05:17 -0700313 else:
314 absSrcFiles = glob.glob(absSrc)
315 for absSrcFile in absSrcFiles:
316 # Create a releative path from source dir to destination dir
317 absSrcDir = os.path.dirname(absSrcFile)
318 relSrcDir = os.path.relpath(absSrcDir, absDestDir)
319
320 # Get the source file name
321 srcFile = os.path.basename(absSrcFile)
322
323 # Now form the final full paths to srcFile. They will be
324 # absolute for the desintaiton and relative for the srouce.
325 absDest = os.path.join(absDestDir, srcFile)
326 relSrc = os.path.join(relSrcDir, srcFile)
327 self.__linkIt(relSrc, absDest)
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500328
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700329
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700330class RemoteSpec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700331
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700332 def __init__(self,
333 name,
Anthony King7bdac712014-07-16 12:56:40 +0100334 url=None,
Steve Raed6480452016-08-10 15:00:00 -0700335 pushUrl=None,
Anthony King7bdac712014-07-16 12:56:40 +0100336 review=None,
Dan Willemsen96c2d652016-04-06 16:03:54 -0700337 revision=None,
David Rileye0684ad2017-04-05 00:02:59 -0700338 orig_name=None,
339 fetchUrl=None):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700340 self.name = name
341 self.url = url
Steve Raed6480452016-08-10 15:00:00 -0700342 self.pushUrl = pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -0700343 self.review = review
Anthony King36ea2fb2014-05-06 11:54:01 +0100344 self.revision = revision
Dan Willemsen96c2d652016-04-06 16:03:54 -0700345 self.orig_name = orig_name
David Rileye0684ad2017-04-05 00:02:59 -0700346 self.fetchUrl = fetchUrl
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700347
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700348
Doug Anderson37282b42011-03-04 11:54:18 -0800349class RepoHook(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700350
Doug Anderson37282b42011-03-04 11:54:18 -0800351 """A RepoHook contains information about a script to run as a hook.
352
353 Hooks are used to run a python script before running an upload (for instance,
354 to run presubmit checks). Eventually, we may have hooks for other actions.
355
356 This shouldn't be confused with files in the 'repo/hooks' directory. Those
357 files are copied into each '.git/hooks' folder for each project. Repo-level
358 hooks are associated instead with repo actions.
359
360 Hooks are always python. When a hook is run, we will load the hook into the
361 interpreter and execute its main() function.
362 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700363
Doug Anderson37282b42011-03-04 11:54:18 -0800364 def __init__(self,
365 hook_type,
366 hooks_project,
367 topdir,
Mike Frysinger40252c22016-08-15 21:23:44 -0400368 manifest_url,
Doug Anderson37282b42011-03-04 11:54:18 -0800369 abort_if_user_denies=False):
370 """RepoHook constructor.
371
372 Params:
373 hook_type: A string representing the type of hook. This is also used
374 to figure out the name of the file containing the hook. For
375 example: 'pre-upload'.
376 hooks_project: The project containing the repo hooks. If you have a
377 manifest, this is manifest.repo_hooks_project. OK if this is None,
378 which will make the hook a no-op.
379 topdir: Repo's top directory (the one containing the .repo directory).
380 Scripts will run with CWD as this directory. If you have a manifest,
381 this is manifest.topdir
Mike Frysinger40252c22016-08-15 21:23:44 -0400382 manifest_url: The URL to the manifest git repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800383 abort_if_user_denies: If True, we'll throw a HookError() if the user
384 doesn't allow us to run the hook.
385 """
386 self._hook_type = hook_type
387 self._hooks_project = hooks_project
Mike Frysinger40252c22016-08-15 21:23:44 -0400388 self._manifest_url = manifest_url
Doug Anderson37282b42011-03-04 11:54:18 -0800389 self._topdir = topdir
390 self._abort_if_user_denies = abort_if_user_denies
391
392 # Store the full path to the script for convenience.
393 if self._hooks_project:
394 self._script_fullpath = os.path.join(self._hooks_project.worktree,
395 self._hook_type + '.py')
396 else:
397 self._script_fullpath = None
398
399 def _GetHash(self):
400 """Return a hash of the contents of the hooks directory.
401
402 We'll just use git to do this. This hash has the property that if anything
403 changes in the directory we will return a different has.
404
405 SECURITY CONSIDERATION:
406 This hash only represents the contents of files in the hook directory, not
407 any other files imported or called by hooks. Changes to imported files
408 can change the script behavior without affecting the hash.
409
410 Returns:
411 A string representing the hash. This will always be ASCII so that it can
412 be printed to the user easily.
413 """
414 assert self._hooks_project, "Must have hooks to calculate their hash."
415
416 # We will use the work_git object rather than just calling GetRevisionId().
417 # That gives us a hash of the latest checked in version of the files that
418 # the user will actually be executing. Specifically, GetRevisionId()
419 # doesn't appear to change even if a user checks out a different version
420 # of the hooks repo (via git checkout) nor if a user commits their own revs.
421 #
422 # NOTE: Local (non-committed) changes will not be factored into this hash.
423 # I think this is OK, since we're really only worried about warning the user
424 # about upstream changes.
425 return self._hooks_project.work_git.rev_parse('HEAD')
426
427 def _GetMustVerb(self):
428 """Return 'must' if the hook is required; 'should' if not."""
429 if self._abort_if_user_denies:
430 return 'must'
431 else:
432 return 'should'
433
434 def _CheckForHookApproval(self):
435 """Check to see whether this hook has been approved.
436
Mike Frysinger40252c22016-08-15 21:23:44 -0400437 We'll accept approval of manifest URLs if they're using secure transports.
438 This way the user can say they trust the manifest hoster. For insecure
439 hosts, we fall back to checking the hash of the hooks repo.
Doug Anderson37282b42011-03-04 11:54:18 -0800440
441 Note that we ask permission for each individual hook even though we use
442 the hash of all hooks when detecting changes. We'd like the user to be
443 able to approve / deny each hook individually. We only use the hash of all
444 hooks because there is no other easy way to detect changes to local imports.
445
446 Returns:
447 True if this hook is approved to run; False otherwise.
448
449 Raises:
450 HookError: Raised if the user doesn't approve and abort_if_user_denies
451 was passed to the consturctor.
452 """
Mike Frysinger40252c22016-08-15 21:23:44 -0400453 if self._ManifestUrlHasSecureScheme():
454 return self._CheckForHookApprovalManifest()
455 else:
456 return self._CheckForHookApprovalHash()
457
458 def _CheckForHookApprovalHelper(self, subkey, new_val, main_prompt,
459 changed_prompt):
460 """Check for approval for a particular attribute and hook.
461
462 Args:
463 subkey: The git config key under [repo.hooks.<hook_type>] to store the
464 last approved string.
465 new_val: The new value to compare against the last approved one.
466 main_prompt: Message to display to the user to ask for approval.
467 changed_prompt: Message explaining why we're re-asking for approval.
468
469 Returns:
470 True if this hook is approved to run; False otherwise.
Doug Anderson37282b42011-03-04 11:54:18 -0800471
Mike Frysinger40252c22016-08-15 21:23:44 -0400472 Raises:
473 HookError: Raised if the user doesn't approve and abort_if_user_denies
474 was passed to the consturctor.
475 """
476 hooks_config = self._hooks_project.config
477 git_approval_key = 'repo.hooks.%s.%s' % (self._hook_type, subkey)
Doug Anderson37282b42011-03-04 11:54:18 -0800478
Mike Frysinger40252c22016-08-15 21:23:44 -0400479 # Get the last value that the user approved for this hook; may be None.
480 old_val = hooks_config.GetString(git_approval_key)
Doug Anderson37282b42011-03-04 11:54:18 -0800481
Mike Frysinger40252c22016-08-15 21:23:44 -0400482 if old_val is not None:
Doug Anderson37282b42011-03-04 11:54:18 -0800483 # User previously approved hook and asked not to be prompted again.
Mike Frysinger40252c22016-08-15 21:23:44 -0400484 if new_val == old_val:
Doug Anderson37282b42011-03-04 11:54:18 -0800485 # Approval matched. We're done.
486 return True
487 else:
488 # Give the user a reason why we're prompting, since they last told
489 # us to "never ask again".
Mike Frysinger40252c22016-08-15 21:23:44 -0400490 prompt = 'WARNING: %s\n\n' % (changed_prompt,)
Doug Anderson37282b42011-03-04 11:54:18 -0800491 else:
492 prompt = ''
493
494 # Prompt the user if we're not on a tty; on a tty we'll assume "no".
495 if sys.stdout.isatty():
Mike Frysinger40252c22016-08-15 21:23:44 -0400496 prompt += main_prompt + ' (yes/always/NO)? '
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530497 response = input(prompt).lower()
David Pursehouse98ffba12012-11-14 11:18:00 +0900498 print()
Doug Anderson37282b42011-03-04 11:54:18 -0800499
500 # User is doing a one-time approval.
501 if response in ('y', 'yes'):
502 return True
Mike Frysinger40252c22016-08-15 21:23:44 -0400503 elif response == 'always':
504 hooks_config.SetString(git_approval_key, new_val)
Doug Anderson37282b42011-03-04 11:54:18 -0800505 return True
506
507 # For anything else, we'll assume no approval.
508 if self._abort_if_user_denies:
509 raise HookError('You must allow the %s hook or use --no-verify.' %
510 self._hook_type)
511
512 return False
513
Mike Frysinger40252c22016-08-15 21:23:44 -0400514 def _ManifestUrlHasSecureScheme(self):
515 """Check if the URI for the manifest is a secure transport."""
516 secure_schemes = ('file', 'https', 'ssh', 'persistent-https', 'sso', 'rpc')
517 parse_results = urllib.parse.urlparse(self._manifest_url)
518 return parse_results.scheme in secure_schemes
519
520 def _CheckForHookApprovalManifest(self):
521 """Check whether the user has approved this manifest host.
522
523 Returns:
524 True if this hook is approved to run; False otherwise.
525 """
526 return self._CheckForHookApprovalHelper(
527 'approvedmanifest',
528 self._manifest_url,
529 'Run hook scripts from %s' % (self._manifest_url,),
530 'Manifest URL has changed since %s was allowed.' % (self._hook_type,))
531
532 def _CheckForHookApprovalHash(self):
533 """Check whether the user has approved the hooks repo.
534
535 Returns:
536 True if this hook is approved to run; False otherwise.
537 """
538 prompt = ('Repo %s run the script:\n'
539 ' %s\n'
540 '\n'
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700541 'Do you want to allow this script to run')
Mike Frysinger40252c22016-08-15 21:23:44 -0400542 return self._CheckForHookApprovalHelper(
543 'approvedhash',
544 self._GetHash(),
Jonathan Nieder71e4cea2016-08-16 12:05:09 -0700545 prompt % (self._GetMustVerb(), self._script_fullpath),
Mike Frysinger40252c22016-08-15 21:23:44 -0400546 'Scripts have changed since %s was allowed.' % (self._hook_type,))
547
Mike Frysingerf7c51602019-06-18 17:23:39 -0400548 @staticmethod
549 def _ExtractInterpFromShebang(data):
550 """Extract the interpreter used in the shebang.
551
552 Try to locate the interpreter the script is using (ignoring `env`).
553
554 Args:
555 data: The file content of the script.
556
557 Returns:
558 The basename of the main script interpreter, or None if a shebang is not
559 used or could not be parsed out.
560 """
561 firstline = data.splitlines()[:1]
562 if not firstline:
563 return None
564
565 # The format here can be tricky.
566 shebang = firstline[0].strip()
567 m = re.match(r'^#!\s*([^\s]+)(?:\s+([^\s]+))?', shebang)
568 if not m:
569 return None
570
571 # If the using `env`, find the target program.
572 interp = m.group(1)
573 if os.path.basename(interp) == 'env':
574 interp = m.group(2)
575
576 return interp
577
578 def _ExecuteHookViaReexec(self, interp, context, **kwargs):
579 """Execute the hook script through |interp|.
580
581 Note: Support for this feature should be dropped ~Jun 2021.
582
583 Args:
584 interp: The Python program to run.
585 context: Basic Python context to execute the hook inside.
586 kwargs: Arbitrary arguments to pass to the hook script.
587
588 Raises:
589 HookError: When the hooks failed for any reason.
590 """
591 # This logic needs to be kept in sync with _ExecuteHookViaImport below.
592 script = """
593import json, os, sys
594path = '''%(path)s'''
595kwargs = json.loads('''%(kwargs)s''')
596context = json.loads('''%(context)s''')
597sys.path.insert(0, os.path.dirname(path))
598data = open(path).read()
599exec(compile(data, path, 'exec'), context)
600context['main'](**kwargs)
601""" % {
602 'path': self._script_fullpath,
603 'kwargs': json.dumps(kwargs),
604 'context': json.dumps(context),
605 }
606
607 # We pass the script via stdin to avoid OS argv limits. It also makes
608 # unhandled exception tracebacks less verbose/confusing for users.
609 cmd = [interp, '-c', 'import sys; exec(sys.stdin.read())']
610 proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
611 proc.communicate(input=script.encode('utf-8'))
612 if proc.returncode:
613 raise HookError('Failed to run %s hook.' % (self._hook_type,))
614
615 def _ExecuteHookViaImport(self, data, context, **kwargs):
616 """Execute the hook code in |data| directly.
617
618 Args:
619 data: The code of the hook to execute.
620 context: Basic Python context to execute the hook inside.
621 kwargs: Arbitrary arguments to pass to the hook script.
622
623 Raises:
624 HookError: When the hooks failed for any reason.
625 """
626 # Exec, storing global context in the context dict. We catch exceptions
627 # and convert to a HookError w/ just the failing traceback.
628 try:
629 exec(compile(data, self._script_fullpath, 'exec'), context)
630 except Exception:
631 raise HookError('%s\nFailed to import %s hook; see traceback above.' %
632 (traceback.format_exc(), self._hook_type))
633
634 # Running the script should have defined a main() function.
635 if 'main' not in context:
636 raise HookError('Missing main() in: "%s"' % self._script_fullpath)
637
638 # Call the main function in the hook. If the hook should cause the
639 # build to fail, it will raise an Exception. We'll catch that convert
640 # to a HookError w/ just the failing traceback.
641 try:
642 context['main'](**kwargs)
643 except Exception:
644 raise HookError('%s\nFailed to run main() for %s hook; see traceback '
645 'above.' % (traceback.format_exc(), self._hook_type))
646
Doug Anderson37282b42011-03-04 11:54:18 -0800647 def _ExecuteHook(self, **kwargs):
648 """Actually execute the given hook.
649
650 This will run the hook's 'main' function in our python interpreter.
651
652 Args:
653 kwargs: Keyword arguments to pass to the hook. These are often specific
654 to the hook type. For instance, pre-upload hooks will contain
655 a project_list.
656 """
657 # Keep sys.path and CWD stashed away so that we can always restore them
658 # upon function exit.
659 orig_path = os.getcwd()
660 orig_syspath = sys.path
661
662 try:
663 # Always run hooks with CWD as topdir.
664 os.chdir(self._topdir)
665
666 # Put the hook dir as the first item of sys.path so hooks can do
667 # relative imports. We want to replace the repo dir as [0] so
668 # hooks can't import repo files.
669 sys.path = [os.path.dirname(self._script_fullpath)] + sys.path[1:]
670
Mike Frysingerf7c51602019-06-18 17:23:39 -0400671 # Initial global context for the hook to run within.
Mike Frysinger4aa4b212016-03-04 15:03:00 -0500672 context = {'__file__': self._script_fullpath}
Doug Anderson37282b42011-03-04 11:54:18 -0800673
Doug Anderson37282b42011-03-04 11:54:18 -0800674 # Add 'hook_should_take_kwargs' to the arguments to be passed to main.
675 # We don't actually want hooks to define their main with this argument--
676 # it's there to remind them that their hook should always take **kwargs.
677 # For instance, a pre-upload hook should be defined like:
678 # def main(project_list, **kwargs):
679 #
680 # This allows us to later expand the API without breaking old hooks.
681 kwargs = kwargs.copy()
682 kwargs['hook_should_take_kwargs'] = True
683
Mike Frysingerf7c51602019-06-18 17:23:39 -0400684 # See what version of python the hook has been written against.
685 data = open(self._script_fullpath).read()
686 interp = self._ExtractInterpFromShebang(data)
687 reexec = False
688 if interp:
689 prog = os.path.basename(interp)
690 if prog.startswith('python2') and sys.version_info.major != 2:
691 reexec = True
692 elif prog.startswith('python3') and sys.version_info.major == 2:
693 reexec = True
694
695 # Attempt to execute the hooks through the requested version of Python.
696 if reexec:
697 try:
698 self._ExecuteHookViaReexec(interp, context, **kwargs)
699 except OSError as e:
700 if e.errno == errno.ENOENT:
701 # We couldn't find the interpreter, so fallback to importing.
702 reexec = False
703 else:
704 raise
705
706 # Run the hook by importing directly.
707 if not reexec:
708 self._ExecuteHookViaImport(data, context, **kwargs)
Doug Anderson37282b42011-03-04 11:54:18 -0800709 finally:
710 # Restore sys.path and CWD.
711 sys.path = orig_syspath
712 os.chdir(orig_path)
713
714 def Run(self, user_allows_all_hooks, **kwargs):
715 """Run the hook.
716
717 If the hook doesn't exist (because there is no hooks project or because
718 this particular hook is not enabled), this is a no-op.
719
720 Args:
721 user_allows_all_hooks: If True, we will never prompt about running the
722 hook--we'll just assume it's OK to run it.
723 kwargs: Keyword arguments to pass to the hook. These are often specific
724 to the hook type. For instance, pre-upload hooks will contain
725 a project_list.
726
727 Raises:
728 HookError: If there was a problem finding the hook or the user declined
729 to run a required hook (from _CheckForHookApproval).
730 """
731 # No-op if there is no hooks project or if hook is disabled.
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700732 if ((not self._hooks_project) or (self._hook_type not in
733 self._hooks_project.enabled_repo_hooks)):
Doug Anderson37282b42011-03-04 11:54:18 -0800734 return
735
736 # Bail with a nice error if we can't find the hook.
737 if not os.path.isfile(self._script_fullpath):
738 raise HookError('Couldn\'t find repo hook: "%s"' % self._script_fullpath)
739
740 # Make sure the user is OK with running the hook.
741 if (not user_allows_all_hooks) and (not self._CheckForHookApproval()):
742 return
743
744 # Run the hook with the same version of python we're using.
745 self._ExecuteHook(**kwargs)
746
747
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748class Project(object):
Kevin Degi384b3c52014-10-16 16:02:58 -0600749 # These objects can be shared between several working trees.
750 shareable_files = ['description', 'info']
751 shareable_dirs = ['hooks', 'objects', 'rr-cache', 'svn']
752 # These objects can only be used by a single working tree.
753 working_tree_files = ['config', 'packed-refs', 'shallow']
754 working_tree_dirs = ['logs', 'refs']
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700755
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756 def __init__(self,
757 manifest,
758 name,
759 remote,
760 gitdir,
David James8d201162013-10-11 17:03:19 -0700761 objdir,
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700762 worktree,
763 relpath,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700764 revisionExpr,
Mike Pontillod3153822012-02-28 11:53:24 -0800765 revisionId,
Anthony King7bdac712014-07-16 12:56:40 +0100766 rebase=True,
767 groups=None,
768 sync_c=False,
769 sync_s=False,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900770 sync_tags=True,
Anthony King7bdac712014-07-16 12:56:40 +0100771 clone_depth=None,
772 upstream=None,
773 parent=None,
774 is_derived=False,
David Pursehouseb1553542014-09-04 21:28:09 +0900775 dest_branch=None,
Simran Basib9a1b732015-08-20 12:19:28 -0700776 optimized_fetch=False,
777 old_revision=None):
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800778 """Init a Project object.
779
780 Args:
781 manifest: The XmlManifest object.
782 name: The `name` attribute of manifest.xml's project element.
783 remote: RemoteSpec object specifying its remote's properties.
784 gitdir: Absolute path of git directory.
David James8d201162013-10-11 17:03:19 -0700785 objdir: Absolute path of directory to store git objects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800786 worktree: Absolute path of git working tree.
787 relpath: Relative path of git working tree to repo's top directory.
788 revisionExpr: The `revision` attribute of manifest.xml's project element.
789 revisionId: git commit id for checking out.
790 rebase: The `rebase` attribute of manifest.xml's project element.
791 groups: The `groups` attribute of manifest.xml's project element.
792 sync_c: The `sync-c` attribute of manifest.xml's project element.
793 sync_s: The `sync-s` attribute of manifest.xml's project element.
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900794 sync_tags: The `sync-tags` attribute of manifest.xml's project element.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800795 upstream: The `upstream` attribute of manifest.xml's project element.
796 parent: The parent Project object.
797 is_derived: False if the project was explicitly defined in the manifest;
798 True if the project is a discovered submodule.
Bryan Jacobsf609f912013-05-06 13:36:24 -0400799 dest_branch: The branch to which to push changes for review by default.
David Pursehouseb1553542014-09-04 21:28:09 +0900800 optimized_fetch: If True, when a project is set to a sha1 revision, only
801 fetch from the remote if the sha1 is not present locally.
Simran Basib9a1b732015-08-20 12:19:28 -0700802 old_revision: saved git commit id for open GITC projects.
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800803 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700804 self.manifest = manifest
805 self.name = name
806 self.remote = remote
Anthony Newnamdf14a702011-01-09 17:31:57 -0800807 self.gitdir = gitdir.replace('\\', '/')
David James8d201162013-10-11 17:03:19 -0700808 self.objdir = objdir.replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800809 if worktree:
Renaud Paquayfef9f212016-11-01 18:28:01 -0700810 self.worktree = os.path.normpath(worktree).replace('\\', '/')
Shawn O. Pearce0ce6ca92011-01-10 13:26:01 -0800811 else:
812 self.worktree = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700813 self.relpath = relpath
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700814 self.revisionExpr = revisionExpr
815
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700816 if revisionId is None \
817 and revisionExpr \
818 and IsId(revisionExpr):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700819 self.revisionId = revisionExpr
820 else:
821 self.revisionId = revisionId
822
Mike Pontillod3153822012-02-28 11:53:24 -0800823 self.rebase = rebase
Colin Cross5acde752012-03-28 20:15:45 -0700824 self.groups = groups
Anatol Pomazau79770d22012-04-20 14:41:59 -0700825 self.sync_c = sync_c
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800826 self.sync_s = sync_s
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +0900827 self.sync_tags = sync_tags
David Pursehouseede7f122012-11-27 22:25:30 +0900828 self.clone_depth = clone_depth
Brian Harring14a66742012-09-28 20:21:57 -0700829 self.upstream = upstream
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800830 self.parent = parent
831 self.is_derived = is_derived
David Pursehouseb1553542014-09-04 21:28:09 +0900832 self.optimized_fetch = optimized_fetch
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800833 self.subprojects = []
Mike Pontillod3153822012-02-28 11:53:24 -0800834
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700835 self.snapshots = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 self.copyfiles = []
Jeff Hamiltone0df2322014-04-21 17:10:59 -0500837 self.linkfiles = []
James W. Mills24c13082012-04-12 15:04:13 -0500838 self.annotations = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700839 self.config = GitConfig.ForRepository(gitdir=self.gitdir,
840 defaults=self.manifest.globalConfig)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700841
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800842 if self.worktree:
David James8d201162013-10-11 17:03:19 -0700843 self.work_git = self._GitGetByExec(self, bare=False, gitdir=gitdir)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800844 else:
845 self.work_git = None
David James8d201162013-10-11 17:03:19 -0700846 self.bare_git = self._GitGetByExec(self, bare=True, gitdir=gitdir)
Shawn O. Pearced237b692009-04-17 18:49:50 -0700847 self.bare_ref = GitRefs(gitdir)
David James8d201162013-10-11 17:03:19 -0700848 self.bare_objdir = self._GitGetByExec(self, bare=True, gitdir=objdir)
Bryan Jacobsf609f912013-05-06 13:36:24 -0400849 self.dest_branch = dest_branch
Simran Basib9a1b732015-08-20 12:19:28 -0700850 self.old_revision = old_revision
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700851
Doug Anderson37282b42011-03-04 11:54:18 -0800852 # This will be filled in if a project is later identified to be the
853 # project containing repo hooks.
854 self.enabled_repo_hooks = []
855
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700856 @property
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800857 def Derived(self):
858 return self.is_derived
859
860 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700861 def Exists(self):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700862 return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700863
864 @property
865 def CurrentBranch(self):
866 """Obtain the name of the currently checked out branch.
867 The branch name omits the 'refs/heads/' prefix.
868 None is returned if the project is on a detached HEAD.
869 """
Shawn O. Pearce5b23f242009-04-17 18:43:33 -0700870 b = self.work_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700871 if b.startswith(R_HEADS):
872 return b[len(R_HEADS):]
873 return None
874
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -0700875 def IsRebaseInProgress(self):
876 w = self.worktree
877 g = os.path.join(w, '.git')
878 return os.path.exists(os.path.join(g, 'rebase-apply')) \
879 or os.path.exists(os.path.join(g, 'rebase-merge')) \
880 or os.path.exists(os.path.join(w, '.dotest'))
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +0200881
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700882 def IsDirty(self, consider_untracked=True):
883 """Is the working directory modified in some way?
884 """
885 self.work_git.update_index('-q',
886 '--unmerged',
887 '--ignore-missing',
888 '--refresh')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900889 if self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700890 return True
891 if self.work_git.DiffZ('diff-files'):
892 return True
893 if consider_untracked and self.work_git.LsOthers():
894 return True
895 return False
896
897 _userident_name = None
898 _userident_email = None
899
900 @property
901 def UserName(self):
902 """Obtain the user's personal name.
903 """
904 if self._userident_name is None:
905 self._LoadUserIdentity()
906 return self._userident_name
907
908 @property
909 def UserEmail(self):
910 """Obtain the user's email address. This is very likely
911 to be their Gerrit login.
912 """
913 if self._userident_email is None:
914 self._LoadUserIdentity()
915 return self._userident_email
916
917 def _LoadUserIdentity(self):
David Pursehousec1b86a22012-11-14 11:36:51 +0900918 u = self.bare_git.var('GIT_COMMITTER_IDENT')
919 m = re.compile("^(.*) <([^>]*)> ").match(u)
920 if m:
921 self._userident_name = m.group(1)
922 self._userident_email = m.group(2)
923 else:
924 self._userident_name = ''
925 self._userident_email = ''
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
927 def GetRemote(self, name):
928 """Get the configuration for a single remote.
929 """
930 return self.config.GetRemote(name)
931
932 def GetBranch(self, name):
933 """Get the configuration for a single branch.
934 """
935 return self.config.GetBranch(name)
936
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700937 def GetBranches(self):
938 """Get all existing local branches.
939 """
940 current = self.CurrentBranch
David Pursehouse8a68ff92012-09-24 12:15:13 +0900941 all_refs = self._allrefs
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700942 heads = {}
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700943
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530944 for name, ref_id in all_refs.items():
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700945 if name.startswith(R_HEADS):
946 name = name[len(R_HEADS):]
947 b = self.GetBranch(name)
948 b.current = name == current
949 b.published = None
David Pursehouse8a68ff92012-09-24 12:15:13 +0900950 b.revision = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700951 heads[name] = b
952
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_PUB):
955 name = name[len(R_PUB):]
956 b = heads.get(name)
957 if b:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900958 b.published = ref_id
Shawn O. Pearce27b07322009-04-10 16:02:48 -0700959
960 return heads
961
Colin Cross5acde752012-03-28 20:15:45 -0700962 def MatchesGroups(self, manifest_groups):
963 """Returns true if the manifest groups specified at init should cause
964 this project to be synced.
965 Prefixing a manifest group with "-" inverts the meaning of a group.
Conley Owensbb1b5f52012-08-13 13:11:18 -0700966 All projects are implicitly labelled with "all".
Colin Cross5acde752012-03-28 20:15:45 -0700967
Conley Owens971de8e2012-04-16 10:36:08 -0700968 labels are resolved in order. In the example case of
Conley Owensbb1b5f52012-08-13 13:11:18 -0700969 project_groups: "all,group1,group2"
Conley Owens971de8e2012-04-16 10:36:08 -0700970 manifest_groups: "-group1,group2"
971 the project will be matched.
David Holmer0a1c6a12012-11-14 19:19:00 -0500972
973 The special manifest group "default" will match any project that
974 does not have the special project group "notdefault"
Conley Owens971de8e2012-04-16 10:36:08 -0700975 """
David Holmer0a1c6a12012-11-14 19:19:00 -0500976 expanded_manifest_groups = manifest_groups or ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700977 expanded_project_groups = ['all'] + (self.groups or [])
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700978 if 'notdefault' not in expanded_project_groups:
David Holmer0a1c6a12012-11-14 19:19:00 -0500979 expanded_project_groups += ['default']
Conley Owensbb1b5f52012-08-13 13:11:18 -0700980
Conley Owens971de8e2012-04-16 10:36:08 -0700981 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700982 for group in expanded_manifest_groups:
983 if group.startswith('-') and group[1:] in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700984 matched = False
Conley Owensbb1b5f52012-08-13 13:11:18 -0700985 elif group in expanded_project_groups:
Conley Owens971de8e2012-04-16 10:36:08 -0700986 matched = True
Colin Cross5acde752012-03-28 20:15:45 -0700987
Conley Owens971de8e2012-04-16 10:36:08 -0700988 return matched
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -0700990# Status Display ##
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700991 def UncommitedFiles(self, get_all=True):
992 """Returns a list of strings, uncommitted files in the git tree.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700993
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700994 Args:
995 get_all: a boolean, if True - get information about all different
996 uncommitted files. If False - return as soon as any kind of
997 uncommitted files is detected.
Anthony Newnamcc50bac2010-04-08 10:28:59 -0500998 """
Vadim Bendebury14e134d2014-10-05 15:40:30 -0700999 details = []
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001000 self.work_git.update_index('-q',
1001 '--unmerged',
1002 '--ignore-missing',
1003 '--refresh')
1004 if self.IsRebaseInProgress():
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001005 details.append("rebase in progress")
1006 if not get_all:
1007 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001008
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001009 changes = self.work_git.DiffZ('diff-index', '--cached', HEAD).keys()
1010 if changes:
1011 details.extend(changes)
1012 if not get_all:
1013 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001014
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001015 changes = self.work_git.DiffZ('diff-files').keys()
1016 if changes:
1017 details.extend(changes)
1018 if not get_all:
1019 return details
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001020
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001021 changes = self.work_git.LsOthers()
1022 if changes:
1023 details.extend(changes)
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001024
Vadim Bendebury14e134d2014-10-05 15:40:30 -07001025 return details
1026
1027 def HasChanges(self):
1028 """Returns true if there are uncommitted changes.
1029 """
1030 if self.UncommitedFiles(get_all=False):
1031 return True
1032 else:
1033 return False
Anthony Newnamcc50bac2010-04-08 10:28:59 -05001034
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001035 def PrintWorkTreeStatus(self, output_redir=None, quiet=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001036 """Prints the status of the repository to stdout.
Terence Haddock4655e812011-03-31 12:33:34 +02001037
1038 Args:
1039 output: If specified, redirect the output to this object.
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001040 quiet: If True then only print the project name. Do not print
1041 the modified files, branch name, etc.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001042 """
Renaud Paquaybed8b622018-09-27 10:46:58 -07001043 if not platform_utils.isdir(self.worktree):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001044 if output_redir is None:
Terence Haddock4655e812011-03-31 12:33:34 +02001045 output_redir = sys.stdout
Sarah Owenscecd1d82012-11-01 22:59:27 -07001046 print(file=output_redir)
1047 print('project %s/' % self.relpath, file=output_redir)
1048 print(' missing (run "repo sync")', file=output_redir)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001049 return
1050
1051 self.work_git.update_index('-q',
1052 '--unmerged',
1053 '--ignore-missing',
1054 '--refresh')
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001055 rb = self.IsRebaseInProgress()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001056 di = self.work_git.DiffZ('diff-index', '-M', '--cached', HEAD)
1057 df = self.work_git.DiffZ('diff-files')
1058 do = self.work_git.LsOthers()
Ali Utku Selen76abcc12012-01-25 10:51:12 +01001059 if not rb and not di and not df and not do and not self.CurrentBranch:
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001060 return 'CLEAN'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001061
1062 out = StatusColoring(self.config)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001063 if output_redir is not None:
Terence Haddock4655e812011-03-31 12:33:34 +02001064 out.redirect(output_redir)
Jakub Vrana0402cd82014-09-09 15:39:15 -07001065 out.project('project %-40s', self.relpath + '/ ')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001066
Andrew Wheeler4d5bb682012-02-27 13:52:22 -06001067 if quiet:
1068 out.nl()
1069 return 'DIRTY'
1070
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071 branch = self.CurrentBranch
1072 if branch is None:
1073 out.nobranch('(*** NO BRANCH ***)')
1074 else:
1075 out.branch('branch %s', branch)
1076 out.nl()
1077
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001078 if rb:
1079 out.important('prior sync failed; rebase still in progress')
1080 out.nl()
1081
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082 paths = list()
1083 paths.extend(di.keys())
1084 paths.extend(df.keys())
1085 paths.extend(do)
1086
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301087 for p in sorted(set(paths)):
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001088 try:
1089 i = di[p]
1090 except KeyError:
1091 i = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001092
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001093 try:
1094 f = df[p]
1095 except KeyError:
1096 f = None
Julius Gustavsson0cb1b3f2010-06-17 17:55:02 +02001097
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001098 if i:
1099 i_status = i.status.upper()
1100 else:
1101 i_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
David Pursehouse5c6eeac2012-10-11 16:44:48 +09001103 if f:
1104 f_status = f.status.lower()
1105 else:
1106 f_status = '-'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001107
1108 if i and i.src_path:
Shawn O. Pearcefe086752009-03-03 13:49:48 -08001109 line = ' %s%s\t%s => %s (%s%%)' % (i_status, f_status,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001110 i.src_path, p, i.level)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111 else:
1112 line = ' %s%s\t%s' % (i_status, f_status, p)
1113
1114 if i and not f:
1115 out.added('%s', line)
1116 elif (i and f) or (not i and f):
1117 out.changed('%s', line)
1118 elif not i and not f:
1119 out.untracked('%s', line)
1120 else:
1121 out.write('%s', line)
1122 out.nl()
Terence Haddock4655e812011-03-31 12:33:34 +02001123
Shawn O. Pearce161f4452009-04-10 17:41:44 -07001124 return 'DIRTY'
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001125
pelyad67872d2012-03-28 14:49:58 +03001126 def PrintWorkTreeDiff(self, absolute_paths=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001127 """Prints the status of the repository to stdout.
1128 """
1129 out = DiffColoring(self.config)
1130 cmd = ['diff']
1131 if out.is_on:
1132 cmd.append('--color')
1133 cmd.append(HEAD)
pelyad67872d2012-03-28 14:49:58 +03001134 if absolute_paths:
1135 cmd.append('--src-prefix=a/%s/' % self.relpath)
1136 cmd.append('--dst-prefix=b/%s/' % self.relpath)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001137 cmd.append('--')
1138 p = GitCommand(self,
1139 cmd,
Anthony King7bdac712014-07-16 12:56:40 +01001140 capture_stdout=True,
1141 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001142 has_diff = False
1143 for line in p.process.stdout:
Mike Frysinger600f4922019-08-03 02:14:28 -04001144 if not hasattr(line, 'encode'):
1145 line = line.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001146 if not has_diff:
1147 out.nl()
1148 out.project('project %s/' % self.relpath)
1149 out.nl()
1150 has_diff = True
Sarah Owenscecd1d82012-11-01 22:59:27 -07001151 print(line[:-1])
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001152 p.Wait()
1153
1154
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001155# Publish / Upload ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001156
David Pursehouse8a68ff92012-09-24 12:15:13 +09001157 def WasPublished(self, branch, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001158 """Was the branch published (uploaded) for code review?
1159 If so, returns the SHA-1 hash of the last published
1160 state for the branch.
1161 """
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001162 key = R_PUB + branch
David Pursehouse8a68ff92012-09-24 12:15:13 +09001163 if all_refs is None:
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001164 try:
1165 return self.bare_git.rev_parse(key)
1166 except GitError:
1167 return None
1168 else:
1169 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001170 return all_refs[key]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001171 except KeyError:
1172 return None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001173
David Pursehouse8a68ff92012-09-24 12:15:13 +09001174 def CleanPublishedCache(self, all_refs=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001175 """Prunes any stale published refs.
1176 """
David Pursehouse8a68ff92012-09-24 12:15:13 +09001177 if all_refs is None:
1178 all_refs = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001179 heads = set()
1180 canrm = {}
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301181 for name, ref_id in all_refs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001182 if name.startswith(R_HEADS):
1183 heads.add(name)
1184 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001185 canrm[name] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001186
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301187 for name, ref_id in canrm.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001188 n = name[len(R_PUB):]
1189 if R_HEADS + n not in heads:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001190 self.bare_git.DeleteRef(name, ref_id)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001191
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001192 def GetUploadableBranches(self, selected_branch=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 """List any branches which can be uploaded for review.
1194 """
1195 heads = {}
1196 pubed = {}
1197
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301198 for name, ref_id in self._allrefs.items():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001199 if name.startswith(R_HEADS):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001200 heads[name[len(R_HEADS):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001201 elif name.startswith(R_PUB):
David Pursehouse8a68ff92012-09-24 12:15:13 +09001202 pubed[name[len(R_PUB):]] = ref_id
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001203
1204 ready = []
Chirayu Desai217ea7d2013-03-01 19:14:38 +05301205 for branch, ref_id in heads.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09001206 if branch in pubed and pubed[branch] == ref_id:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001207 continue
Mandeep Singh Bainesd6c93a22011-05-26 10:34:11 -07001208 if selected_branch and branch != selected_branch:
1209 continue
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001210
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001211 rb = self.GetUploadableBranch(branch)
1212 if rb:
1213 ready.append(rb)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001214 return ready
1215
Shawn O. Pearce35f25962008-11-11 17:03:13 -08001216 def GetUploadableBranch(self, branch_name):
1217 """Get a single uploadable branch, or None.
1218 """
1219 branch = self.GetBranch(branch_name)
1220 base = branch.LocalMerge
1221 if branch.LocalMerge:
1222 rb = ReviewableBranch(self, branch, base)
1223 if rb.commits:
1224 return rb
1225 return None
1226
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001227 def UploadForReview(self, branch=None,
Anthony King7bdac712014-07-16 12:56:40 +01001228 people=([], []),
Brian Harring435370c2012-07-28 15:37:04 -07001229 auto_topic=False,
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001230 draft=False,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001231 private=False,
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001232 notify=None,
Changcheng Xiao87984c62017-08-02 16:55:03 +02001233 wip=False,
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001234 dest_branch=None,
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001235 validate_certs=True,
1236 push_options=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001237 """Uploads the named branch for code review.
1238 """
1239 if branch is None:
1240 branch = self.CurrentBranch
1241 if branch is None:
1242 raise GitError('not currently on a branch')
1243
1244 branch = self.GetBranch(branch)
1245 if not branch.LocalMerge:
1246 raise GitError('branch %s does not track a remote' % branch.name)
1247 if not branch.remote.review:
1248 raise GitError('remote %s has no review url' % branch.remote.name)
1249
Bryan Jacobsf609f912013-05-06 13:36:24 -04001250 if dest_branch is None:
1251 dest_branch = self.dest_branch
1252 if dest_branch is None:
1253 dest_branch = branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001254 if not dest_branch.startswith(R_HEADS):
1255 dest_branch = R_HEADS + dest_branch
1256
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -08001257 if not branch.remote.projectname:
1258 branch.remote.projectname = self.name
1259 branch.remote.Save()
1260
Łukasz Gardońbed59ce2017-08-08 10:18:11 +02001261 url = branch.remote.ReviewUrl(self.UserEmail, validate_certs)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001262 if url is None:
1263 raise UploadError('review not configured')
1264 cmd = ['push']
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001265
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001266 if url.startswith('ssh://'):
Jonathan Nieder713c5872018-11-05 13:21:52 -08001267 cmd.append('--receive-pack=gerrit receive-pack')
Shawn O. Pearcea5ece0e2010-07-15 16:52:42 -07001268
Masaya Suzuki305a2d02017-11-13 10:48:34 -08001269 for push_option in (push_options or []):
1270 cmd.append('-o')
1271 cmd.append(push_option)
1272
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001273 cmd.append(url)
1274
1275 if dest_branch.startswith(R_HEADS):
1276 dest_branch = dest_branch[len(R_HEADS):]
Brian Harring435370c2012-07-28 15:37:04 -07001277
Jonathan Niederc94d6eb2017-08-08 18:34:53 +00001278 upload_type = 'for'
1279 if draft:
1280 upload_type = 'drafts'
1281
1282 ref_spec = '%s:refs/%s/%s' % (R_HEADS + branch.name, upload_type,
1283 dest_branch)
David Pursehousef25a3702018-11-14 19:01:22 -08001284 opts = []
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001285 if auto_topic:
David Pursehousef25a3702018-11-14 19:01:22 -08001286 opts += ['topic=' + branch.name]
Changcheng Xiao87984c62017-08-02 16:55:03 +02001287
David Pursehousef25a3702018-11-14 19:01:22 -08001288 opts += ['r=%s' % p for p in people[0]]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001289 opts += ['cc=%s' % p for p in people[1]]
Vadim Bendeburybd8f6582018-10-31 13:48:01 -07001290 if notify:
1291 opts += ['notify=' + notify]
Jonathan Nieder713c5872018-11-05 13:21:52 -08001292 if private:
1293 opts += ['private']
1294 if wip:
1295 opts += ['wip']
1296 if opts:
1297 ref_spec = ref_spec + '%' + ','.join(opts)
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001298 cmd.append(ref_spec)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -08001299
Anthony King7bdac712014-07-16 12:56:40 +01001300 if GitCommand(self, cmd, bare=True).Wait() != 0:
Shawn O. Pearcec9571422012-01-11 14:58:54 -08001301 raise UploadError('Upload failed')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001302
1303 msg = "posted to %s for %s" % (branch.remote.review, dest_branch)
1304 self.bare_git.UpdateRef(R_PUB + branch.name,
1305 R_HEADS + branch.name,
Anthony King7bdac712014-07-16 12:56:40 +01001306 message=msg)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001307
1308
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001309# Sync ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001310
Julien Campergue335f5ef2013-10-16 11:02:35 +02001311 def _ExtractArchive(self, tarpath, path=None):
1312 """Extract the given tar on its current location
1313
1314 Args:
1315 - tarpath: The path to the actual tar file
1316
1317 """
1318 try:
1319 with tarfile.open(tarpath, 'r') as tar:
1320 tar.extractall(path=path)
1321 return True
1322 except (IOError, tarfile.TarError) as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001323 _error("Cannot extract archive %s: %s", tarpath, str(e))
Julien Campergue335f5ef2013-10-16 11:02:35 +02001324 return False
1325
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001326 def Sync_NetworkHalf(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001327 quiet=False,
1328 is_new=None,
1329 current_branch_only=False,
1330 force_sync=False,
1331 clone_bundle=True,
1332 no_tags=False,
1333 archive=False,
1334 optimized_fetch=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07001335 prune=False,
Xin Li745be2e2019-06-03 11:24:30 -07001336 submodules=False,
1337 clone_filter=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001338 """Perform only the network IO portion of the sync process.
1339 Local working directory/branch state is not affected.
1340 """
Julien Campergue335f5ef2013-10-16 11:02:35 +02001341 if archive and not isinstance(self, MetaProject):
1342 if self.remote.url.startswith(('http://', 'https://')):
David Pursehousef33929d2015-08-24 14:39:14 +09001343 _error("%s: Cannot fetch archives from http/https remotes.", self.name)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001344 return False
1345
1346 name = self.relpath.replace('\\', '/')
1347 name = name.replace('/', '_')
1348 tarpath = '%s.tar' % name
1349 topdir = self.manifest.topdir
1350
1351 try:
1352 self._FetchArchive(tarpath, cwd=topdir)
1353 except GitError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001354 _error('%s', e)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001355 return False
1356
1357 # From now on, we only need absolute tarpath
1358 tarpath = os.path.join(topdir, tarpath)
1359
1360 if not self._ExtractArchive(tarpath, path=topdir):
1361 return False
1362 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001363 platform_utils.remove(tarpath)
Julien Campergue335f5ef2013-10-16 11:02:35 +02001364 except OSError as e:
David Pursehousef33929d2015-08-24 14:39:14 +09001365 _warn("Cannot remove archive %s: %s", tarpath, str(e))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001366 self._CopyAndLinkFiles()
Julien Campergue335f5ef2013-10-16 11:02:35 +02001367 return True
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07001368 if is_new is None:
1369 is_new = not self.Exists
Shawn O. Pearce88443382010-10-08 10:02:09 +02001370 if is_new:
Kevin Degiabaa7f32014-11-12 11:27:45 -07001371 self._InitGitDir(force_sync=force_sync)
Jimmie Westera0444582012-10-24 13:44:42 +02001372 else:
1373 self._UpdateHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001374 self._InitRemote()
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001375
1376 if is_new:
1377 alt = os.path.join(self.gitdir, 'objects/info/alternates')
1378 try:
Renaud Paquay2a4be942016-11-01 13:48:15 -07001379 fd = open(alt)
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001380 try:
Samuel Hollandbaa00092018-01-22 10:57:29 -06001381 # This works for both absolute and relative alternate directories.
1382 alt_dir = os.path.join(self.objdir, 'objects', fd.readline().rstrip())
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001383 finally:
1384 fd.close()
1385 except IOError:
1386 alt_dir = None
1387 else:
1388 alt_dir = None
1389
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -07001390 if clone_bundle \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001391 and alt_dir is None \
1392 and self._ApplyCloneBundle(initial=is_new, quiet=quiet):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07001393 is_new = False
1394
Shawn O. Pearce6ba6ba02012-05-24 09:46:50 -07001395 if not current_branch_only:
1396 if self.sync_c:
1397 current_branch_only = True
1398 elif not self.manifest._loaded:
1399 # Manifest cannot check defaults until it syncs.
1400 current_branch_only = False
1401 elif self.manifest.default.sync_c:
1402 current_branch_only = True
1403
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09001404 if not no_tags:
1405 if not self.sync_tags:
1406 no_tags = True
1407
Aymen Bouaziz6c594462016-10-25 18:03:51 +02001408 if self.clone_depth:
1409 depth = self.clone_depth
1410 else:
1411 depth = self.manifest.manifestProject.config.GetString('repo.depth')
1412
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001413 need_to_fetch = not (optimized_fetch and
1414 (ID_RE.match(self.revisionExpr) and
Zac Livingstone4332262017-06-16 08:56:09 -06001415 self._CheckForImmutableRevision()))
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001416 if (need_to_fetch and
1417 not self._RemoteFetch(initial=is_new, quiet=quiet, alt_dir=alt_dir,
1418 current_branch_only=current_branch_only,
Martin Kellye4e94d22017-03-21 16:05:12 -07001419 no_tags=no_tags, prune=prune, depth=depth,
Xin Li745be2e2019-06-03 11:24:30 -07001420 submodules=submodules, force_sync=force_sync,
1421 clone_filter=clone_filter)):
Anthony King7bdac712014-07-16 12:56:40 +01001422 return False
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001423
Nikolai Merinov09f0abb2018-10-19 15:07:05 +05001424 mp = self.manifest.manifestProject
1425 dissociate = mp.config.GetBoolean('repo.dissociate')
1426 if dissociate:
1427 alternates_file = os.path.join(self.gitdir, 'objects/info/alternates')
1428 if os.path.exists(alternates_file):
1429 cmd = ['repack', '-a', '-d']
1430 if GitCommand(self, cmd, bare=True).Wait() != 0:
1431 return False
1432 platform_utils.remove(alternates_file)
1433
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001434 if self.worktree:
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001435 self._InitMRef()
1436 else:
1437 self._InitMirrorHead()
1438 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001439 platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'))
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08001440 except OSError:
1441 pass
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001442 return True
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001443
1444 def PostRepoUpgrade(self):
1445 self._InitHooks()
1446
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001447 def _CopyAndLinkFiles(self):
Simran Basib9a1b732015-08-20 12:19:28 -07001448 if self.manifest.isGitcClient:
1449 return
David Pursehouse8a68ff92012-09-24 12:15:13 +09001450 for copyfile in self.copyfiles:
1451 copyfile._Copy()
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001452 for linkfile in self.linkfiles:
1453 linkfile._Link()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001454
Julien Camperguedd654222014-01-09 16:21:37 +01001455 def GetCommitRevisionId(self):
1456 """Get revisionId of a commit.
1457
1458 Use this method instead of GetRevisionId to get the id of the commit rather
1459 than the id of the current git object (for example, a tag)
1460
1461 """
1462 if not self.revisionExpr.startswith(R_TAGS):
1463 return self.GetRevisionId(self._allrefs)
1464
1465 try:
1466 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1467 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001468 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1469 (self.revisionExpr, self.name))
Julien Camperguedd654222014-01-09 16:21:37 +01001470
David Pursehouse8a68ff92012-09-24 12:15:13 +09001471 def GetRevisionId(self, all_refs=None):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001472 if self.revisionId:
1473 return self.revisionId
1474
1475 rem = self.GetRemote(self.remote.name)
1476 rev = rem.ToLocal(self.revisionExpr)
1477
David Pursehouse8a68ff92012-09-24 12:15:13 +09001478 if all_refs is not None and rev in all_refs:
1479 return all_refs[rev]
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001480
1481 try:
1482 return self.bare_git.rev_parse('--verify', '%s^0' % rev)
1483 except GitError:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001484 raise ManifestInvalidRevisionError('revision %s in %s not found' %
1485 (self.revisionExpr, self.name))
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001486
Martin Kellye4e94d22017-03-21 16:05:12 -07001487 def Sync_LocalHalf(self, syncbuf, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001488 """Perform only the local IO portion of the sync process.
1489 Network access is not required.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001490 """
Martin Kellye4e94d22017-03-21 16:05:12 -07001491 self._InitWorkTree(force_sync=force_sync, submodules=submodules)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001492 all_refs = self.bare_ref.all
1493 self.CleanPublishedCache(all_refs)
1494 revid = self.GetRevisionId(all_refs)
Skyler Kaufman835cd682011-03-08 12:14:41 -08001495
David Pursehouse1d947b32012-10-25 12:23:11 +09001496 def _doff():
1497 self._FastForward(revid)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001498 self._CopyAndLinkFiles()
David Pursehouse1d947b32012-10-25 12:23:11 +09001499
Martin Kellye4e94d22017-03-21 16:05:12 -07001500 def _dosubmodules():
1501 self._SyncSubmodules(quiet=True)
1502
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001503 head = self.work_git.GetHead()
1504 if head.startswith(R_HEADS):
1505 branch = head[len(R_HEADS):]
1506 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001507 head = all_refs[head]
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001508 except KeyError:
1509 head = None
1510 else:
1511 branch = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001512
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001513 if branch is None or syncbuf.detach_head:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001514 # Currently on a detached HEAD. The user is assumed to
1515 # not have any local modifications worth worrying about.
1516 #
Shawn O. Pearce3d2cdd02009-04-18 15:26:10 -07001517 if self.IsRebaseInProgress():
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001518 syncbuf.fail(self, _PriorSyncFailedError())
1519 return
1520
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001521 if head == revid:
1522 # No changes; don't do anything further.
Florian Vallee7cf1b362012-06-07 17:11:42 +02001523 # Except if the head needs to be detached
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001524 #
Florian Vallee7cf1b362012-06-07 17:11:42 +02001525 if not syncbuf.detach_head:
Dan Willemsen029eaf32015-09-03 12:52:28 -07001526 # The copy/linkfile config may have changed.
1527 self._CopyAndLinkFiles()
Florian Vallee7cf1b362012-06-07 17:11:42 +02001528 return
1529 else:
1530 lost = self._revlist(not_rev(revid), HEAD)
1531 if lost:
1532 syncbuf.info(self, "discarding %d commits", len(lost))
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001533
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001534 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001535 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001536 if submodules:
1537 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001538 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001539 syncbuf.fail(self, e)
1540 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001541 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001542 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001543
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001544 if head == revid:
1545 # No changes; don't do anything further.
1546 #
Dan Willemsen029eaf32015-09-03 12:52:28 -07001547 # The copy/linkfile config may have changed.
1548 self._CopyAndLinkFiles()
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07001549 return
1550
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001551 branch = self.GetBranch(branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001552
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001553 if not branch.LocalMerge:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001554 # The current branch has no tracking configuration.
Anatol Pomazau2a32f6a2011-08-30 10:52:33 -07001555 # Jump off it to a detached HEAD.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001556 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001557 syncbuf.info(self,
1558 "leaving %s; does not track upstream",
1559 branch.name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001560 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001561 self._Checkout(revid, quiet=True)
Martin Kellye4e94d22017-03-21 16:05:12 -07001562 if submodules:
1563 self._SyncSubmodules(quiet=True)
Sarah Owensa5be53f2012-09-09 15:37:57 -07001564 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001565 syncbuf.fail(self, e)
1566 return
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001567 self._CopyAndLinkFiles()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001568 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001569
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001570 upstream_gain = self._revlist(not_rev(HEAD), revid)
David Pursehouse8a68ff92012-09-24 12:15:13 +09001571 pub = self.WasPublished(branch.name, all_refs)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001572 if pub:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001573 not_merged = self._revlist(not_rev(revid), pub)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001574 if not_merged:
1575 if upstream_gain:
1576 # The user has published this branch and some of those
1577 # commits are not yet merged upstream. We do not want
1578 # to rewrite the published commits so we punt.
1579 #
Daniel Sandler4c50dee2010-03-02 15:38:03 -05001580 syncbuf.fail(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001581 "branch %s is published (but not merged) and is now "
1582 "%d commits behind" % (branch.name, len(upstream_gain)))
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001583 return
Shawn O. Pearce05f66b62009-04-21 08:26:32 -07001584 elif pub == head:
1585 # All published commits are merged, and thus we are a
1586 # strict subset. We can fast-forward safely.
Shawn O. Pearcea54c5272008-10-30 11:03:00 -07001587 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001588 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001589 if submodules:
1590 syncbuf.later1(self, _dosubmodules)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001591 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001592
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001593 # Examine the local commits not in the remote. Find the
1594 # last one attributed to this user, if any.
1595 #
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001596 local_changes = self._revlist(not_rev(revid), HEAD, format='%H %ce')
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001597 last_mine = None
1598 cnt_mine = 0
1599 for commit in local_changes:
Mike Frysinger600f4922019-08-03 02:14:28 -04001600 commit_id, committer_email = commit.split(' ', 1)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001601 if committer_email == self.UserEmail:
1602 last_mine = commit_id
1603 cnt_mine += 1
1604
Shawn O. Pearceda88ff42009-06-03 11:09:12 -07001605 if not upstream_gain and cnt_mine == len(local_changes):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001606 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001607
1608 if self.IsDirty(consider_untracked=False):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001609 syncbuf.fail(self, _DirtyError())
1610 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001611
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001612 # If the upstream switched on us, warn the user.
1613 #
1614 if branch.merge != self.revisionExpr:
1615 if branch.merge and self.revisionExpr:
1616 syncbuf.info(self,
1617 'manifest switched %s...%s',
1618 branch.merge,
1619 self.revisionExpr)
1620 elif branch.merge:
1621 syncbuf.info(self,
1622 'manifest no longer tracks %s',
1623 branch.merge)
1624
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001625 if cnt_mine < len(local_changes):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001626 # Upstream rebased. Not everything in HEAD
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001627 # was created by this user.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001628 #
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001629 syncbuf.info(self,
1630 "discarding %d commits removed from upstream",
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001631 len(local_changes) - cnt_mine)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001632
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001633 branch.remote = self.GetRemote(self.remote.name)
Anatol Pomazaucd7c5de2012-03-20 13:45:00 -07001634 if not ID_RE.match(self.revisionExpr):
1635 # in case of manifest sync the revisionExpr might be a SHA1
1636 branch.merge = self.revisionExpr
Conley Owens04f2f0e2014-10-01 17:22:46 -07001637 if not branch.merge.startswith('refs/'):
1638 branch.merge = R_HEADS + branch.merge
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001639 branch.Save()
1640
Mike Pontillod3153822012-02-28 11:53:24 -08001641 if cnt_mine > 0 and self.rebase:
Martin Kellye4e94d22017-03-21 16:05:12 -07001642 def _docopyandlink():
1643 self._CopyAndLinkFiles()
1644
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001645 def _dorebase():
Anthony King7bdac712014-07-16 12:56:40 +01001646 self._Rebase(upstream='%s^1' % last_mine, onto=revid)
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001647 syncbuf.later2(self, _dorebase)
Martin Kellye4e94d22017-03-21 16:05:12 -07001648 if submodules:
1649 syncbuf.later2(self, _dosubmodules)
1650 syncbuf.later2(self, _docopyandlink)
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07001651 elif local_changes:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001652 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001653 self._ResetHard(revid)
Martin Kellye4e94d22017-03-21 16:05:12 -07001654 if submodules:
1655 self._SyncSubmodules(quiet=True)
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001656 self._CopyAndLinkFiles()
Sarah Owensa5be53f2012-09-09 15:37:57 -07001657 except GitError as e:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001658 syncbuf.fail(self, e)
1659 return
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001660 else:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001661 syncbuf.later1(self, _doff)
Martin Kellye4e94d22017-03-21 16:05:12 -07001662 if submodules:
1663 syncbuf.later1(self, _dosubmodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001664
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001665 def AddCopyFile(self, src, dest, absdest):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001666 # dest should already be an absolute path, but src is project relative
1667 # make src an absolute path
Shawn O. Pearcec7a4eef2009-03-05 10:32:38 -08001668 abssrc = os.path.join(self.worktree, src)
1669 self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001670
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001671 def AddLinkFile(self, src, dest, absdest):
1672 # dest should already be an absolute path, but src is project relative
Colin Cross0184dcc2015-05-05 00:24:54 -07001673 # make src relative path to dest
1674 absdestdir = os.path.dirname(absdest)
1675 relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir)
Wink Saville4c426ef2015-06-03 08:05:17 -07001676 self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest))
Jeff Hamiltone0df2322014-04-21 17:10:59 -05001677
James W. Mills24c13082012-04-12 15:04:13 -05001678 def AddAnnotation(self, name, value, keep):
1679 self.annotations.append(_Annotation(name, value, keep))
1680
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001681 def DownloadPatchSet(self, change_id, patch_id):
1682 """Download a single patch set of a single change to FETCH_HEAD.
1683 """
1684 remote = self.GetRemote(self.remote.name)
1685
1686 cmd = ['fetch', remote.name]
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001687 cmd.append('refs/changes/%2.2d/%d/%d'
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001688 % (change_id % 100, change_id, patch_id))
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001689 if GitCommand(self, cmd, bare=True).Wait() != 0:
1690 return None
1691 return DownloadedChange(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001692 self.GetRevisionId(),
Shawn O. Pearce632768b2008-10-23 11:58:52 -07001693 change_id,
1694 patch_id,
1695 self.bare_git.rev_parse('FETCH_HEAD'))
1696
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001697
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001698# Branch Management ##
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001699
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001700 def StartBranch(self, name, branch_merge='', revision=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001701 """Create a new branch off the manifest's revision.
1702 """
Simran Basib9a1b732015-08-20 12:19:28 -07001703 if not branch_merge:
1704 branch_merge = self.revisionExpr
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001705 head = self.work_git.GetHead()
1706 if head == (R_HEADS + name):
1707 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001708
David Pursehouse8a68ff92012-09-24 12:15:13 +09001709 all_refs = self.bare_ref.all
Anthony King7bdac712014-07-16 12:56:40 +01001710 if R_HEADS + name in all_refs:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001711 return GitCommand(self,
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001712 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001713 capture_stdout=True,
1714 capture_stderr=True).Wait() == 0
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001715
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001716 branch = self.GetBranch(name)
1717 branch.remote = self.GetRemote(self.remote.name)
Simran Basib9a1b732015-08-20 12:19:28 -07001718 branch.merge = branch_merge
1719 if not branch.merge.startswith('refs/') and not ID_RE.match(branch_merge):
1720 branch.merge = R_HEADS + branch_merge
Theodore Dubois60fdc5c2019-07-30 12:14:25 -07001721
1722 if revision is None:
1723 revid = self.GetRevisionId(all_refs)
1724 else:
1725 revid = self.work_git.rev_parse(revision)
Shawn O. Pearce0a389e92009-04-10 16:21:18 -07001726
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001727 if head.startswith(R_HEADS):
1728 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001729 head = all_refs[head]
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001730 except KeyError:
1731 head = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001732 if revid and head and revid == head:
1733 ref = os.path.join(self.gitdir, R_HEADS + name)
1734 try:
1735 os.makedirs(os.path.dirname(ref))
1736 except OSError:
1737 pass
1738 _lwrite(ref, '%s\n' % revid)
1739 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1740 'ref: %s%s\n' % (R_HEADS, name))
1741 branch.Save()
1742 return True
1743
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001744 if GitCommand(self,
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001745 ['checkout', '-b', branch.name, revid],
Anthony King7bdac712014-07-16 12:56:40 +01001746 capture_stdout=True,
1747 capture_stderr=True).Wait() == 0:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -07001748 branch.Save()
1749 return True
1750 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001751
Wink Saville02d79452009-04-10 13:01:24 -07001752 def CheckoutBranch(self, name):
1753 """Checkout a local topic branch.
Doug Anderson3ba5f952011-04-07 12:51:04 -07001754
1755 Args:
1756 name: The name of the branch to checkout.
1757
1758 Returns:
1759 True if the checkout succeeded; False if it didn't; None if the branch
1760 didn't exist.
Wink Saville02d79452009-04-10 13:01:24 -07001761 """
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001762 rev = R_HEADS + name
1763 head = self.work_git.GetHead()
1764 if head == rev:
1765 # Already on the branch
1766 #
1767 return True
Wink Saville02d79452009-04-10 13:01:24 -07001768
David Pursehouse8a68ff92012-09-24 12:15:13 +09001769 all_refs = self.bare_ref.all
Wink Saville02d79452009-04-10 13:01:24 -07001770 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001771 revid = all_refs[rev]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001772 except KeyError:
1773 # Branch does not exist in this project
1774 #
Doug Anderson3ba5f952011-04-07 12:51:04 -07001775 return None
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001776
1777 if head.startswith(R_HEADS):
1778 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09001779 head = all_refs[head]
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001780 except KeyError:
1781 head = None
1782
1783 if head == revid:
1784 # Same revision; just update HEAD to point to the new
1785 # target branch, but otherwise take no other action.
1786 #
1787 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1788 'ref: %s%s\n' % (R_HEADS, name))
1789 return True
Wink Saville02d79452009-04-10 13:01:24 -07001790
Shawn O. Pearce89e717d2009-04-18 15:04:41 -07001791 return GitCommand(self,
1792 ['checkout', name, '--'],
Anthony King7bdac712014-07-16 12:56:40 +01001793 capture_stdout=True,
1794 capture_stderr=True).Wait() == 0
Wink Saville02d79452009-04-10 13:01:24 -07001795
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001796 def AbandonBranch(self, name):
1797 """Destroy a local topic branch.
Doug Andersondafb1d62011-04-07 11:46:59 -07001798
1799 Args:
1800 name: The name of the branch to abandon.
1801
1802 Returns:
1803 True if the abandon succeeded; False if it didn't; None if the branch
1804 didn't exist.
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001805 """
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001806 rev = R_HEADS + name
David Pursehouse8a68ff92012-09-24 12:15:13 +09001807 all_refs = self.bare_ref.all
1808 if rev not in all_refs:
Doug Andersondafb1d62011-04-07 11:46:59 -07001809 # Doesn't exist
1810 return None
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001811
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001812 head = self.work_git.GetHead()
1813 if head == rev:
1814 # We can't destroy the branch while we are sitting
1815 # on it. Switch to a detached HEAD.
1816 #
David Pursehouse8a68ff92012-09-24 12:15:13 +09001817 head = all_refs[head]
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001818
David Pursehouse8a68ff92012-09-24 12:15:13 +09001819 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001820 if head == revid:
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001821 _lwrite(os.path.join(self.worktree, '.git', HEAD),
1822 '%s\n' % revid)
1823 else:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001824 self._Checkout(revid, quiet=True)
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001825
Shawn O. Pearce552ac892009-04-18 15:15:24 -07001826 return GitCommand(self,
1827 ['branch', '-D', name],
Anthony King7bdac712014-07-16 12:56:40 +01001828 capture_stdout=True,
1829 capture_stderr=True).Wait() == 0
Shawn O. Pearce9fa44db2008-11-03 11:24:59 -08001830
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001831 def PruneHeads(self):
1832 """Prune any topic branches already merged into upstream.
1833 """
1834 cb = self.CurrentBranch
1835 kill = []
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001836 left = self._allrefs
1837 for name in left.keys():
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001838 if name.startswith(R_HEADS):
1839 name = name[len(R_HEADS):]
1840 if cb is None or name != cb:
1841 kill.append(name)
1842
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001843 rev = self.GetRevisionId(left)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001844 if cb is not None \
1845 and not self._revlist(HEAD + '...' + rev) \
Anthony King7bdac712014-07-16 12:56:40 +01001846 and not self.IsDirty(consider_untracked=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001847 self.work_git.DetachHead(HEAD)
1848 kill.append(cb)
1849
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001850 if kill:
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07001851 old = self.bare_git.GetHead()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001852
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001853 try:
1854 self.bare_git.DetachHead(rev)
1855
1856 b = ['branch', '-d']
1857 b.extend(kill)
1858 b = GitCommand(self, b, bare=True,
1859 capture_stdout=True,
1860 capture_stderr=True)
1861 b.Wait()
1862 finally:
Dan Willemsen1a799d12015-12-15 13:40:05 -08001863 if ID_RE.match(old):
1864 self.bare_git.DetachHead(old)
1865 else:
1866 self.bare_git.SetHead(old)
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001867 left = self._allrefs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001868
Shawn O. Pearce3778f9d2009-03-02 12:30:50 -08001869 for branch in kill:
1870 if (R_HEADS + branch) not in left:
1871 self.CleanPublishedCache()
1872 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001873
1874 if cb and cb not in kill:
1875 kill.append(cb)
Shawn O. Pearce7c6c64d2009-03-02 12:38:13 -08001876 kill.sort()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001877
1878 kept = []
1879 for branch in kill:
Anthony King7bdac712014-07-16 12:56:40 +01001880 if R_HEADS + branch in left:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001881 branch = self.GetBranch(branch)
1882 base = branch.LocalMerge
1883 if not base:
1884 base = rev
1885 kept.append(ReviewableBranch(self, branch, base))
1886 return kept
1887
1888
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001889# Submodule Management ##
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001890
1891 def GetRegisteredSubprojects(self):
1892 result = []
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001893
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001894 def rec(subprojects):
1895 if not subprojects:
1896 return
1897 result.extend(subprojects)
1898 for p in subprojects:
1899 rec(p.subprojects)
1900 rec(self.subprojects)
1901 return result
1902
1903 def _GetSubmodules(self):
1904 # Unfortunately we cannot call `git submodule status --recursive` here
1905 # because the working tree might not exist yet, and it cannot be used
1906 # without a working tree in its current implementation.
1907
1908 def get_submodules(gitdir, rev):
1909 # Parse .gitmodules for submodule sub_paths and sub_urls
1910 sub_paths, sub_urls = parse_gitmodules(gitdir, rev)
1911 if not sub_paths:
1912 return []
1913 # Run `git ls-tree` to read SHAs of submodule object, which happen to be
1914 # revision of submodule repository
1915 sub_revs = git_ls_tree(gitdir, rev, sub_paths)
1916 submodules = []
1917 for sub_path, sub_url in zip(sub_paths, sub_urls):
1918 try:
1919 sub_rev = sub_revs[sub_path]
1920 except KeyError:
1921 # Ignore non-exist submodules
1922 continue
1923 submodules.append((sub_rev, sub_path, sub_url))
1924 return submodules
1925
Sebastian Schuberth41a26832019-03-11 13:53:38 +01001926 re_path = re.compile(r'^submodule\.(.+)\.path=(.*)$')
1927 re_url = re.compile(r'^submodule\.(.+)\.url=(.*)$')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07001928
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001929 def parse_gitmodules(gitdir, rev):
1930 cmd = ['cat-file', 'blob', '%s:.gitmodules' % rev]
1931 try:
Anthony King7bdac712014-07-16 12:56:40 +01001932 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1933 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001934 except GitError:
1935 return [], []
1936 if p.Wait() != 0:
1937 return [], []
1938
1939 gitmodules_lines = []
1940 fd, temp_gitmodules_path = tempfile.mkstemp()
1941 try:
1942 os.write(fd, p.stdout)
1943 os.close(fd)
1944 cmd = ['config', '--file', temp_gitmodules_path, '--list']
Anthony King7bdac712014-07-16 12:56:40 +01001945 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1946 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001947 if p.Wait() != 0:
1948 return [], []
1949 gitmodules_lines = p.stdout.split('\n')
1950 except GitError:
1951 return [], []
1952 finally:
Renaud Paquay010fed72016-11-11 14:25:29 -08001953 platform_utils.remove(temp_gitmodules_path)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001954
1955 names = set()
1956 paths = {}
1957 urls = {}
1958 for line in gitmodules_lines:
1959 if not line:
1960 continue
1961 m = re_path.match(line)
1962 if m:
1963 names.add(m.group(1))
1964 paths[m.group(1)] = m.group(2)
1965 continue
1966 m = re_url.match(line)
1967 if m:
1968 names.add(m.group(1))
1969 urls[m.group(1)] = m.group(2)
1970 continue
1971 names = sorted(names)
1972 return ([paths.get(name, '') for name in names],
1973 [urls.get(name, '') for name in names])
1974
1975 def git_ls_tree(gitdir, rev, paths):
1976 cmd = ['ls-tree', rev, '--']
1977 cmd.extend(paths)
1978 try:
Anthony King7bdac712014-07-16 12:56:40 +01001979 p = GitCommand(None, cmd, capture_stdout=True, capture_stderr=True,
1980 bare=True, gitdir=gitdir)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001981 except GitError:
1982 return []
1983 if p.Wait() != 0:
1984 return []
1985 objects = {}
1986 for line in p.stdout.split('\n'):
1987 if not line.strip():
1988 continue
1989 object_rev, object_path = line.split()[2:4]
1990 objects[object_path] = object_rev
1991 return objects
1992
1993 try:
1994 rev = self.GetRevisionId()
1995 except GitError:
1996 return []
1997 return get_submodules(self.gitdir, rev)
1998
1999 def GetDerivedSubprojects(self):
2000 result = []
2001 if not self.Exists:
2002 # If git repo does not exist yet, querying its submodules will
2003 # mess up its states; so return here.
2004 return result
2005 for rev, path, url in self._GetSubmodules():
2006 name = self.manifest.GetSubprojectName(self, path)
David James8d201162013-10-11 17:03:19 -07002007 relpath, worktree, gitdir, objdir = \
2008 self.manifest.GetSubprojectPaths(self, name, path)
2009 project = self.manifest.paths.get(relpath)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002010 if project:
2011 result.extend(project.GetDerivedSubprojects())
2012 continue
David James8d201162013-10-11 17:03:19 -07002013
Shouheng Zhang02c0ee62017-12-08 09:54:58 +08002014 if url.startswith('..'):
2015 url = urllib.parse.urljoin("%s/" % self.remote.url, url)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002016 remote = RemoteSpec(self.remote.name,
Anthony King7bdac712014-07-16 12:56:40 +01002017 url=url,
Steve Raed6480452016-08-10 15:00:00 -07002018 pushUrl=self.remote.pushUrl,
Anthony King7bdac712014-07-16 12:56:40 +01002019 review=self.remote.review,
2020 revision=self.remote.revision)
2021 subproject = Project(manifest=self.manifest,
2022 name=name,
2023 remote=remote,
2024 gitdir=gitdir,
2025 objdir=objdir,
2026 worktree=worktree,
2027 relpath=relpath,
Aymen Bouaziz2598ed02016-06-24 14:34:08 +02002028 revisionExpr=rev,
Anthony King7bdac712014-07-16 12:56:40 +01002029 revisionId=rev,
2030 rebase=self.rebase,
2031 groups=self.groups,
2032 sync_c=self.sync_c,
2033 sync_s=self.sync_s,
YOUNG HO CHAa32c92c2018-02-14 16:57:31 +09002034 sync_tags=self.sync_tags,
Anthony King7bdac712014-07-16 12:56:40 +01002035 parent=self,
2036 is_derived=True)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08002037 result.append(subproject)
2038 result.extend(subproject.GetDerivedSubprojects())
2039 return result
2040
2041
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002042# Direct Git Commands ##
Zac Livingstone4332262017-06-16 08:56:09 -06002043 def _CheckForImmutableRevision(self):
Chris AtLee2fb64662014-01-16 21:32:33 -05002044 try:
2045 # if revision (sha or tag) is not present then following function
2046 # throws an error.
2047 self.bare_git.rev_parse('--verify', '%s^0' % self.revisionExpr)
2048 return True
2049 except GitError:
2050 # There is no such persistent revision. We have to fetch it.
2051 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002052
Julien Campergue335f5ef2013-10-16 11:02:35 +02002053 def _FetchArchive(self, tarpath, cwd=None):
2054 cmd = ['archive', '-v', '-o', tarpath]
2055 cmd.append('--remote=%s' % self.remote.url)
2056 cmd.append('--prefix=%s/' % self.relpath)
2057 cmd.append(self.revisionExpr)
2058
2059 command = GitCommand(self, cmd, cwd=cwd,
2060 capture_stdout=True,
2061 capture_stderr=True)
2062
2063 if command.Wait() != 0:
2064 raise GitError('git archive %s: %s' % (self.name, command.stderr))
2065
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002066 def _RemoteFetch(self, name=None,
2067 current_branch_only=False,
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002068 initial=False,
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002069 quiet=False,
Mitchel Humpherys597868b2012-10-29 10:18:34 -07002070 alt_dir=None,
David Pursehouse74cfd272015-10-14 10:50:15 +09002071 no_tags=False,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002072 prune=False,
Martin Kellye4e94d22017-03-21 16:05:12 -07002073 depth=None,
Mike Frysingere57f1142019-03-18 21:27:54 -04002074 submodules=False,
Xin Li745be2e2019-06-03 11:24:30 -07002075 force_sync=False,
2076 clone_filter=None):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002077
2078 is_sha1 = False
2079 tag_name = None
David Pursehouse9bc422f2014-04-15 10:28:56 +09002080 # The depth should not be used when fetching to a mirror because
2081 # it will result in a shallow repository that cannot be cloned or
2082 # fetched from.
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002083 # The repo project should also never be synced with partial depth.
2084 if self.manifest.IsMirror or self.relpath == '.repo/repo':
2085 depth = None
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002086
Shawn Pearce69e04d82014-01-29 12:48:54 -08002087 if depth:
2088 current_branch_only = True
2089
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002090 if ID_RE.match(self.revisionExpr) is not None:
2091 is_sha1 = True
2092
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002093 if current_branch_only:
Nasser Grainawi909d58b2014-09-19 12:13:04 -06002094 if self.revisionExpr.startswith(R_TAGS):
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002095 # this is a tag and its sha1 value should never change
2096 tag_name = self.revisionExpr[len(R_TAGS):]
2097
2098 if is_sha1 or tag_name is not None:
Zac Livingstone4332262017-06-16 08:56:09 -06002099 if self._CheckForImmutableRevision():
Tim Schumacher1f1596b2019-04-15 11:17:08 +02002100 if not quiet:
2101 print('Skipped fetching project %s (already have persistent ref)'
2102 % self.name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002103 return True
Bertrand SIMONNET3000cda2014-11-25 16:19:29 -08002104 if is_sha1 and not depth:
2105 # When syncing a specific commit and --depth is not set:
2106 # * if upstream is explicitly specified and is not a sha1, fetch only
2107 # upstream as users expect only upstream to be fetch.
2108 # Note: The commit might not be in upstream in which case the sync
2109 # will fail.
2110 # * otherwise, fetch all branches to make sure we end up with the
2111 # specific commit.
Aymen Bouaziz037040f2016-06-28 12:27:23 +02002112 if self.upstream:
2113 current_branch_only = not ID_RE.match(self.upstream)
2114 else:
2115 current_branch_only = False
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002116
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002117 if not name:
2118 name = self.remote.name
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002119
2120 ssh_proxy = False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002121 remote = self.GetRemote(name)
2122 if remote.PreConnectFetch():
Shawn O. Pearcefb231612009-04-10 18:53:46 -07002123 ssh_proxy = True
2124
Shawn O. Pearce88443382010-10-08 10:02:09 +02002125 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002126 if alt_dir and 'objects' == os.path.basename(alt_dir):
2127 ref_dir = os.path.dirname(alt_dir)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002128 packed_refs = os.path.join(self.gitdir, 'packed-refs')
2129 remote = self.GetRemote(name)
2130
David Pursehouse8a68ff92012-09-24 12:15:13 +09002131 all_refs = self.bare_ref.all
2132 ids = set(all_refs.values())
Shawn O. Pearce88443382010-10-08 10:02:09 +02002133 tmp = set()
2134
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302135 for r, ref_id in GitRefs(ref_dir).all.items():
David Pursehouse8a68ff92012-09-24 12:15:13 +09002136 if r not in all_refs:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002137 if r.startswith(R_TAGS) or remote.WritesTo(r):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002138 all_refs[r] = ref_id
2139 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002140 continue
2141
David Pursehouse8a68ff92012-09-24 12:15:13 +09002142 if ref_id in ids:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002143 continue
2144
David Pursehouse8a68ff92012-09-24 12:15:13 +09002145 r = 'refs/_alt/%s' % ref_id
2146 all_refs[r] = ref_id
2147 ids.add(ref_id)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002148 tmp.add(r)
2149
heping3d7bbc92017-04-12 19:51:47 +08002150 tmp_packed_lines = []
2151 old_packed_lines = []
Shawn O. Pearce88443382010-10-08 10:02:09 +02002152
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302153 for r in sorted(all_refs):
David Pursehouse8a68ff92012-09-24 12:15:13 +09002154 line = '%s %s\n' % (all_refs[r], r)
heping3d7bbc92017-04-12 19:51:47 +08002155 tmp_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002156 if r not in tmp:
heping3d7bbc92017-04-12 19:51:47 +08002157 old_packed_lines.append(line)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002158
heping3d7bbc92017-04-12 19:51:47 +08002159 tmp_packed = ''.join(tmp_packed_lines)
2160 old_packed = ''.join(old_packed_lines)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002161 _lwrite(packed_refs, tmp_packed)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002162 else:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002163 alt_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002164
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002165 cmd = ['fetch']
Doug Anderson30d45292011-05-04 15:01:04 -07002166
Xin Li745be2e2019-06-03 11:24:30 -07002167 if clone_filter:
2168 git_require((2, 19, 0), fail=True, msg='partial clones')
2169 cmd.append('--filter=%s' % clone_filter)
2170 self.config.SetString('extensions.partialclone', self.remote.name)
2171
Conley Owensf97e8382015-01-21 11:12:46 -08002172 if depth:
Doug Anderson30d45292011-05-04 15:01:04 -07002173 cmd.append('--depth=%s' % depth)
Dan Willemseneeab6862015-08-03 13:11:53 -07002174 else:
2175 # If this repo has shallow objects, then we don't know which refs have
2176 # shallow objects or not. Tell git to unshallow all fetched refs. Don't
2177 # do this with projects that don't have shallow objects, since it is less
2178 # efficient.
2179 if os.path.exists(os.path.join(self.gitdir, 'shallow')):
2180 cmd.append('--depth=2147483647')
Doug Anderson30d45292011-05-04 15:01:04 -07002181
Shawn O. Pearce16614f82010-10-29 12:05:43 -07002182 if quiet:
2183 cmd.append('--quiet')
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002184 if not self.worktree:
2185 cmd.append('--update-head-ok')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002186 cmd.append(name)
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002187
Mitchel Humpherys26c45a72014-03-10 14:21:59 -07002188 # If using depth then we should not get all the tags since they may
2189 # be outside of the depth.
2190 if no_tags or depth:
2191 cmd.append('--no-tags')
2192 else:
2193 cmd.append('--tags')
2194
Mike Frysingere57f1142019-03-18 21:27:54 -04002195 if force_sync:
2196 cmd.append('--force')
2197
David Pursehouse74cfd272015-10-14 10:50:15 +09002198 if prune:
2199 cmd.append('--prune')
2200
Martin Kellye4e94d22017-03-21 16:05:12 -07002201 if submodules:
2202 cmd.append('--recurse-submodules=on-demand')
2203
Conley Owens80b87fe2014-05-09 17:13:44 -07002204 spec = []
Brian Harring14a66742012-09-28 20:21:57 -07002205 if not current_branch_only:
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002206 # Fetch whole repo
Conley Owens80b87fe2014-05-09 17:13:44 -07002207 spec.append(str((u'+refs/heads/*:') + remote.ToLocal('refs/heads/*')))
Xin Li3069be22019-08-22 10:00:27 -07002208 if not (no_tags or depth):
2209 spec.append(str((u'+refs/tags/*:') + remote.ToLocal('refs/tags/*')))
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -07002210 elif tag_name is not None:
Conley Owens80b87fe2014-05-09 17:13:44 -07002211 spec.append('tag')
2212 spec.append(tag_name)
Nasser Grainawi04e52d62014-09-30 13:34:52 -06002213
David Pursehouse403b64e2015-04-27 10:41:33 +09002214 if not self.manifest.IsMirror:
2215 branch = self.revisionExpr
Kevin Degi679bac42015-06-22 15:31:26 -06002216 if is_sha1 and depth and git_require((1, 8, 3)):
David Pursehouse403b64e2015-04-27 10:41:33 +09002217 # Shallow checkout of a specific commit, fetch from that commit and not
2218 # the heads only as the commit might be deeper in the history.
2219 spec.append(branch)
2220 else:
2221 if is_sha1:
2222 branch = self.upstream
2223 if branch is not None and branch.strip():
2224 if not branch.startswith('refs/'):
2225 branch = R_HEADS + branch
2226 spec.append(str((u'+%s:' % branch) + remote.ToLocal(branch)))
Conley Owens80b87fe2014-05-09 17:13:44 -07002227 cmd.extend(spec)
2228
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002229 ok = False
David Pursehouse8a68ff92012-09-24 12:15:13 +09002230 for _i in range(2):
John L. Villalovos9c76f672015-03-16 20:49:10 -07002231 gitcmd = GitCommand(self, cmd, bare=True, ssh_proxy=ssh_proxy)
John L. Villalovos126e2982015-01-29 21:58:12 -08002232 ret = gitcmd.Wait()
Brian Harring14a66742012-09-28 20:21:57 -07002233 if ret == 0:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002234 ok = True
2235 break
John L. Villalovos126e2982015-01-29 21:58:12 -08002236 # If needed, run the 'git remote prune' the first time through the loop
2237 elif (not _i and
2238 "error:" in gitcmd.stderr and
2239 "git remote prune" in gitcmd.stderr):
2240 prunecmd = GitCommand(self, ['remote', 'prune', name], bare=True,
John L. Villalovos9c76f672015-03-16 20:49:10 -07002241 ssh_proxy=ssh_proxy)
John L. Villalovose30f46b2015-02-25 14:27:02 -08002242 ret = prunecmd.Wait()
John L. Villalovose30f46b2015-02-25 14:27:02 -08002243 if ret:
John L. Villalovos126e2982015-01-29 21:58:12 -08002244 break
2245 continue
Brian Harring14a66742012-09-28 20:21:57 -07002246 elif current_branch_only and is_sha1 and ret == 128:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002247 # Exit code 128 means "couldn't find the ref you asked for"; if we're
2248 # in sha1 mode, we just tried sync'ing from the upstream field; it
2249 # doesn't exist, thus abort the optimization attempt and do a full sync.
Brian Harring14a66742012-09-28 20:21:57 -07002250 break
Colin Crossc4b301f2015-05-13 00:10:02 -07002251 elif ret < 0:
2252 # Git died with a signal, exit immediately
2253 break
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002254 time.sleep(random.randint(30, 45))
Shawn O. Pearce88443382010-10-08 10:02:09 +02002255
2256 if initial:
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002257 if alt_dir:
Shawn O. Pearce88443382010-10-08 10:02:09 +02002258 if old_packed != '':
2259 _lwrite(packed_refs, old_packed)
2260 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002261 platform_utils.remove(packed_refs)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002262 self.bare_git.pack_refs('--all', '--prune')
Brian Harring14a66742012-09-28 20:21:57 -07002263
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002264 if is_sha1 and current_branch_only:
Brian Harring14a66742012-09-28 20:21:57 -07002265 # We just synced the upstream given branch; verify we
2266 # got what we wanted, else trigger a second run of all
2267 # refs.
Zac Livingstone4332262017-06-16 08:56:09 -06002268 if not self._CheckForImmutableRevision():
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002269 if current_branch_only and depth:
2270 # Sync the current branch only with depth set to None
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002271 return self._RemoteFetch(name=name,
2272 current_branch_only=current_branch_only,
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002273 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002274 depth=None, clone_filter=clone_filter)
Aymen Bouaziz6c594462016-10-25 18:03:51 +02002275 else:
2276 # Avoid infinite recursion: sync all branches with depth set to None
2277 return self._RemoteFetch(name=name, current_branch_only=False,
2278 initial=False, quiet=quiet, alt_dir=alt_dir,
Xin Li745be2e2019-06-03 11:24:30 -07002279 depth=None, clone_filter=clone_filter)
Brian Harring14a66742012-09-28 20:21:57 -07002280
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002281 return ok
2282
2283 def _ApplyCloneBundle(self, initial=False, quiet=False):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002284 if initial and \
2285 (self.manifest.manifestProject.config.GetString('repo.depth') or
2286 self.clone_depth):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002287 return False
2288
2289 remote = self.GetRemote(self.remote.name)
2290 bundle_url = remote.url + '/clone.bundle'
2291 bundle_url = GitConfig.ForUser().UrlInsteadOf(bundle_url)
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002292 if GetSchemeFromUrl(bundle_url) not in ('http', 'https',
2293 'persistent-http',
2294 'persistent-https'):
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002295 return False
2296
2297 bundle_dst = os.path.join(self.gitdir, 'clone.bundle')
2298 bundle_tmp = os.path.join(self.gitdir, 'clone.bundle.tmp')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002299
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002300 exist_dst = os.path.exists(bundle_dst)
2301 exist_tmp = os.path.exists(bundle_tmp)
2302
2303 if not initial and not exist_dst and not exist_tmp:
2304 return False
2305
2306 if not exist_dst:
2307 exist_dst = self._FetchBundle(bundle_url, bundle_tmp, bundle_dst, quiet)
2308 if not exist_dst:
2309 return False
2310
2311 cmd = ['fetch']
2312 if quiet:
2313 cmd.append('--quiet')
2314 if not self.worktree:
2315 cmd.append('--update-head-ok')
2316 cmd.append(bundle_dst)
2317 for f in remote.fetch:
2318 cmd.append(str(f))
Xin Li6e538442018-12-10 11:33:16 -08002319 cmd.append('+refs/tags/*:refs/tags/*')
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002320
2321 ok = GitCommand(self, cmd, bare=True).Wait() == 0
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002322 if os.path.exists(bundle_dst):
Renaud Paquay010fed72016-11-11 14:25:29 -08002323 platform_utils.remove(bundle_dst)
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002324 if os.path.exists(bundle_tmp):
Renaud Paquay010fed72016-11-11 14:25:29 -08002325 platform_utils.remove(bundle_tmp)
Shawn O. Pearce88443382010-10-08 10:02:09 +02002326 return ok
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002327
Shawn O. Pearcec325dc32011-10-03 08:30:24 -07002328 def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet):
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002329 if os.path.exists(dstPath):
Renaud Paquay010fed72016-11-11 14:25:29 -08002330 platform_utils.remove(dstPath)
Shawn O. Pearce29472462011-10-11 09:24:07 -07002331
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002332 cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location']
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002333 if quiet:
2334 cmd += ['--silent']
2335 if os.path.exists(tmpPath):
2336 size = os.stat(tmpPath).st_size
2337 if size >= 1024:
2338 cmd += ['--continue-at', '%d' % (size,)]
2339 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002340 platform_utils.remove(tmpPath)
Xin Li3698ab72019-06-25 16:09:43 -07002341 with GetUrlCookieFile(srcUrl, quiet) as (cookiefile, proxy):
Dave Borowitz137d0132015-01-02 11:12:54 -08002342 if cookiefile:
Dave Borowitz4abf8e62015-01-02 11:39:04 -08002343 cmd += ['--cookie', cookiefile, '--cookie-jar', cookiefile]
Xin Li3698ab72019-06-25 16:09:43 -07002344 if proxy:
2345 cmd += ['--proxy', proxy]
2346 elif 'http_proxy' in os.environ and 'darwin' == sys.platform:
2347 cmd += ['--proxy', os.environ['http_proxy']]
2348 if srcUrl.startswith('persistent-https'):
2349 srcUrl = 'http' + srcUrl[len('persistent-https'):]
2350 elif srcUrl.startswith('persistent-http'):
2351 srcUrl = 'http' + srcUrl[len('persistent-http'):]
Dave Borowitz137d0132015-01-02 11:12:54 -08002352 cmd += [srcUrl]
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002353
Dave Borowitz137d0132015-01-02 11:12:54 -08002354 if IsTrace():
2355 Trace('%s', ' '.join(cmd))
2356 try:
2357 proc = subprocess.Popen(cmd)
2358 except OSError:
2359 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002360
Dave Borowitz137d0132015-01-02 11:12:54 -08002361 curlret = proc.wait()
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002362
Dave Borowitz137d0132015-01-02 11:12:54 -08002363 if curlret == 22:
2364 # From curl man page:
2365 # 22: HTTP page not retrieved. The requested url was not found or
2366 # returned another error with the HTTP error code being 400 or above.
2367 # This return code only appears if -f, --fail is used.
2368 if not quiet:
2369 print("Server does not provide clone.bundle; ignoring.",
2370 file=sys.stderr)
2371 return False
Matt Gumbel2dc810c2012-08-30 09:39:36 -07002372
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002373 if os.path.exists(tmpPath):
Kris Giesingc8d882a2014-12-23 13:02:32 -08002374 if curlret == 0 and self._IsValidBundle(tmpPath, quiet):
Renaud Paquayad1abcb2016-11-01 11:34:55 -07002375 platform_utils.rename(tmpPath, dstPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002376 return True
2377 else:
Renaud Paquay010fed72016-11-11 14:25:29 -08002378 platform_utils.remove(tmpPath)
Shawn O. Pearce5e7127d2012-08-02 14:57:37 -07002379 return False
2380 else:
2381 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -07002382
Kris Giesingc8d882a2014-12-23 13:02:32 -08002383 def _IsValidBundle(self, path, quiet):
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002384 try:
Pierre Tardy2b7daff2019-05-16 10:28:21 +02002385 with open(path, 'rb') as f:
2386 if f.read(16) == b'# v2 git bundle\n':
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002387 return True
2388 else:
Kris Giesingc8d882a2014-12-23 13:02:32 -08002389 if not quiet:
2390 print("Invalid clone.bundle file; ignoring.", file=sys.stderr)
Dave Borowitz91f3ba52013-06-03 12:15:23 -07002391 return False
2392 except OSError:
2393 return False
2394
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002395 def _Checkout(self, rev, quiet=False):
2396 cmd = ['checkout']
2397 if quiet:
2398 cmd.append('-q')
2399 cmd.append(rev)
2400 cmd.append('--')
2401 if GitCommand(self, cmd).Wait() != 0:
2402 if self._allrefs:
2403 raise GitError('%s checkout %s ' % (self.name, rev))
2404
Anthony King7bdac712014-07-16 12:56:40 +01002405 def _CherryPick(self, rev):
Pierre Tardye5a21222011-03-24 16:28:18 +01002406 cmd = ['cherry-pick']
2407 cmd.append(rev)
2408 cmd.append('--')
2409 if GitCommand(self, cmd).Wait() != 0:
2410 if self._allrefs:
2411 raise GitError('%s cherry-pick %s ' % (self.name, rev))
2412
Akshay Verma0f2e45a2018-03-24 12:27:05 +05302413 def _LsRemote(self, refs):
2414 cmd = ['ls-remote', self.remote.name, refs]
Akshay Vermacf7c0832018-03-15 21:56:30 +05302415 p = GitCommand(self, cmd, capture_stdout=True)
2416 if p.Wait() == 0:
Mike Frysinger600f4922019-08-03 02:14:28 -04002417 return p.stdout
Akshay Vermacf7c0832018-03-15 21:56:30 +05302418 return None
2419
Anthony King7bdac712014-07-16 12:56:40 +01002420 def _Revert(self, rev):
Erwan Mahea94f1622011-08-19 13:56:09 +02002421 cmd = ['revert']
2422 cmd.append('--no-edit')
2423 cmd.append(rev)
2424 cmd.append('--')
2425 if GitCommand(self, cmd).Wait() != 0:
2426 if self._allrefs:
2427 raise GitError('%s revert %s ' % (self.name, rev))
2428
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002429 def _ResetHard(self, rev, quiet=True):
2430 cmd = ['reset', '--hard']
2431 if quiet:
2432 cmd.append('-q')
2433 cmd.append(rev)
2434 if GitCommand(self, cmd).Wait() != 0:
2435 raise GitError('%s reset --hard %s ' % (self.name, rev))
2436
Martin Kellye4e94d22017-03-21 16:05:12 -07002437 def _SyncSubmodules(self, quiet=True):
2438 cmd = ['submodule', 'update', '--init', '--recursive']
2439 if quiet:
2440 cmd.append('-q')
2441 if GitCommand(self, cmd).Wait() != 0:
2442 raise GitError('%s submodule update --init --recursive %s ' % self.name)
2443
Anthony King7bdac712014-07-16 12:56:40 +01002444 def _Rebase(self, upstream, onto=None):
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002445 cmd = ['rebase']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002446 if onto is not None:
2447 cmd.extend(['--onto', onto])
2448 cmd.append(upstream)
Shawn O. Pearce19a83d82009-04-16 08:14:26 -07002449 if GitCommand(self, cmd).Wait() != 0:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002450 raise GitError('%s rebase %s ' % (self.name, upstream))
2451
Pierre Tardy3d125942012-05-04 12:18:12 +02002452 def _FastForward(self, head, ffonly=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002453 cmd = ['merge', head]
Pierre Tardy3d125942012-05-04 12:18:12 +02002454 if ffonly:
2455 cmd.append("--ff-only")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002456 if GitCommand(self, cmd).Wait() != 0:
2457 raise GitError('%s merge %s ' % (self.name, head))
2458
Kevin Degiabaa7f32014-11-12 11:27:45 -07002459 def _InitGitDir(self, mirror_git=None, force_sync=False):
Kevin Degi384b3c52014-10-16 16:02:58 -06002460 init_git_dir = not os.path.exists(self.gitdir)
2461 init_obj_dir = not os.path.exists(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002462 try:
2463 # Initialize the bare repository, which contains all of the objects.
2464 if init_obj_dir:
2465 os.makedirs(self.objdir)
2466 self.bare_objdir.init()
David James8d201162013-10-11 17:03:19 -07002467
Kevin Degib1a07b82015-07-27 13:33:43 -06002468 # If we have a separate directory to hold refs, initialize it as well.
2469 if self.objdir != self.gitdir:
2470 if init_git_dir:
2471 os.makedirs(self.gitdir)
Kevin Degi384b3c52014-10-16 16:02:58 -06002472
Kevin Degib1a07b82015-07-27 13:33:43 -06002473 if init_obj_dir or init_git_dir:
2474 self._ReferenceGitDir(self.objdir, self.gitdir, share_refs=False,
2475 copy_all=True)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002476 try:
2477 self._CheckDirReference(self.objdir, self.gitdir, share_refs=False)
2478 except GitError as e:
Kevin Degiabaa7f32014-11-12 11:27:45 -07002479 if force_sync:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002480 print("Retrying clone after deleting %s" %
2481 self.gitdir, file=sys.stderr)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002482 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002483 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2484 if self.worktree and os.path.exists(platform_utils.realpath
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002485 (self.worktree)):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002486 platform_utils.rmtree(platform_utils.realpath(self.worktree))
Kevin Degiabaa7f32014-11-12 11:27:45 -07002487 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2488 except:
2489 raise e
2490 raise e
Shawn O. Pearce2816d4f2009-03-03 17:53:18 -08002491
Kevin Degib1a07b82015-07-27 13:33:43 -06002492 if init_git_dir:
2493 mp = self.manifest.manifestProject
2494 ref_dir = mp.config.GetString('repo.reference') or ''
Shawn O. Pearce88443382010-10-08 10:02:09 +02002495
Kevin Degib1a07b82015-07-27 13:33:43 -06002496 if ref_dir or mirror_git:
2497 if not mirror_git:
2498 mirror_git = os.path.join(ref_dir, self.name + '.git')
2499 repo_git = os.path.join(ref_dir, '.repo', 'projects',
2500 self.relpath + '.git')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002501
Kevin Degib1a07b82015-07-27 13:33:43 -06002502 if os.path.exists(mirror_git):
2503 ref_dir = mirror_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002504
Kevin Degib1a07b82015-07-27 13:33:43 -06002505 elif os.path.exists(repo_git):
2506 ref_dir = repo_git
Shawn O. Pearce88443382010-10-08 10:02:09 +02002507
Kevin Degib1a07b82015-07-27 13:33:43 -06002508 else:
2509 ref_dir = None
Shawn O. Pearce88443382010-10-08 10:02:09 +02002510
Kevin Degib1a07b82015-07-27 13:33:43 -06002511 if ref_dir:
Samuel Hollandbaa00092018-01-22 10:57:29 -06002512 if not os.path.isabs(ref_dir):
2513 # The alternate directory is relative to the object database.
2514 ref_dir = os.path.relpath(ref_dir,
2515 os.path.join(self.objdir, 'objects'))
Kevin Degib1a07b82015-07-27 13:33:43 -06002516 _lwrite(os.path.join(self.gitdir, 'objects/info/alternates'),
2517 os.path.join(ref_dir, 'objects') + '\n')
Shawn O. Pearce88443382010-10-08 10:02:09 +02002518
Kevin Degib1a07b82015-07-27 13:33:43 -06002519 self._UpdateHooks()
Jimmie Westera0444582012-10-24 13:44:42 +02002520
Kevin Degib1a07b82015-07-27 13:33:43 -06002521 m = self.manifest.manifestProject.config
2522 for key in ['user.name', 'user.email']:
2523 if m.Has(key, include_defaults=False):
2524 self.config.SetString(key, m.GetString(key))
David Pursehouse76a4a9d2016-08-16 12:11:12 +09002525 self.config.SetString('filter.lfs.smudge', 'git-lfs smudge --skip -- %f')
Francois Ferrandc5b0e232019-05-24 09:35:20 +02002526 self.config.SetString('filter.lfs.process', 'git-lfs filter-process --skip')
Kevin Degib1a07b82015-07-27 13:33:43 -06002527 if self.manifest.IsMirror:
2528 self.config.SetString('core.bare', 'true')
2529 else:
2530 self.config.SetString('core.bare', None)
2531 except Exception:
2532 if init_obj_dir and os.path.exists(self.objdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002533 platform_utils.rmtree(self.objdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002534 if init_git_dir and os.path.exists(self.gitdir):
Renaud Paquaya65adf72016-11-03 10:37:53 -07002535 platform_utils.rmtree(self.gitdir)
Kevin Degib1a07b82015-07-27 13:33:43 -06002536 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002537
Jimmie Westera0444582012-10-24 13:44:42 +02002538 def _UpdateHooks(self):
2539 if os.path.exists(self.gitdir):
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002540 self._InitHooks()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002541
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002542 def _InitHooks(self):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002543 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002544 if not os.path.exists(hooks):
2545 os.makedirs(hooks)
Jonathan Nieder93719792015-03-17 11:29:58 -07002546 for stock_hook in _ProjectHooks():
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002547 name = os.path.basename(stock_hook)
2548
Victor Boivie65e0f352011-04-18 11:23:29 +02002549 if name in ('commit-msg',) and not self.remote.review \
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002550 and self is not self.manifest.manifestProject:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002551 # Don't install a Gerrit Code Review hook if this
2552 # project does not appear to use it for reviews.
2553 #
Victor Boivie65e0f352011-04-18 11:23:29 +02002554 # Since the manifest project is one of those, but also
2555 # managed through gerrit, it's excluded
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002556 continue
2557
2558 dst = os.path.join(hooks, name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002559 if platform_utils.islink(dst):
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002560 continue
2561 if os.path.exists(dst):
2562 if filecmp.cmp(stock_hook, dst, shallow=False):
Renaud Paquay010fed72016-11-11 14:25:29 -08002563 platform_utils.remove(dst)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002564 else:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002565 _warn("%s: Not replacing locally modified %s hook",
2566 self.relpath, name)
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002567 continue
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002568 try:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002569 platform_utils.symlink(
2570 os.path.relpath(stock_hook, os.path.dirname(dst)), dst)
Sarah Owensa5be53f2012-09-09 15:37:57 -07002571 except OSError as e:
Shawn O. Pearce9452e4e2009-08-22 18:17:46 -07002572 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002573 raise GitError(self._get_symlink_error_message())
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08002574 else:
2575 raise
2576
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002577 def _InitRemote(self):
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002578 if self.remote.url:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002579 remote = self.GetRemote(self.remote.name)
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002580 remote.url = self.remote.url
Steve Raed6480452016-08-10 15:00:00 -07002581 remote.pushUrl = self.remote.pushUrl
Shawn O. Pearced1f70d92009-05-19 14:58:02 -07002582 remote.review = self.remote.review
2583 remote.projectname = self.name
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002584
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002585 if self.worktree:
2586 remote.ResetFetch(mirror=False)
2587 else:
2588 remote.ResetFetch(mirror=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002589 remote.Save()
2590
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002591 def _InitMRef(self):
2592 if self.manifest.branch:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002593 self._InitAnyMRef(R_M + self.manifest.branch)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002594
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002595 def _InitMirrorHead(self):
Shawn O. Pearcefe200ee2009-06-01 15:28:21 -07002596 self._InitAnyMRef(HEAD)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002597
2598 def _InitAnyMRef(self, ref):
2599 cur = self.bare_ref.symref(ref)
2600
2601 if self.revisionId:
2602 if cur != '' or self.bare_ref.get(ref) != self.revisionId:
2603 msg = 'manifest set to %s' % self.revisionId
2604 dst = self.revisionId + '^0'
Anthony King7bdac712014-07-16 12:56:40 +01002605 self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07002606 else:
2607 remote = self.GetRemote(self.remote.name)
2608 dst = remote.ToLocal(self.revisionExpr)
2609 if cur != dst:
2610 msg = 'manifest set to %s' % self.revisionExpr
2611 self.bare_git.symbolic_ref('-m', msg, ref, dst)
Shawn O. Pearcee284ad12008-11-04 07:37:10 -08002612
Kevin Degi384b3c52014-10-16 16:02:58 -06002613 def _CheckDirReference(self, srcdir, destdir, share_refs):
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002614 symlink_files = self.shareable_files[:]
2615 symlink_dirs = self.shareable_dirs[:]
Kevin Degi384b3c52014-10-16 16:02:58 -06002616 if share_refs:
2617 symlink_files += self.working_tree_files
2618 symlink_dirs += self.working_tree_dirs
2619 to_symlink = symlink_files + symlink_dirs
2620 for name in set(to_symlink):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002621 dst = platform_utils.realpath(os.path.join(destdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002622 if os.path.lexists(dst):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002623 src = platform_utils.realpath(os.path.join(srcdir, name))
Kevin Degi384b3c52014-10-16 16:02:58 -06002624 # Fail if the links are pointing to the wrong place
2625 if src != dst:
Marc Herbertec287902016-10-27 12:58:26 -07002626 _error('%s is different in %s vs %s', name, destdir, srcdir)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002627 raise GitError('--force-sync not enabled; cannot overwrite a local '
Simon Ruggierf9b76832015-07-31 17:18:34 -04002628 'work tree. If you\'re comfortable with the '
2629 'possibility of losing the work tree\'s git metadata,'
2630 ' use `repo sync --force-sync {0}` to '
2631 'proceed.'.format(self.relpath))
Kevin Degi384b3c52014-10-16 16:02:58 -06002632
David James8d201162013-10-11 17:03:19 -07002633 def _ReferenceGitDir(self, gitdir, dotgit, share_refs, copy_all):
2634 """Update |dotgit| to reference |gitdir|, using symlinks where possible.
2635
2636 Args:
2637 gitdir: The bare git repository. Must already be initialized.
2638 dotgit: The repository you would like to initialize.
2639 share_refs: If true, |dotgit| will store its refs under |gitdir|.
2640 Only one work tree can store refs under a given |gitdir|.
2641 copy_all: If true, copy all remaining files from |gitdir| -> |dotgit|.
2642 This saves you the effort of initializing |dotgit| yourself.
2643 """
Dan Willemsenbdb866e2016-04-05 17:22:02 -07002644 symlink_files = self.shareable_files[:]
2645 symlink_dirs = self.shareable_dirs[:]
David James8d201162013-10-11 17:03:19 -07002646 if share_refs:
Kevin Degi384b3c52014-10-16 16:02:58 -06002647 symlink_files += self.working_tree_files
2648 symlink_dirs += self.working_tree_dirs
David James8d201162013-10-11 17:03:19 -07002649 to_symlink = symlink_files + symlink_dirs
2650
2651 to_copy = []
2652 if copy_all:
Renaud Paquaybed8b622018-09-27 10:46:58 -07002653 to_copy = platform_utils.listdir(gitdir)
David James8d201162013-10-11 17:03:19 -07002654
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002655 dotgit = platform_utils.realpath(dotgit)
David James8d201162013-10-11 17:03:19 -07002656 for name in set(to_copy).union(to_symlink):
2657 try:
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002658 src = platform_utils.realpath(os.path.join(gitdir, name))
Dan Willemsen2a3e1522015-07-30 20:43:33 -07002659 dst = os.path.join(dotgit, name)
David James8d201162013-10-11 17:03:19 -07002660
Kevin Degi384b3c52014-10-16 16:02:58 -06002661 if os.path.lexists(dst):
2662 continue
David James8d201162013-10-11 17:03:19 -07002663
2664 # If the source dir doesn't exist, create an empty dir.
2665 if name in symlink_dirs and not os.path.lexists(src):
2666 os.makedirs(src)
2667
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002668 if name in to_symlink:
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07002669 platform_utils.symlink(
2670 os.path.relpath(src, os.path.dirname(dst)), dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002671 elif copy_all and not platform_utils.islink(dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -07002672 if platform_utils.isdir(src):
Cheuk Leung8ac0c962015-07-06 21:33:00 +02002673 shutil.copytree(src, dst)
2674 elif os.path.isfile(src):
2675 shutil.copy(src, dst)
2676
Conley Owens80b87fe2014-05-09 17:13:44 -07002677 # If the source file doesn't exist, ensure the destination
2678 # file doesn't either.
2679 if name in symlink_files and not os.path.lexists(src):
2680 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08002681 platform_utils.remove(dst)
Conley Owens80b87fe2014-05-09 17:13:44 -07002682 except OSError:
2683 pass
2684
David James8d201162013-10-11 17:03:19 -07002685 except OSError as e:
2686 if e.errno == errno.EPERM:
Renaud Paquay788e9622017-01-27 11:41:12 -08002687 raise DownloadError(self._get_symlink_error_message())
David James8d201162013-10-11 17:03:19 -07002688 else:
2689 raise
2690
Martin Kellye4e94d22017-03-21 16:05:12 -07002691 def _InitWorkTree(self, force_sync=False, submodules=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002692 dotgit = os.path.join(self.worktree, '.git')
Kevin Degi384b3c52014-10-16 16:02:58 -06002693 init_dotgit = not os.path.exists(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002694 try:
2695 if init_dotgit:
2696 os.makedirs(dotgit)
2697 self._ReferenceGitDir(self.gitdir, dotgit, share_refs=True,
2698 copy_all=False)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002699
Kevin Degiabaa7f32014-11-12 11:27:45 -07002700 try:
2701 self._CheckDirReference(self.gitdir, dotgit, share_refs=True)
2702 except GitError as e:
2703 if force_sync:
2704 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -07002705 platform_utils.rmtree(dotgit)
Martin Kellye4e94d22017-03-21 16:05:12 -07002706 return self._InitWorkTree(force_sync=False, submodules=submodules)
Kevin Degiabaa7f32014-11-12 11:27:45 -07002707 except:
2708 raise e
2709 raise e
Kevin Degi384b3c52014-10-16 16:02:58 -06002710
Kevin Degib1a07b82015-07-27 13:33:43 -06002711 if init_dotgit:
2712 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002713
Kevin Degib1a07b82015-07-27 13:33:43 -06002714 cmd = ['read-tree', '--reset', '-u']
2715 cmd.append('-v')
2716 cmd.append(HEAD)
2717 if GitCommand(self, cmd).Wait() != 0:
Mikhail Naganovb5548382019-05-09 12:59:49 -07002718 raise GitError("cannot initialize work tree for " + self.name)
Victor Boivie0960b5b2010-11-26 13:42:13 +01002719
Martin Kellye4e94d22017-03-21 16:05:12 -07002720 if submodules:
2721 self._SyncSubmodules(quiet=True)
Kevin Degib1a07b82015-07-27 13:33:43 -06002722 self._CopyAndLinkFiles()
2723 except Exception:
2724 if init_dotgit:
Renaud Paquaya65adf72016-11-03 10:37:53 -07002725 platform_utils.rmtree(dotgit)
Kevin Degib1a07b82015-07-27 13:33:43 -06002726 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002727
Renaud Paquay788e9622017-01-27 11:41:12 -08002728 def _get_symlink_error_message(self):
2729 if platform_utils.isWindows():
2730 return ('Unable to create symbolic link. Please re-run the command as '
2731 'Administrator, or see '
2732 'https://github.com/git-for-windows/git/wiki/Symbolic-Links '
2733 'for other options.')
2734 return 'filesystem must support symlinks'
2735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002736 def _gitdir_path(self, path):
Renaud Paquay227ad2e2016-11-01 14:37:13 -07002737 return platform_utils.realpath(os.path.join(self.gitdir, path))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002738
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002739 def _revlist(self, *args, **kw):
2740 a = []
2741 a.extend(args)
2742 a.append('--')
2743 return self.work_git.rev_list(*a, **kw)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002744
2745 @property
2746 def _allrefs(self):
Shawn O. Pearced237b692009-04-17 18:49:50 -07002747 return self.bare_ref.all
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002748
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002749 def _getLogs(self, rev1, rev2, oneline=False, color=True, pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002750 """Get logs between two revisions of this project."""
2751 comp = '..'
2752 if rev1:
2753 revs = [rev1]
2754 if rev2:
2755 revs.extend([comp, rev2])
2756 cmd = ['log', ''.join(revs)]
2757 out = DiffColoring(self.config)
2758 if out.is_on and color:
2759 cmd.append('--color')
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002760 if pretty_format is not None:
2761 cmd.append('--pretty=format:%s' % pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002762 if oneline:
2763 cmd.append('--oneline')
2764
2765 try:
2766 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2767 if log.Wait() == 0:
2768 return log.stdout
2769 except GitError:
2770 # worktree may not exist if groups changed for example. In that case,
2771 # try in gitdir instead.
2772 if not os.path.exists(self.worktree):
2773 return self.bare_git.log(*cmd[1:])
2774 else:
2775 raise
2776 return None
2777
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002778 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True,
2779 pretty_format=None):
Julien Camperguedd654222014-01-09 16:21:37 +01002780 """Get the list of logs from this revision to given revisionId"""
2781 logs = {}
2782 selfId = self.GetRevisionId(self._allrefs)
2783 toId = toProject.GetRevisionId(toProject._allrefs)
2784
Sebastian Schuberth7ecccf62016-03-29 14:11:20 +02002785 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color,
2786 pretty_format=pretty_format)
2787 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color,
2788 pretty_format=pretty_format)
Julien Camperguedd654222014-01-09 16:21:37 +01002789 return logs
2790
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002791 class _GitGetByExec(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002792
David James8d201162013-10-11 17:03:19 -07002793 def __init__(self, project, bare, gitdir):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002794 self._project = project
2795 self._bare = bare
David James8d201162013-10-11 17:03:19 -07002796 self._gitdir = gitdir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002797
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002798 def LsOthers(self):
2799 p = GitCommand(self._project,
2800 ['ls-files',
2801 '-z',
2802 '--others',
2803 '--exclude-standard'],
Anthony King7bdac712014-07-16 12:56:40 +01002804 bare=False,
David James8d201162013-10-11 17:03:19 -07002805 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002806 capture_stdout=True,
2807 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002808 if p.Wait() == 0:
2809 out = p.stdout
2810 if out:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002811 # Backslash is not anomalous
David Pursehouse65b0ba52018-06-24 16:21:51 +09002812 return out[:-1].split('\0')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002813 return []
2814
2815 def DiffZ(self, name, *args):
2816 cmd = [name]
2817 cmd.append('-z')
Eli Ribble2d095da2019-05-02 18:21:42 -07002818 cmd.append('--ignore-submodules')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002819 cmd.extend(args)
2820 p = GitCommand(self._project,
2821 cmd,
David James8d201162013-10-11 17:03:19 -07002822 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002823 bare=False,
2824 capture_stdout=True,
2825 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002826 try:
2827 out = p.process.stdout.read()
Mike Frysinger600f4922019-08-03 02:14:28 -04002828 if not hasattr(out, 'encode'):
2829 out = out.decode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002830 r = {}
2831 if out:
David Pursehouse65b0ba52018-06-24 16:21:51 +09002832 out = iter(out[:-1].split('\0'))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002833 while out:
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002834 try:
Anthony King2cd1f042014-05-05 21:24:05 +01002835 info = next(out)
2836 path = next(out)
Shawn O. Pearce02dbb6d2008-10-21 13:59:08 -07002837 except StopIteration:
2838 break
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002839
2840 class _Info(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002841
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002842 def __init__(self, path, omode, nmode, oid, nid, state):
2843 self.path = path
2844 self.src_path = None
2845 self.old_mode = omode
2846 self.new_mode = nmode
2847 self.old_id = oid
2848 self.new_id = nid
2849
2850 if len(state) == 1:
2851 self.status = state
2852 self.level = None
2853 else:
2854 self.status = state[:1]
2855 self.level = state[1:]
2856 while self.level.startswith('0'):
2857 self.level = self.level[1:]
2858
2859 info = info[1:].split(' ')
David Pursehouse8f62fb72012-11-14 12:09:38 +09002860 info = _Info(path, *info)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002861 if info.status in ('R', 'C'):
2862 info.src_path = info.path
Anthony King2cd1f042014-05-05 21:24:05 +01002863 info.path = next(out)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002864 r[info.path] = info
2865 return r
2866 finally:
2867 p.Wait()
2868
2869 def GetHead(self):
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002870 if self._bare:
2871 path = os.path.join(self._project.gitdir, HEAD)
2872 else:
2873 path = os.path.join(self._project.worktree, '.git', HEAD)
Conley Owens75ee0572012-11-15 17:33:11 -08002874 try:
Renaud Paquay2a4be942016-11-01 13:48:15 -07002875 fd = open(path)
Dan Sandler53e902a2014-03-09 13:20:02 -04002876 except IOError as e:
2877 raise NoManifestException(path, str(e))
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002878 try:
Renaud Paquay2a4be942016-11-01 13:48:15 -07002879 line = fd.readline()
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -07002880 finally:
2881 fd.close()
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302882 try:
2883 line = line.decode()
2884 except AttributeError:
2885 pass
Shawn O. Pearce5b23f242009-04-17 18:43:33 -07002886 if line.startswith('ref: '):
2887 return line[5:-1]
2888 return line[:-1]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002889
2890 def SetHead(self, ref, message=None):
2891 cmdv = []
2892 if message is not None:
2893 cmdv.extend(['-m', message])
2894 cmdv.append(HEAD)
2895 cmdv.append(ref)
2896 self.symbolic_ref(*cmdv)
2897
2898 def DetachHead(self, new, message=None):
2899 cmdv = ['--no-deref']
2900 if message is not None:
2901 cmdv.extend(['-m', message])
2902 cmdv.append(HEAD)
2903 cmdv.append(new)
2904 self.update_ref(*cmdv)
2905
2906 def UpdateRef(self, name, new, old=None,
2907 message=None,
2908 detach=False):
2909 cmdv = []
2910 if message is not None:
2911 cmdv.extend(['-m', message])
2912 if detach:
2913 cmdv.append('--no-deref')
2914 cmdv.append(name)
2915 cmdv.append(new)
2916 if old is not None:
2917 cmdv.append(old)
2918 self.update_ref(*cmdv)
2919
2920 def DeleteRef(self, name, old=None):
2921 if not old:
2922 old = self.rev_parse(name)
2923 self.update_ref('-d', name, old)
Shawn O. Pearcefbcde472009-04-17 20:58:02 -07002924 self._project.bare_ref.deleted(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002925
Shawn O. Pearce8ad8a0e2009-05-29 18:28:25 -07002926 def rev_list(self, *args, **kw):
2927 if 'format' in kw:
2928 cmdv = ['log', '--pretty=format:%s' % kw['format']]
2929 else:
2930 cmdv = ['rev-list']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002931 cmdv.extend(args)
2932 p = GitCommand(self._project,
2933 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002934 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002935 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002936 capture_stdout=True,
2937 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002938 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002939 raise GitError('%s rev-list %s: %s' %
2940 (self._project.name, str(args), p.stderr))
Mike Frysinger81f5c592019-07-04 18:13:31 -04002941 return p.stdout.splitlines()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002942
2943 def __getattr__(self, name):
Doug Anderson37282b42011-03-04 11:54:18 -08002944 """Allow arbitrary git commands using pythonic syntax.
2945
2946 This allows you to do things like:
2947 git_obj.rev_parse('HEAD')
2948
2949 Since we don't have a 'rev_parse' method defined, the __getattr__ will
2950 run. We'll replace the '_' with a '-' and try to run a git command.
Dave Borowitz091f8932012-10-23 17:01:04 -07002951 Any other positional arguments will be passed to the git command, and the
2952 following keyword arguments are supported:
2953 config: An optional dict of git config options to be passed with '-c'.
Doug Anderson37282b42011-03-04 11:54:18 -08002954
2955 Args:
2956 name: The name of the git command to call. Any '_' characters will
2957 be replaced with '-'.
2958
2959 Returns:
2960 A callable object that will try to call git with the named command.
2961 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002962 name = name.replace('_', '-')
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002963
Dave Borowitz091f8932012-10-23 17:01:04 -07002964 def runner(*args, **kwargs):
2965 cmdv = []
2966 config = kwargs.pop('config', None)
2967 for k in kwargs:
2968 raise TypeError('%s() got an unexpected keyword argument %r'
2969 % (name, k))
2970 if config is not None:
Dave Borowitzb42b4742012-10-31 12:27:27 -07002971 if not git_require((1, 7, 2)):
2972 raise ValueError('cannot set config on command line for %s()'
2973 % name)
Chirayu Desai217ea7d2013-03-01 19:14:38 +05302974 for k, v in config.items():
Dave Borowitz091f8932012-10-23 17:01:04 -07002975 cmdv.append('-c')
2976 cmdv.append('%s=%s' % (k, v))
2977 cmdv.append(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002978 cmdv.extend(args)
2979 p = GitCommand(self._project,
2980 cmdv,
Anthony King7bdac712014-07-16 12:56:40 +01002981 bare=self._bare,
David James8d201162013-10-11 17:03:19 -07002982 gitdir=self._gitdir,
Anthony King7bdac712014-07-16 12:56:40 +01002983 capture_stdout=True,
2984 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002985 if p.Wait() != 0:
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002986 raise GitError('%s %s: %s' %
2987 (self._project.name, name, p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002988 r = p.stdout
2989 if r.endswith('\n') and r.index('\n') == len(r) - 1:
2990 return r[:-1]
2991 return r
2992 return runner
2993
2994
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002995class _PriorSyncFailedError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07002996
Shawn O. Pearce350cde42009-04-16 11:21:18 -07002997 def __str__(self):
2998 return 'prior sync failed; rebase still in progress'
2999
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003000
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003001class _DirtyError(Exception):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003002
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003003 def __str__(self):
3004 return 'contains uncommitted changes'
3005
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003006
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003007class _InfoMessage(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003008
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003009 def __init__(self, project, text):
3010 self.project = project
3011 self.text = text
3012
3013 def Print(self, syncbuf):
3014 syncbuf.out.info('%s/: %s', self.project.relpath, self.text)
3015 syncbuf.out.nl()
3016
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003017
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003018class _Failure(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003019
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003020 def __init__(self, project, why):
3021 self.project = project
3022 self.why = why
3023
3024 def Print(self, syncbuf):
3025 syncbuf.out.fail('error: %s/: %s',
3026 self.project.relpath,
3027 str(self.why))
3028 syncbuf.out.nl()
3029
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003030
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003031class _Later(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003032
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003033 def __init__(self, project, action):
3034 self.project = project
3035 self.action = action
3036
3037 def Run(self, syncbuf):
3038 out = syncbuf.out
3039 out.project('project %s/', self.project.relpath)
3040 out.nl()
3041 try:
3042 self.action()
3043 out.nl()
3044 return True
David Pursehouse8a68ff92012-09-24 12:15:13 +09003045 except GitError:
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003046 out.nl()
3047 return False
3048
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003049
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003050class _SyncColoring(Coloring):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003051
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003052 def __init__(self, config):
3053 Coloring.__init__(self, config, 'reposync')
Anthony King7bdac712014-07-16 12:56:40 +01003054 self.project = self.printer('header', attr='bold')
3055 self.info = self.printer('info')
3056 self.fail = self.printer('fail', fg='red')
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003057
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003058
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003059class SyncBuffer(object):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003060
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003061 def __init__(self, config, detach_head=False):
3062 self._messages = []
3063 self._failures = []
3064 self._later_queue1 = []
3065 self._later_queue2 = []
3066
3067 self.out = _SyncColoring(config)
3068 self.out.redirect(sys.stderr)
3069
3070 self.detach_head = detach_head
3071 self.clean = True
David Rileye0684ad2017-04-05 00:02:59 -07003072 self.recent_clean = True
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003073
3074 def info(self, project, fmt, *args):
3075 self._messages.append(_InfoMessage(project, fmt % args))
3076
3077 def fail(self, project, err=None):
3078 self._failures.append(_Failure(project, err))
David Rileye0684ad2017-04-05 00:02:59 -07003079 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003080
3081 def later1(self, project, what):
3082 self._later_queue1.append(_Later(project, what))
3083
3084 def later2(self, project, what):
3085 self._later_queue2.append(_Later(project, what))
3086
3087 def Finish(self):
3088 self._PrintMessages()
3089 self._RunLater()
3090 self._PrintMessages()
3091 return self.clean
3092
David Rileye0684ad2017-04-05 00:02:59 -07003093 def Recently(self):
3094 recent_clean = self.recent_clean
3095 self.recent_clean = True
3096 return recent_clean
3097
3098 def _MarkUnclean(self):
3099 self.clean = False
3100 self.recent_clean = False
3101
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003102 def _RunLater(self):
3103 for q in ['_later_queue1', '_later_queue2']:
3104 if not self._RunQueue(q):
3105 return
3106
3107 def _RunQueue(self, queue):
3108 for m in getattr(self, queue):
3109 if not m.Run(self):
David Rileye0684ad2017-04-05 00:02:59 -07003110 self._MarkUnclean()
Shawn O. Pearce350cde42009-04-16 11:21:18 -07003111 return False
3112 setattr(self, queue, [])
3113 return True
3114
3115 def _PrintMessages(self):
3116 for m in self._messages:
3117 m.Print(self)
3118 for m in self._failures:
3119 m.Print(self)
3120
3121 self._messages = []
3122 self._failures = []
3123
3124
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003125class MetaProject(Project):
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003126
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003127 """A special project housed under .repo.
3128 """
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003129
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003130 def __init__(self, manifest, name, gitdir, worktree):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003131 Project.__init__(self,
Anthony King7bdac712014-07-16 12:56:40 +01003132 manifest=manifest,
3133 name=name,
3134 gitdir=gitdir,
3135 objdir=gitdir,
3136 worktree=worktree,
3137 remote=RemoteSpec('origin'),
3138 relpath='.repo/%s' % name,
3139 revisionExpr='refs/heads/master',
3140 revisionId=None,
3141 groups=None)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003142
3143 def PreSync(self):
3144 if self.Exists:
3145 cb = self.CurrentBranch
3146 if cb:
3147 base = self.GetBranch(cb).merge
3148 if base:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003149 self.revisionExpr = base
3150 self.revisionId = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003151
Martin Kelly224a31a2017-07-10 14:46:25 -07003152 def MetaBranchSwitch(self, submodules=False):
Florian Vallee5d016502012-06-07 17:19:26 +02003153 """ Prepare MetaProject for manifest branch switch
3154 """
3155
3156 # detach and delete manifest branch, allowing a new
3157 # branch to take over
Anthony King7bdac712014-07-16 12:56:40 +01003158 syncbuf = SyncBuffer(self.config, detach_head=True)
Martin Kelly224a31a2017-07-10 14:46:25 -07003159 self.Sync_LocalHalf(syncbuf, submodules=submodules)
Florian Vallee5d016502012-06-07 17:19:26 +02003160 syncbuf.Finish()
3161
3162 return GitCommand(self,
Mark E. Hamilton30b0f4e2016-02-10 10:44:30 -07003163 ['update-ref', '-d', 'refs/heads/default'],
3164 capture_stdout=True,
3165 capture_stderr=True).Wait() == 0
Florian Vallee5d016502012-06-07 17:19:26 +02003166
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003167 @property
Shawn O. Pearcef6906872009-04-18 10:49:00 -07003168 def LastFetch(self):
3169 try:
3170 fh = os.path.join(self.gitdir, 'FETCH_HEAD')
3171 return os.path.getmtime(fh)
3172 except OSError:
3173 return 0
3174
3175 @property
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003176 def HasChanges(self):
3177 """Has the remote received new commits not yet checked out?
3178 """
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003179 if not self.remote or not self.revisionExpr:
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003180 return False
3181
David Pursehouse8a68ff92012-09-24 12:15:13 +09003182 all_refs = self.bare_ref.all
3183 revid = self.GetRevisionId(all_refs)
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003184 head = self.work_git.GetHead()
3185 if head.startswith(R_HEADS):
3186 try:
David Pursehouse8a68ff92012-09-24 12:15:13 +09003187 head = all_refs[head]
Shawn O. Pearce336f7bd2009-04-18 10:39:28 -07003188 except KeyError:
3189 head = None
3190
3191 if revid == head:
3192 return False
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07003193 elif self._revlist(not_rev(HEAD), revid):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07003194 return True
3195 return False