blob: af1a1015133575820becee6867b46f221f87e997 [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
Mike Frysingerf841ca42020-02-18 21:31:51 -050072 _USER_CONFIG = '~/.gitconfig'
73
Xin Li0cb6e922021-06-16 10:19:00 -070074 _ForSystem = None
75 _SYSTEM_CONFIG = '/etc/gitconfig'
76
77 @classmethod
78 def ForSystem(cls):
79 if cls._ForSystem is None:
80 cls._ForSystem = cls(configfile=cls._SYSTEM_CONFIG)
81 return cls._ForSystem
82
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070083 @classmethod
84 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070085 if cls._ForUser is None:
Mike Frysingerf841ca42020-02-18 21:31:51 -050086 cls._ForUser = cls(configfile=os.path.expanduser(cls._USER_CONFIG))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070087 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070088
89 @classmethod
90 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090091 return cls(configfile=os.path.join(gitdir, 'config'),
92 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093
Anthony King85b24ac2014-05-06 15:57:48 +010094 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090095 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070096 self.defaults = defaults
97 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070098 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070099 self._remotes = {}
100 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700101
Anthony King85b24ac2014-05-06 15:57:48 +0100102 self._json = jsonFile
103 if self._json is None:
104 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900105 os.path.dirname(self.file),
106 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700107
Jack Neusc474c9c2021-07-26 23:08:54 +0000108 def ClearCache(self):
109 """Clear the in-memory cache of config."""
110 self._cache_dict = None
111
David Pursehousee5913ae2020-02-12 13:56:59 +0900112 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700113 """Return true if this configuration file has the key.
114 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700115 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700116 return True
117 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900118 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119 return False
120
Joanna Wangea5239d2022-12-02 09:47:29 -0500121 def GetInt(self, name: str) -> Union[int, None]:
Mike Frysinger77b43972020-02-19 17:55:22 -0500122 """Returns an integer from the configuration file.
123
124 This follows the git config syntax.
125
126 Args:
127 name: The key to lookup.
128
129 Returns:
Joanna Wangea5239d2022-12-02 09:47:29 -0500130 None if the value was not defined, or is not an int.
Mike Frysinger77b43972020-02-19 17:55:22 -0500131 Otherwise, the number itself.
132 """
133 v = self.GetString(name)
134 if v is None:
135 return None
136 v = v.strip()
137
138 mult = 1
139 if v.endswith('k'):
140 v = v[:-1]
141 mult = 1024
142 elif v.endswith('m'):
143 v = v[:-1]
144 mult = 1024 * 1024
145 elif v.endswith('g'):
146 v = v[:-1]
147 mult = 1024 * 1024 * 1024
148
149 base = 10
150 if v.startswith('0x'):
151 base = 16
152
153 try:
154 return int(v, base=base) * mult
155 except ValueError:
Joanna Wangea5239d2022-12-02 09:47:29 -0500156 print(
157 f"warning: expected {name} to represent an integer, got {v} instead",
158 file=sys.stderr)
Mike Frysinger77b43972020-02-19 17:55:22 -0500159 return None
160
Ian Kasprzak835a34b2021-03-05 11:04:49 -0800161 def DumpConfigDict(self):
162 """Returns the current configuration dict.
163
164 Configuration data is information only (e.g. logging) and
165 should not be considered a stable data-source.
166
167 Returns:
168 dict of {<key>, <value>} for git configuration cache.
169 <value> are strings converted by GetString.
170 """
171 config_dict = {}
172 for key in self._cache:
173 config_dict[key] = self.GetString(key)
174 return config_dict
175
Joanna Wangea5239d2022-12-02 09:47:29 -0500176 def GetBoolean(self, name: str) -> Union[str, None]:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 """Returns a boolean from the configuration file.
178 None : The value was not defined, or is not a boolean.
179 True : The value was set to true or yes.
180 False: The value was set to false or no.
181 """
182 v = self.GetString(name)
183 if v is None:
184 return None
185 v = v.lower()
186 if v in ('true', 'yes'):
187 return True
188 if v in ('false', 'no'):
189 return False
Joanna Wangea5239d2022-12-02 09:47:29 -0500190 print(f"warning: expected {name} to represent a boolean, got {v} instead",
191 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192 return None
193
Mike Frysinger38867fb2021-02-09 23:14:41 -0500194 def SetBoolean(self, name, value):
195 """Set the truthy value for a key."""
196 if value is not None:
197 value = 'true' if value else 'false'
198 self.SetString(name, value)
199
Joanna Wangea5239d2022-12-02 09:47:29 -0500200 def GetString(self, name: str, all_keys: bool = False) -> Union[str, None]:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201 """Get the first value for a key, or None if it is not defined.
202
203 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900204 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700206 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700207 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 except KeyError:
209 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900210 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 v = []
212
David Pursehouse8a68ff92012-09-24 12:15:13 +0900213 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 if v:
215 return v[0]
216 return None
217
218 r = []
219 r.extend(v)
220 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900221 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 return r
223
224 def SetString(self, name, value):
225 """Set the value(s) for a key.
226 Only this configuration file is modified.
227
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000228 The supplied value should be either a string, or a list of strings (to
229 store multiple values), or None (to delete the key).
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700231 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700232
233 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700234 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 except KeyError:
236 old = []
237
238 if value is None:
239 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700240 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 self._do('--unset-all', name)
242
243 elif isinstance(value, list):
244 if len(value) == 0:
245 self.SetString(name, None)
246
247 elif len(value) == 1:
248 self.SetString(name, value[0])
249
250 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700251 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700253 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700254 self._do('--add', name, value[i])
255
256 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700257 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700258 self._do('--replace-all', name, value)
259
260 def GetRemote(self, name):
261 """Get the remote.$name.* configuration values as an object.
262 """
263 try:
264 r = self._remotes[name]
265 except KeyError:
266 r = Remote(self, name)
267 self._remotes[r.name] = r
268 return r
269
270 def GetBranch(self, name):
271 """Get the branch.$name.* configuration values as an object.
272 """
273 try:
274 b = self._branches[name]
275 except KeyError:
276 b = Branch(self, name)
277 self._branches[b.name] = b
278 return b
279
Raman Tenneti7954de12021-07-28 14:36:49 -0700280 def GetSyncAnalysisStateData(self):
281 """Returns data to be logged for the analysis of sync performance."""
282 return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
283
284 def UpdateSyncAnalysisState(self, options, superproject_logging_data):
285 """Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
286
287 Args:
288 options: Options passed to sync returned from optparse. See _Options().
289 superproject_logging_data: A dictionary of superproject data that is to be logged.
290
291 Returns:
292 SyncAnalysisState object.
293 """
294 return SyncAnalysisState(self, options, superproject_logging_data)
295
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700296 def GetSubSections(self, section):
297 """List all subsection names matching $section.*.*
298 """
299 return self._sections.get(section, set())
300
David Pursehousee5913ae2020-02-12 13:56:59 +0900301 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700302 """Does at least one key in section.subsection exist?
303 """
304 try:
305 return subsection in self._sections[section]
306 except KeyError:
307 return False
308
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700309 def UrlInsteadOf(self, url):
310 """Resolve any url.*.insteadof references.
311 """
312 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700313 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
314 if old_url is not None and url.startswith(old_url):
315 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700316 return url
317
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700318 @property
319 def _sections(self):
320 d = self._section_dict
321 if d is None:
322 d = {}
323 for name in self._cache.keys():
324 p = name.split('.')
325 if 2 == len(p):
326 section = p[0]
327 subsect = ''
328 else:
329 section = p[0]
330 subsect = '.'.join(p[1:-1])
331 if section not in d:
332 d[section] = set()
333 d[section].add(subsect)
334 self._section_dict = d
335 return d
336
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700337 @property
338 def _cache(self):
339 if self._cache_dict is None:
340 self._cache_dict = self._Read()
341 return self._cache_dict
342
343 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100344 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700345 if d is None:
346 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100347 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700348 return d
349
Anthony King85b24ac2014-05-06 15:57:48 +0100350 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700351 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900352 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800353 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700354 return None
355 except OSError:
356 return None
357 try:
Joanna Wanga6c52f52022-11-03 16:51:19 -0400358 with Trace(': parsing %s', self.file):
359 with open(self._json) as fd:
360 return json.load(fd)
Sl0v3C00c5ea32021-11-07 08:48:09 +0800361 except (IOError, ValueError):
Mike Frysinger9d96f582021-09-28 11:27:24 -0400362 platform_utils.remove(self._json, missing_ok=True)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700363 return None
364
Anthony King85b24ac2014-05-06 15:57:48 +0100365 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700366 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500367 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100368 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100369 except (IOError, TypeError):
Mike Frysinger9d96f582021-09-28 11:27:24 -0400370 platform_utils.remove(self._json, missing_ok=True)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700371
372 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700373 """
374 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700375
David Aguilar438c5472009-06-28 15:09:16 -0700376 This internal method populates the GitConfig cache.
377
378 """
David Aguilar438c5472009-06-28 15:09:16 -0700379 c = {}
Mike Frysingerf88282c2021-09-28 15:59:40 -0400380 if not os.path.exists(self.file):
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700381 return c
Mike Frysingerf88282c2021-09-28 15:59:40 -0400382
383 d = self._do('--null', '--list')
Dylan Denge469a0c2018-06-23 15:02:26 +0800384 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700385 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900386 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700387 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900388 key = line
389 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700390
391 if key in c:
392 c[key].append(val)
393 else:
394 c[key] = [val]
395
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700396 return c
397
398 def _do(self, *args):
Xin Li0cb6e922021-06-16 10:19:00 -0700399 if self.file == self._SYSTEM_CONFIG:
400 command = ['config', '--system', '--includes']
401 else:
402 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700403 command.extend(args)
404
405 p = GitCommand(None,
406 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900407 capture_stdout=True,
408 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700409 if p.Wait() == 0:
410 return p.stdout
411 else:
Jack Neusc474c9c2021-07-26 23:08:54 +0000412 raise GitError('git config %s: %s' % (str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700413
414
Mike Frysingerf841ca42020-02-18 21:31:51 -0500415class RepoConfig(GitConfig):
416 """User settings for repo itself."""
417
418 _USER_CONFIG = '~/.repoconfig/config'
419
420
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700421class RefSpec(object):
422 """A Git refspec line, split into its components:
423
424 forced: True if the line starts with '+'
425 src: Left side of the line
426 dst: Right side of the line
427 """
428
429 @classmethod
430 def FromString(cls, rs):
431 lhs, rhs = rs.split(':', 2)
432 if lhs.startswith('+'):
433 lhs = lhs[1:]
434 forced = True
435 else:
436 forced = False
437 return cls(forced, lhs, rhs)
438
439 def __init__(self, forced, lhs, rhs):
440 self.forced = forced
441 self.src = lhs
442 self.dst = rhs
443
444 def SourceMatches(self, rev):
445 if self.src:
446 if rev == self.src:
447 return True
448 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
449 return True
450 return False
451
452 def DestMatches(self, ref):
453 if self.dst:
454 if ref == self.dst:
455 return True
456 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
457 return True
458 return False
459
460 def MapSource(self, rev):
461 if self.src.endswith('/*'):
462 return self.dst[:-1] + rev[len(self.src) - 1:]
463 return self.dst
464
465 def __str__(self):
466 s = ''
467 if self.forced:
468 s += '+'
469 if self.src:
470 s += self.src
471 if self.dst:
472 s += ':'
473 s += self.dst
474 return s
475
476
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700477URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700478
David Pursehouse819827a2020-02-12 15:20:19 +0900479
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700480def GetSchemeFromUrl(url):
481 m = URI_ALL.match(url)
482 if m:
483 return m.group(1)
484 return None
485
David Pursehouse819827a2020-02-12 15:20:19 +0900486
Dan Willemsen0745bb22015-08-17 13:41:45 -0700487@contextlib.contextmanager
488def GetUrlCookieFile(url, quiet):
489 if url.startswith('persistent-'):
490 try:
491 p = subprocess.Popen(
492 ['git-remote-persistent-https', '-print_config', url],
493 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
494 stderr=subprocess.PIPE)
495 try:
496 cookieprefix = 'http.cookiefile='
497 proxyprefix = 'http.proxy='
498 cookiefile = None
499 proxy = None
500 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500501 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700502 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900503 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700504 if line.startswith(proxyprefix):
505 proxy = line[len(proxyprefix):]
506 # Leave subprocess open, as cookie file may be transient.
507 if cookiefile or proxy:
508 yield cookiefile, proxy
509 return
510 finally:
511 p.stdin.close()
512 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500513 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700514 if ' -print_config' in err_msg:
515 pass # Persistent proxy doesn't support -print_config.
516 elif not quiet:
517 print(err_msg, file=sys.stderr)
518 except OSError as e:
519 if e.errno == errno.ENOENT:
520 pass # No persistent proxy.
521 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900522 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
523 if cookiefile:
524 cookiefile = os.path.expanduser(cookiefile)
525 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700526
David Pursehouse819827a2020-02-12 15:20:19 +0900527
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528class Remote(object):
529 """Configuration options related to a remote.
530 """
David Pursehouse819827a2020-02-12 15:20:19 +0900531
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700532 def __init__(self, config, name):
533 self._config = config
534 self.name = name
535 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700536 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700537 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800538 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530539 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900540 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800541 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800542
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100543 def _InsteadOf(self):
544 globCfg = GitConfig.ForUser()
545 urlList = globCfg.GetSubSections('url')
546 longest = ""
547 longestUrl = ""
548
549 for url in urlList:
550 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900551 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100552
553 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900554 if (self.url.startswith(insteadOf)
555 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100556 longest = insteadOf
557 longestUrl = url
558
559 if len(longest) == 0:
560 return self.url
561
562 return self.url.replace(longest, longestUrl, 1)
563
Mike Frysinger339f2df2021-05-06 00:44:42 -0400564 def PreConnectFetch(self, ssh_proxy):
Mike Frysinger19e409c2021-05-05 19:44:35 -0400565 """Run any setup for this remote before we connect to it.
566
567 In practice, if the remote is using SSH, we'll attempt to create a new
568 SSH master session to it for reuse across projects.
569
Mike Frysinger339f2df2021-05-06 00:44:42 -0400570 Args:
571 ssh_proxy: The SSH settings for managing master sessions.
572
Mike Frysinger19e409c2021-05-05 19:44:35 -0400573 Returns:
574 Whether the preconnect phase for this remote was successful.
575 """
Mike Frysinger339f2df2021-05-06 00:44:42 -0400576 if not ssh_proxy:
577 return True
578
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100579 connectionUrl = self._InsteadOf()
Mike Frysinger339f2df2021-05-06 00:44:42 -0400580 return ssh_proxy.preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700581
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200582 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800583 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800584 if self.review is None:
585 return None
586
587 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700588 if u.startswith('persistent-'):
589 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100590 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800591 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700592 if u.endswith('/Gerrit'):
593 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800594 if u.endswith('/ssh_info'):
595 u = u[:len(u) - len('/ssh_info')]
596 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900597 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800598 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800599
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700600 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800601 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700602 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800603 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
604 self._review_url = self._SshReviewUrl(userEmail, host, port)
605 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100606 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800607 self._review_url = u # Assume it's right
608 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200609 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
610 self._review_url = http_url
611 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700612 else:
613 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800614 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200615 if not validate_certs:
616 context = ssl._create_unverified_context()
617 info = urllib.request.urlopen(info_url, context=context).read()
618 else:
619 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400620 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700621 # If `info` contains '<', we assume the server gave us some sort
622 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700623 #
Conley Owens745a39b2013-06-05 13:16:18 -0700624 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800625 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700626 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400627 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800628 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000629 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700630 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800631 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700632 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700633 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900634 except HTTPException as e:
635 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800636
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800637 REVIEW_CACHE[u] = self._review_url
638 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800639
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800640 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700641 username = self._config.GetString('review.%s.username' % self.review)
642 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800643 username = userEmail.split('@')[0]
644 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645
646 def ToLocal(self, rev):
647 """Convert a remote revision string to something we have locally.
648 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200649 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700650 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651
652 if not rev.startswith('refs/'):
653 rev = R_HEADS + rev
654
655 for spec in self.fetch:
656 if spec.SourceMatches(rev):
657 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600658
659 if not rev.startswith(R_HEADS):
660 return rev
661
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400662 raise GitError('%s: remote %s does not have %s' %
663 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700664
665 def WritesTo(self, ref):
666 """True if the remote stores to the tracking ref.
667 """
668 for spec in self.fetch:
669 if spec.DestMatches(ref):
670 return True
671 return False
672
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800673 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 """Set the fetch refspec to its default value.
675 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800676 if mirror:
677 dst = 'refs/heads/*'
678 else:
679 dst = 'refs/remotes/%s/*' % self.name
680 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700681
682 def Save(self):
683 """Save this remote to the configuration.
684 """
685 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700686 if self.pushUrl is not None:
687 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
688 else:
689 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800691 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530692 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693
694 def _Set(self, key, value):
695 key = 'remote.%s.%s' % (self.name, key)
696 return self._config.SetString(key, value)
697
David Pursehouse8a68ff92012-09-24 12:15:13 +0900698 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900700 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701
702
703class Branch(object):
704 """Configuration options related to a single branch.
705 """
David Pursehouse819827a2020-02-12 15:20:19 +0900706
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700707 def __init__(self, config, name):
708 self._config = config
709 self.name = name
710 self.merge = self._Get('merge')
711
712 r = self._Get('remote')
713 if r:
714 self.remote = self._config.GetRemote(r)
715 else:
716 self.remote = None
717
718 @property
719 def LocalMerge(self):
720 """Convert the merge spec to a local name.
721 """
722 if self.remote and self.merge:
723 return self.remote.ToLocal(self.merge)
724 return None
725
726 def Save(self):
727 """Save this branch back into the configuration.
728 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700729 if self._config.HasSection('branch', self.name):
730 if self.remote:
731 self._Set('remote', self.remote.name)
732 else:
733 self._Set('remote', None)
734 self._Set('merge', self.merge)
735
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500737 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700738 fd.write('[branch "%s"]\n' % self.name)
739 if self.remote:
740 fd.write('\tremote = %s\n' % self.remote.name)
741 if self.merge:
742 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
744 def _Set(self, key, value):
745 key = 'branch.%s.%s' % (self.name, key)
746 return self._config.SetString(key, value)
747
David Pursehouse8a68ff92012-09-24 12:15:13 +0900748 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700749 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900750 return self._config.GetString(key, all_keys=all_keys)
Raman Tenneti7954de12021-07-28 14:36:49 -0700751
752
753class SyncAnalysisState:
754 """Configuration options related to logging of sync state for analysis.
755
756 This object is versioned.
757 """
758 def __init__(self, config, options, superproject_logging_data):
759 """Initializes SyncAnalysisState.
760
761 Saves the following data into the |config| object.
762 - sys.argv, options, superproject's logging data.
763 - repo.*, branch.* and remote.* parameters from config object.
764 - Current time as synctime.
765 - Version number of the object.
766
767 All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
768
769 Args:
770 config: GitConfig object to store all options.
771 options: Options passed to sync returned from optparse. See _Options().
772 superproject_logging_data: A dictionary of superproject data that is to be logged.
773 """
774 self._config = config
775 now = datetime.datetime.utcnow()
776 self._Set('main.synctime', now.isoformat() + 'Z')
777 self._Set('main.version', '1')
778 self._Set('sys.argv', sys.argv)
779 for key, value in superproject_logging_data.items():
780 self._Set(f'superproject.{key}', value)
781 for key, value in options.__dict__.items():
782 self._Set(f'options.{key}', value)
783 config_items = config.DumpConfigDict().items()
784 EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
785 self._SetDictionary({k: v for k, v in config_items
786 if not k.startswith(SYNC_STATE_PREFIX) and
787 k.split('.', 1)[0] in EXTRACT_NAMESPACES})
788
789 def _SetDictionary(self, data):
790 """Save all key/value pairs of |data| dictionary.
791
792 Args:
793 data: A dictionary whose key/value are to be saved.
794 """
795 for key, value in data.items():
796 self._Set(key, value)
797
798 def _Set(self, key, value):
799 """Set the |value| for a |key| in the |_config| member.
800
801 |key| is prepended with the value of SYNC_STATE_PREFIX constant.
802
803 Args:
804 key: Name of the key.
805 value: |value| could be of any type. If it is 'bool', it will be saved
806 as a Boolean and for all other types, it will be saved as a String.
807 """
808 if value is None:
809 return
810 sync_key = f'{SYNC_STATE_PREFIX}{key}'
Raman Tenneti9122bfc2021-07-29 15:11:23 -0700811 sync_key = sync_key.replace('_', '')
Raman Tenneti7954de12021-07-28 14:36:49 -0700812 if isinstance(value, str):
813 self._config.SetString(sync_key, value)
814 elif isinstance(value, bool):
815 self._config.SetBoolean(sync_key, value)
816 else:
817 self._config.SetString(sync_key, str(value))