blob: 9ad979ad2e96cf6cc5b02dffe95638b3f4be6cdf [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Dan Willemsen0745bb22015-08-17 13:41:45 -070015import contextlib
Raman Tenneti7954de12021-07-28 14:36:49 -070016import datetime
Dan Willemsen0745bb22015-08-17 13:41:45 -070017import errno
Mike Frysingeracf63b22019-06-13 02:24:21 -040018from http.client import HTTPException
Anthony King85b24ac2014-05-06 15:57:48 +010019import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import os
21import re
Łukasz Gardońbed59ce2017-08-08 10:18:11 +020022import ssl
Shawn O. Pearcefb231612009-04-10 18:53:46 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Joanna Wangea5239d2022-12-02 09:47:29 -050025from typing import Union
Mike Frysingeracf63b22019-06-13 02:24:21 -040026import urllib.error
27import urllib.request
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070028
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080029from error import GitError, UploadError
Renaud Paquay010fed72016-11-11 14:25:29 -080030import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040031from repo_trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070032from git_command import GitCommand
Zac Livingston9ead97b2017-06-13 08:29:04 -060033from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Raman Tenneti7954de12021-07-28 14:36:49 -070035# Prefix that is prepended to all the keys of SyncAnalysisState's data
36# that is saved in the config.
37SYNC_STATE_PREFIX = 'repo.syncstate.'
38
David Pursehouse1d947b32012-10-25 12:23:11 +090039ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070040
Shawn O. Pearce146fe902009-03-25 14:06:43 -070041REVIEW_CACHE = dict()
42
David Pursehouse819827a2020-02-12 15:20:19 +090043
Zac Livingston9ead97b2017-06-13 08:29:04 -060044def IsChange(rev):
45 return rev.startswith(R_CHANGES)
46
David Pursehouse819827a2020-02-12 15:20:19 +090047
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048def IsId(rev):
49 return ID_RE.match(rev)
50
David Pursehouse819827a2020-02-12 15:20:19 +090051
Zac Livingston9ead97b2017-06-13 08:29:04 -060052def IsTag(rev):
53 return rev.startswith(R_TAGS)
54
David Pursehouse819827a2020-02-12 15:20:19 +090055
Zac Livingston9ead97b2017-06-13 08:29:04 -060056def IsImmutable(rev):
57 return IsChange(rev) or IsId(rev) or IsTag(rev)
58
David Pursehouse819827a2020-02-12 15:20:19 +090059
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070060def _key(name):
61 parts = name.split('.')
62 if len(parts) < 2:
63 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090064 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070065 parts[-1] = parts[-1].lower()
66 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067
David Pursehouse819827a2020-02-12 15:20:19 +090068
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070069class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070070 _ForUser = None
71
Xin Li0cb6e922021-06-16 10:19:00 -070072 _ForSystem = None
73 _SYSTEM_CONFIG = '/etc/gitconfig'
74
75 @classmethod
76 def ForSystem(cls):
77 if cls._ForSystem is None:
78 cls._ForSystem = cls(configfile=cls._SYSTEM_CONFIG)
79 return cls._ForSystem
80
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081 @classmethod
82 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070083 if cls._ForUser is None:
Gavin Mak7e3b65b2023-01-26 23:27:51 +000084 cls._ForUser = cls(configfile=cls._getUserConfig())
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070085 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070086
Gavin Mak7e3b65b2023-01-26 23:27:51 +000087 @staticmethod
88 def _getUserConfig():
89 return os.path.expanduser('~/.gitconfig')
90
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070091 @classmethod
92 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090093 return cls(configfile=os.path.join(gitdir, 'config'),
94 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095
Anthony King85b24ac2014-05-06 15:57:48 +010096 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090097 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098 self.defaults = defaults
99 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700100 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101 self._remotes = {}
102 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700103
Anthony King85b24ac2014-05-06 15:57:48 +0100104 self._json = jsonFile
105 if self._json is None:
106 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900107 os.path.dirname(self.file),
108 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700109
Jack Neusc474c9c2021-07-26 23:08:54 +0000110 def ClearCache(self):
111 """Clear the in-memory cache of config."""
112 self._cache_dict = None
113
David Pursehousee5913ae2020-02-12 13:56:59 +0900114 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115 """Return true if this configuration file has the key.
116 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700117 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118 return True
119 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900120 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 return False
122
Joanna Wangea5239d2022-12-02 09:47:29 -0500123 def GetInt(self, name: str) -> Union[int, None]:
Mike Frysinger77b43972020-02-19 17:55:22 -0500124 """Returns an integer from the configuration file.
125
126 This follows the git config syntax.
127
128 Args:
129 name: The key to lookup.
130
131 Returns:
Joanna Wangea5239d2022-12-02 09:47:29 -0500132 None if the value was not defined, or is not an int.
Mike Frysinger77b43972020-02-19 17:55:22 -0500133 Otherwise, the number itself.
134 """
135 v = self.GetString(name)
136 if v is None:
137 return None
138 v = v.strip()
139
140 mult = 1
141 if v.endswith('k'):
142 v = v[:-1]
143 mult = 1024
144 elif v.endswith('m'):
145 v = v[:-1]
146 mult = 1024 * 1024
147 elif v.endswith('g'):
148 v = v[:-1]
149 mult = 1024 * 1024 * 1024
150
151 base = 10
152 if v.startswith('0x'):
153 base = 16
154
155 try:
156 return int(v, base=base) * mult
157 except ValueError:
Joanna Wangea5239d2022-12-02 09:47:29 -0500158 print(
159 f"warning: expected {name} to represent an integer, got {v} instead",
160 file=sys.stderr)
Mike Frysinger77b43972020-02-19 17:55:22 -0500161 return None
162
Ian Kasprzak835a34b2021-03-05 11:04:49 -0800163 def DumpConfigDict(self):
164 """Returns the current configuration dict.
165
166 Configuration data is information only (e.g. logging) and
167 should not be considered a stable data-source.
168
169 Returns:
170 dict of {<key>, <value>} for git configuration cache.
171 <value> are strings converted by GetString.
172 """
173 config_dict = {}
174 for key in self._cache:
175 config_dict[key] = self.GetString(key)
176 return config_dict
177
Joanna Wangea5239d2022-12-02 09:47:29 -0500178 def GetBoolean(self, name: str) -> Union[str, None]:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700179 """Returns a boolean from the configuration file.
180 None : The value was not defined, or is not a boolean.
181 True : The value was set to true or yes.
182 False: The value was set to false or no.
183 """
184 v = self.GetString(name)
185 if v is None:
186 return None
187 v = v.lower()
188 if v in ('true', 'yes'):
189 return True
190 if v in ('false', 'no'):
191 return False
Joanna Wangea5239d2022-12-02 09:47:29 -0500192 print(f"warning: expected {name} to represent a boolean, got {v} instead",
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +0100193 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700194 return None
195
Mike Frysinger38867fb2021-02-09 23:14:41 -0500196 def SetBoolean(self, name, value):
197 """Set the truthy value for a key."""
198 if value is not None:
199 value = 'true' if value else 'false'
200 self.SetString(name, value)
201
Sergiy Belozorov78e82ec2023-01-05 18:57:31 +0100202 def GetString(self, name: str, all_keys: bool = False) -> Union[str, None]:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700203 """Get the first value for a key, or None if it is not defined.
204
205 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900206 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700209 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700210 except KeyError:
211 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900212 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700213 v = []
214
David Pursehouse8a68ff92012-09-24 12:15:13 +0900215 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 if v:
217 return v[0]
218 return None
219
220 r = []
221 r.extend(v)
222 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900223 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 return r
225
226 def SetString(self, name, value):
227 """Set the value(s) for a key.
228 Only this configuration file is modified.
229
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000230 The supplied value should be either a string, or a list of strings (to
231 store multiple values), or None (to delete the key).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700233 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234
235 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700236 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 except KeyError:
238 old = []
239
240 if value is None:
241 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700242 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 self._do('--unset-all', name)
244
245 elif isinstance(value, list):
246 if len(value) == 0:
247 self.SetString(name, None)
248
249 elif len(value) == 1:
250 self.SetString(name, value[0])
251
252 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700253 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700255 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256 self._do('--add', name, value[i])
257
258 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700259 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260 self._do('--replace-all', name, value)
261
262 def GetRemote(self, name):
263 """Get the remote.$name.* configuration values as an object.
264 """
265 try:
266 r = self._remotes[name]
267 except KeyError:
268 r = Remote(self, name)
269 self._remotes[r.name] = r
270 return r
271
272 def GetBranch(self, name):
273 """Get the branch.$name.* configuration values as an object.
274 """
275 try:
276 b = self._branches[name]
277 except KeyError:
278 b = Branch(self, name)
279 self._branches[b.name] = b
280 return b
281
Raman Tenneti7954de12021-07-28 14:36:49 -0700282 def GetSyncAnalysisStateData(self):
283 """Returns data to be logged for the analysis of sync performance."""
284 return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
285
286 def UpdateSyncAnalysisState(self, options, superproject_logging_data):
287 """Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
288
289 Args:
290 options: Options passed to sync returned from optparse. See _Options().
291 superproject_logging_data: A dictionary of superproject data that is to be logged.
292
293 Returns:
294 SyncAnalysisState object.
295 """
296 return SyncAnalysisState(self, options, superproject_logging_data)
297
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700298 def GetSubSections(self, section):
299 """List all subsection names matching $section.*.*
300 """
301 return self._sections.get(section, set())
302
David Pursehousee5913ae2020-02-12 13:56:59 +0900303 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700304 """Does at least one key in section.subsection exist?
305 """
306 try:
307 return subsection in self._sections[section]
308 except KeyError:
309 return False
310
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700311 def UrlInsteadOf(self, url):
312 """Resolve any url.*.insteadof references.
313 """
314 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700315 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
316 if old_url is not None and url.startswith(old_url):
317 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700318 return url
319
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700320 @property
321 def _sections(self):
322 d = self._section_dict
323 if d is None:
324 d = {}
325 for name in self._cache.keys():
326 p = name.split('.')
327 if 2 == len(p):
328 section = p[0]
329 subsect = ''
330 else:
331 section = p[0]
332 subsect = '.'.join(p[1:-1])
333 if section not in d:
334 d[section] = set()
335 d[section].add(subsect)
336 self._section_dict = d
337 return d
338
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700339 @property
340 def _cache(self):
341 if self._cache_dict is None:
342 self._cache_dict = self._Read()
343 return self._cache_dict
344
345 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100346 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700347 if d is None:
348 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100349 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700350 return d
351
Anthony King85b24ac2014-05-06 15:57:48 +0100352 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700353 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900354 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800355 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700356 return None
357 except OSError:
358 return None
359 try:
Joanna Wanga6c52f52022-11-03 16:51:19 -0400360 with Trace(': parsing %s', self.file):
361 with open(self._json) as fd:
362 return json.load(fd)
Sl0v3C00c5ea32021-11-07 08:48:09 +0800363 except (IOError, ValueError):
Mike Frysinger9d96f582021-09-28 11:27:24 -0400364 platform_utils.remove(self._json, missing_ok=True)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700365 return None
366
Anthony King85b24ac2014-05-06 15:57:48 +0100367 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700368 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500369 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100370 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100371 except (IOError, TypeError):
Mike Frysinger9d96f582021-09-28 11:27:24 -0400372 platform_utils.remove(self._json, missing_ok=True)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700373
374 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700375 """
376 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700377
David Aguilar438c5472009-06-28 15:09:16 -0700378 This internal method populates the GitConfig cache.
379
380 """
David Aguilar438c5472009-06-28 15:09:16 -0700381 c = {}
Mike Frysingerf88282c2021-09-28 15:59:40 -0400382 if not os.path.exists(self.file):
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700383 return c
Mike Frysingerf88282c2021-09-28 15:59:40 -0400384
385 d = self._do('--null', '--list')
Dylan Denge469a0c2018-06-23 15:02:26 +0800386 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700387 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900388 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700389 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900390 key = line
391 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700392
393 if key in c:
394 c[key].append(val)
395 else:
396 c[key] = [val]
397
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700398 return c
399
400 def _do(self, *args):
Xin Li0cb6e922021-06-16 10:19:00 -0700401 if self.file == self._SYSTEM_CONFIG:
402 command = ['config', '--system', '--includes']
403 else:
404 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700405 command.extend(args)
406
407 p = GitCommand(None,
408 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900409 capture_stdout=True,
410 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700411 if p.Wait() == 0:
412 return p.stdout
413 else:
Jack Neusc474c9c2021-07-26 23:08:54 +0000414 raise GitError('git config %s: %s' % (str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700415
416
Mike Frysingerf841ca42020-02-18 21:31:51 -0500417class RepoConfig(GitConfig):
418 """User settings for repo itself."""
419
Gavin Mak7e3b65b2023-01-26 23:27:51 +0000420 @staticmethod
421 def _getUserConfig():
422 repo_config_dir = os.getenv('REPO_CONFIG_DIR', os.path.expanduser('~'))
423 return os.path.join(repo_config_dir, '.repoconfig/config')
Mike Frysingerf841ca42020-02-18 21:31:51 -0500424
425
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700426class RefSpec(object):
427 """A Git refspec line, split into its components:
428
429 forced: True if the line starts with '+'
430 src: Left side of the line
431 dst: Right side of the line
432 """
433
434 @classmethod
435 def FromString(cls, rs):
436 lhs, rhs = rs.split(':', 2)
437 if lhs.startswith('+'):
438 lhs = lhs[1:]
439 forced = True
440 else:
441 forced = False
442 return cls(forced, lhs, rhs)
443
444 def __init__(self, forced, lhs, rhs):
445 self.forced = forced
446 self.src = lhs
447 self.dst = rhs
448
449 def SourceMatches(self, rev):
450 if self.src:
451 if rev == self.src:
452 return True
453 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
454 return True
455 return False
456
457 def DestMatches(self, ref):
458 if self.dst:
459 if ref == self.dst:
460 return True
461 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
462 return True
463 return False
464
465 def MapSource(self, rev):
466 if self.src.endswith('/*'):
467 return self.dst[:-1] + rev[len(self.src) - 1:]
468 return self.dst
469
470 def __str__(self):
471 s = ''
472 if self.forced:
473 s += '+'
474 if self.src:
475 s += self.src
476 if self.dst:
477 s += ':'
478 s += self.dst
479 return s
480
481
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700482URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700483
David Pursehouse819827a2020-02-12 15:20:19 +0900484
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700485def GetSchemeFromUrl(url):
486 m = URI_ALL.match(url)
487 if m:
488 return m.group(1)
489 return None
490
David Pursehouse819827a2020-02-12 15:20:19 +0900491
Dan Willemsen0745bb22015-08-17 13:41:45 -0700492@contextlib.contextmanager
493def GetUrlCookieFile(url, quiet):
494 if url.startswith('persistent-'):
495 try:
496 p = subprocess.Popen(
497 ['git-remote-persistent-https', '-print_config', url],
498 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
499 stderr=subprocess.PIPE)
500 try:
501 cookieprefix = 'http.cookiefile='
502 proxyprefix = 'http.proxy='
503 cookiefile = None
504 proxy = None
505 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500506 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700507 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900508 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700509 if line.startswith(proxyprefix):
510 proxy = line[len(proxyprefix):]
511 # Leave subprocess open, as cookie file may be transient.
512 if cookiefile or proxy:
513 yield cookiefile, proxy
514 return
515 finally:
516 p.stdin.close()
517 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500518 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700519 if ' -print_config' in err_msg:
520 pass # Persistent proxy doesn't support -print_config.
521 elif not quiet:
522 print(err_msg, file=sys.stderr)
523 except OSError as e:
524 if e.errno == errno.ENOENT:
525 pass # No persistent proxy.
526 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900527 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
528 if cookiefile:
529 cookiefile = os.path.expanduser(cookiefile)
530 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700531
David Pursehouse819827a2020-02-12 15:20:19 +0900532
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700533class Remote(object):
534 """Configuration options related to a remote.
535 """
David Pursehouse819827a2020-02-12 15:20:19 +0900536
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537 def __init__(self, config, name):
538 self._config = config
539 self.name = name
540 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700541 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700542 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800543 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530544 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900545 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800546 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800547
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100548 def _InsteadOf(self):
549 globCfg = GitConfig.ForUser()
550 urlList = globCfg.GetSubSections('url')
551 longest = ""
552 longestUrl = ""
553
554 for url in urlList:
555 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900556 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100557
558 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900559 if (self.url.startswith(insteadOf)
560 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100561 longest = insteadOf
562 longestUrl = url
563
564 if len(longest) == 0:
565 return self.url
566
567 return self.url.replace(longest, longestUrl, 1)
568
Mike Frysinger339f2df2021-05-06 00:44:42 -0400569 def PreConnectFetch(self, ssh_proxy):
Mike Frysinger19e409c2021-05-05 19:44:35 -0400570 """Run any setup for this remote before we connect to it.
571
572 In practice, if the remote is using SSH, we'll attempt to create a new
573 SSH master session to it for reuse across projects.
574
Mike Frysinger339f2df2021-05-06 00:44:42 -0400575 Args:
576 ssh_proxy: The SSH settings for managing master sessions.
577
Mike Frysinger19e409c2021-05-05 19:44:35 -0400578 Returns:
579 Whether the preconnect phase for this remote was successful.
580 """
Mike Frysinger339f2df2021-05-06 00:44:42 -0400581 if not ssh_proxy:
582 return True
583
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100584 connectionUrl = self._InsteadOf()
Mike Frysinger339f2df2021-05-06 00:44:42 -0400585 return ssh_proxy.preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700586
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200587 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800588 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800589 if self.review is None:
590 return None
591
592 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700593 if u.startswith('persistent-'):
594 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100595 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800596 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700597 if u.endswith('/Gerrit'):
598 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800599 if u.endswith('/ssh_info'):
600 u = u[:len(u) - len('/ssh_info')]
601 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900602 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800603 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800604
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700605 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800606 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700607 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800608 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
609 self._review_url = self._SshReviewUrl(userEmail, host, port)
610 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100611 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800612 self._review_url = u # Assume it's right
613 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200614 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
615 self._review_url = http_url
616 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700617 else:
618 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800619 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200620 if not validate_certs:
621 context = ssl._create_unverified_context()
622 info = urllib.request.urlopen(info_url, context=context).read()
623 else:
624 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400625 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700626 # If `info` contains '<', we assume the server gave us some sort
627 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700628 #
Conley Owens745a39b2013-06-05 13:16:18 -0700629 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800630 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700631 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400632 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800633 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000634 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700635 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800636 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700637 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700638 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900639 except HTTPException as e:
640 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800641
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800642 REVIEW_CACHE[u] = self._review_url
643 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800644
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800645 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700646 username = self._config.GetString('review.%s.username' % self.review)
647 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800648 username = userEmail.split('@')[0]
649 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700650
651 def ToLocal(self, rev):
652 """Convert a remote revision string to something we have locally.
653 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200654 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656
657 if not rev.startswith('refs/'):
658 rev = R_HEADS + rev
659
660 for spec in self.fetch:
661 if spec.SourceMatches(rev):
662 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600663
664 if not rev.startswith(R_HEADS):
665 return rev
666
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400667 raise GitError('%s: remote %s does not have %s' %
668 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669
670 def WritesTo(self, ref):
671 """True if the remote stores to the tracking ref.
672 """
673 for spec in self.fetch:
674 if spec.DestMatches(ref):
675 return True
676 return False
677
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800678 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679 """Set the fetch refspec to its default value.
680 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800681 if mirror:
682 dst = 'refs/heads/*'
683 else:
684 dst = 'refs/remotes/%s/*' % self.name
685 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686
687 def Save(self):
688 """Save this remote to the configuration.
689 """
690 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700691 if self.pushUrl is not None:
692 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
693 else:
694 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800696 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530697 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698
699 def _Set(self, key, value):
700 key = 'remote.%s.%s' % (self.name, key)
701 return self._config.SetString(key, value)
702
David Pursehouse8a68ff92012-09-24 12:15:13 +0900703 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900705 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706
707
708class Branch(object):
709 """Configuration options related to a single branch.
710 """
David Pursehouse819827a2020-02-12 15:20:19 +0900711
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 def __init__(self, config, name):
713 self._config = config
714 self.name = name
715 self.merge = self._Get('merge')
716
717 r = self._Get('remote')
718 if r:
719 self.remote = self._config.GetRemote(r)
720 else:
721 self.remote = None
722
723 @property
724 def LocalMerge(self):
725 """Convert the merge spec to a local name.
726 """
727 if self.remote and self.merge:
728 return self.remote.ToLocal(self.merge)
729 return None
730
731 def Save(self):
732 """Save this branch back into the configuration.
733 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700734 if self._config.HasSection('branch', self.name):
735 if self.remote:
736 self._Set('remote', self.remote.name)
737 else:
738 self._Set('remote', None)
739 self._Set('merge', self.merge)
740
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500742 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700743 fd.write('[branch "%s"]\n' % self.name)
744 if self.remote:
745 fd.write('\tremote = %s\n' % self.remote.name)
746 if self.merge:
747 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
749 def _Set(self, key, value):
750 key = 'branch.%s.%s' % (self.name, key)
751 return self._config.SetString(key, value)
752
David Pursehouse8a68ff92012-09-24 12:15:13 +0900753 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900755 return self._config.GetString(key, all_keys=all_keys)
Raman Tenneti7954de12021-07-28 14:36:49 -0700756
757
758class SyncAnalysisState:
759 """Configuration options related to logging of sync state for analysis.
760
761 This object is versioned.
762 """
763 def __init__(self, config, options, superproject_logging_data):
764 """Initializes SyncAnalysisState.
765
766 Saves the following data into the |config| object.
767 - sys.argv, options, superproject's logging data.
768 - repo.*, branch.* and remote.* parameters from config object.
769 - Current time as synctime.
770 - Version number of the object.
771
772 All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
773
774 Args:
775 config: GitConfig object to store all options.
776 options: Options passed to sync returned from optparse. See _Options().
777 superproject_logging_data: A dictionary of superproject data that is to be logged.
778 """
779 self._config = config
780 now = datetime.datetime.utcnow()
781 self._Set('main.synctime', now.isoformat() + 'Z')
782 self._Set('main.version', '1')
783 self._Set('sys.argv', sys.argv)
784 for key, value in superproject_logging_data.items():
785 self._Set(f'superproject.{key}', value)
786 for key, value in options.__dict__.items():
787 self._Set(f'options.{key}', value)
788 config_items = config.DumpConfigDict().items()
789 EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
790 self._SetDictionary({k: v for k, v in config_items
791 if not k.startswith(SYNC_STATE_PREFIX) and
792 k.split('.', 1)[0] in EXTRACT_NAMESPACES})
793
794 def _SetDictionary(self, data):
795 """Save all key/value pairs of |data| dictionary.
796
797 Args:
798 data: A dictionary whose key/value are to be saved.
799 """
800 for key, value in data.items():
801 self._Set(key, value)
802
803 def _Set(self, key, value):
804 """Set the |value| for a |key| in the |_config| member.
805
806 |key| is prepended with the value of SYNC_STATE_PREFIX constant.
807
808 Args:
809 key: Name of the key.
810 value: |value| could be of any type. If it is 'bool', it will be saved
811 as a Boolean and for all other types, it will be saved as a String.
812 """
813 if value is None:
814 return
815 sync_key = f'{SYNC_STATE_PREFIX}{key}'
Raman Tenneti9122bfc2021-07-29 15:11:23 -0700816 sync_key = sync_key.replace('_', '')
Raman Tenneti7954de12021-07-28 14:36:49 -0700817 if isinstance(value, str):
818 self._config.SetString(sync_key, value)
819 elif isinstance(value, bool):
820 self._config.SetBoolean(sync_key, value)
821 else:
822 self._config.SetString(sync_key, str(value))