blob: 6f80ae089595bcf7e93abfb453678554a724366f [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
Mike Frysingeracf63b22019-06-13 02:24:21 -040025import urllib.error
26import urllib.request
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070027
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080028from error import GitError, UploadError
Renaud Paquay010fed72016-11-11 14:25:29 -080029import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040030from repo_trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070031from git_command import GitCommand
Zac Livingston9ead97b2017-06-13 08:29:04 -060032from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070033
Raman Tenneti7954de12021-07-28 14:36:49 -070034# Prefix that is prepended to all the keys of SyncAnalysisState's data
35# that is saved in the config.
36SYNC_STATE_PREFIX = 'repo.syncstate.'
37
David Pursehouse1d947b32012-10-25 12:23:11 +090038ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
Shawn O. Pearce146fe902009-03-25 14:06:43 -070040REVIEW_CACHE = dict()
41
David Pursehouse819827a2020-02-12 15:20:19 +090042
Zac Livingston9ead97b2017-06-13 08:29:04 -060043def IsChange(rev):
44 return rev.startswith(R_CHANGES)
45
David Pursehouse819827a2020-02-12 15:20:19 +090046
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070047def IsId(rev):
48 return ID_RE.match(rev)
49
David Pursehouse819827a2020-02-12 15:20:19 +090050
Zac Livingston9ead97b2017-06-13 08:29:04 -060051def IsTag(rev):
52 return rev.startswith(R_TAGS)
53
David Pursehouse819827a2020-02-12 15:20:19 +090054
Zac Livingston9ead97b2017-06-13 08:29:04 -060055def IsImmutable(rev):
56 return IsChange(rev) or IsId(rev) or IsTag(rev)
57
David Pursehouse819827a2020-02-12 15:20:19 +090058
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070059def _key(name):
60 parts = name.split('.')
61 if len(parts) < 2:
62 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090063 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070064 parts[-1] = parts[-1].lower()
65 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070066
David Pursehouse819827a2020-02-12 15:20:19 +090067
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070069 _ForUser = None
70
Mike Frysingerf841ca42020-02-18 21:31:51 -050071 _USER_CONFIG = '~/.gitconfig'
72
Xin Li0cb6e922021-06-16 10:19:00 -070073 _ForSystem = None
74 _SYSTEM_CONFIG = '/etc/gitconfig'
75
76 @classmethod
77 def ForSystem(cls):
78 if cls._ForSystem is None:
79 cls._ForSystem = cls(configfile=cls._SYSTEM_CONFIG)
80 return cls._ForSystem
81
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082 @classmethod
83 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070084 if cls._ForUser is None:
Mike Frysingerf841ca42020-02-18 21:31:51 -050085 cls._ForUser = cls(configfile=os.path.expanduser(cls._USER_CONFIG))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070086 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070087
88 @classmethod
89 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090090 return cls(configfile=os.path.join(gitdir, 'config'),
91 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070092
Anthony King85b24ac2014-05-06 15:57:48 +010093 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090094 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095 self.defaults = defaults
96 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070097 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098 self._remotes = {}
99 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700100
Anthony King85b24ac2014-05-06 15:57:48 +0100101 self._json = jsonFile
102 if self._json is None:
103 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900104 os.path.dirname(self.file),
105 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700106
Jack Neusc474c9c2021-07-26 23:08:54 +0000107 def ClearCache(self):
108 """Clear the in-memory cache of config."""
109 self._cache_dict = None
110
David Pursehousee5913ae2020-02-12 13:56:59 +0900111 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700112 """Return true if this configuration file has the key.
113 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700114 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115 return True
116 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900117 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118 return False
119
Mike Frysinger77b43972020-02-19 17:55:22 -0500120 def GetInt(self, name):
121 """Returns an integer from the configuration file.
122
123 This follows the git config syntax.
124
125 Args:
126 name: The key to lookup.
127
128 Returns:
129 None if the value was not defined, or is not a boolean.
130 Otherwise, the number itself.
131 """
132 v = self.GetString(name)
133 if v is None:
134 return None
135 v = v.strip()
136
137 mult = 1
138 if v.endswith('k'):
139 v = v[:-1]
140 mult = 1024
141 elif v.endswith('m'):
142 v = v[:-1]
143 mult = 1024 * 1024
144 elif v.endswith('g'):
145 v = v[:-1]
146 mult = 1024 * 1024 * 1024
147
148 base = 10
149 if v.startswith('0x'):
150 base = 16
151
152 try:
153 return int(v, base=base) * mult
154 except ValueError:
155 return None
156
Ian Kasprzak835a34b2021-03-05 11:04:49 -0800157 def DumpConfigDict(self):
158 """Returns the current configuration dict.
159
160 Configuration data is information only (e.g. logging) and
161 should not be considered a stable data-source.
162
163 Returns:
164 dict of {<key>, <value>} for git configuration cache.
165 <value> are strings converted by GetString.
166 """
167 config_dict = {}
168 for key in self._cache:
169 config_dict[key] = self.GetString(key)
170 return config_dict
171
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 def GetBoolean(self, name):
173 """Returns a boolean from the configuration file.
174 None : The value was not defined, or is not a boolean.
175 True : The value was set to true or yes.
176 False: The value was set to false or no.
177 """
178 v = self.GetString(name)
179 if v is None:
180 return None
181 v = v.lower()
182 if v in ('true', 'yes'):
183 return True
184 if v in ('false', 'no'):
185 return False
186 return None
187
Mike Frysinger38867fb2021-02-09 23:14:41 -0500188 def SetBoolean(self, name, value):
189 """Set the truthy value for a key."""
190 if value is not None:
191 value = 'true' if value else 'false'
192 self.SetString(name, value)
193
David Pursehouse8a68ff92012-09-24 12:15:13 +0900194 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195 """Get the first value for a key, or None if it is not defined.
196
197 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900198 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700200 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700201 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202 except KeyError:
203 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900204 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 v = []
206
David Pursehouse8a68ff92012-09-24 12:15:13 +0900207 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208 if v:
209 return v[0]
210 return None
211
212 r = []
213 r.extend(v)
214 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900215 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 return r
217
218 def SetString(self, name, value):
219 """Set the value(s) for a key.
220 Only this configuration file is modified.
221
222 The supplied value should be either a string,
223 or a list of strings (to store multiple values).
224 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700225 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226
227 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700228 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 except KeyError:
230 old = []
231
232 if value is None:
233 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700234 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 self._do('--unset-all', name)
236
237 elif isinstance(value, list):
238 if len(value) == 0:
239 self.SetString(name, None)
240
241 elif len(value) == 1:
242 self.SetString(name, value[0])
243
244 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700245 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700247 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 self._do('--add', name, value[i])
249
250 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700251 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700252 self._do('--replace-all', name, value)
253
254 def GetRemote(self, name):
255 """Get the remote.$name.* configuration values as an object.
256 """
257 try:
258 r = self._remotes[name]
259 except KeyError:
260 r = Remote(self, name)
261 self._remotes[r.name] = r
262 return r
263
264 def GetBranch(self, name):
265 """Get the branch.$name.* configuration values as an object.
266 """
267 try:
268 b = self._branches[name]
269 except KeyError:
270 b = Branch(self, name)
271 self._branches[b.name] = b
272 return b
273
Raman Tenneti7954de12021-07-28 14:36:49 -0700274 def GetSyncAnalysisStateData(self):
275 """Returns data to be logged for the analysis of sync performance."""
276 return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
277
278 def UpdateSyncAnalysisState(self, options, superproject_logging_data):
279 """Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
280
281 Args:
282 options: Options passed to sync returned from optparse. See _Options().
283 superproject_logging_data: A dictionary of superproject data that is to be logged.
284
285 Returns:
286 SyncAnalysisState object.
287 """
288 return SyncAnalysisState(self, options, superproject_logging_data)
289
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700290 def GetSubSections(self, section):
291 """List all subsection names matching $section.*.*
292 """
293 return self._sections.get(section, set())
294
David Pursehousee5913ae2020-02-12 13:56:59 +0900295 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700296 """Does at least one key in section.subsection exist?
297 """
298 try:
299 return subsection in self._sections[section]
300 except KeyError:
301 return False
302
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700303 def UrlInsteadOf(self, url):
304 """Resolve any url.*.insteadof references.
305 """
306 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700307 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
308 if old_url is not None and url.startswith(old_url):
309 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700310 return url
311
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700312 @property
313 def _sections(self):
314 d = self._section_dict
315 if d is None:
316 d = {}
317 for name in self._cache.keys():
318 p = name.split('.')
319 if 2 == len(p):
320 section = p[0]
321 subsect = ''
322 else:
323 section = p[0]
324 subsect = '.'.join(p[1:-1])
325 if section not in d:
326 d[section] = set()
327 d[section].add(subsect)
328 self._section_dict = d
329 return d
330
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 @property
332 def _cache(self):
333 if self._cache_dict is None:
334 self._cache_dict = self._Read()
335 return self._cache_dict
336
337 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100338 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700339 if d is None:
340 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100341 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700342 return d
343
Anthony King85b24ac2014-05-06 15:57:48 +0100344 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700345 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900346 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800347 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700348 return None
349 except OSError:
350 return None
351 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100352 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500353 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100354 return json.load(fd)
Sl0v3C00c5ea32021-11-07 08:48:09 +0800355 except (IOError, ValueError):
Mike Frysinger9d96f582021-09-28 11:27:24 -0400356 platform_utils.remove(self._json, missing_ok=True)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700357 return None
358
Anthony King85b24ac2014-05-06 15:57:48 +0100359 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700360 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500361 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100362 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100363 except (IOError, TypeError):
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
366 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700367 """
368 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700369
David Aguilar438c5472009-06-28 15:09:16 -0700370 This internal method populates the GitConfig cache.
371
372 """
David Aguilar438c5472009-06-28 15:09:16 -0700373 c = {}
Mike Frysingerf88282c2021-09-28 15:59:40 -0400374 if not os.path.exists(self.file):
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700375 return c
Mike Frysingerf88282c2021-09-28 15:59:40 -0400376
377 d = self._do('--null', '--list')
Dylan Denge469a0c2018-06-23 15:02:26 +0800378 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700379 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900380 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700381 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900382 key = line
383 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700384
385 if key in c:
386 c[key].append(val)
387 else:
388 c[key] = [val]
389
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700390 return c
391
392 def _do(self, *args):
Xin Li0cb6e922021-06-16 10:19:00 -0700393 if self.file == self._SYSTEM_CONFIG:
394 command = ['config', '--system', '--includes']
395 else:
396 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700397 command.extend(args)
398
399 p = GitCommand(None,
400 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900401 capture_stdout=True,
402 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700403 if p.Wait() == 0:
404 return p.stdout
405 else:
Jack Neusc474c9c2021-07-26 23:08:54 +0000406 raise GitError('git config %s: %s' % (str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700407
408
Mike Frysingerf841ca42020-02-18 21:31:51 -0500409class RepoConfig(GitConfig):
410 """User settings for repo itself."""
411
412 _USER_CONFIG = '~/.repoconfig/config'
413
414
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700415class RefSpec(object):
416 """A Git refspec line, split into its components:
417
418 forced: True if the line starts with '+'
419 src: Left side of the line
420 dst: Right side of the line
421 """
422
423 @classmethod
424 def FromString(cls, rs):
425 lhs, rhs = rs.split(':', 2)
426 if lhs.startswith('+'):
427 lhs = lhs[1:]
428 forced = True
429 else:
430 forced = False
431 return cls(forced, lhs, rhs)
432
433 def __init__(self, forced, lhs, rhs):
434 self.forced = forced
435 self.src = lhs
436 self.dst = rhs
437
438 def SourceMatches(self, rev):
439 if self.src:
440 if rev == self.src:
441 return True
442 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
443 return True
444 return False
445
446 def DestMatches(self, ref):
447 if self.dst:
448 if ref == self.dst:
449 return True
450 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
451 return True
452 return False
453
454 def MapSource(self, rev):
455 if self.src.endswith('/*'):
456 return self.dst[:-1] + rev[len(self.src) - 1:]
457 return self.dst
458
459 def __str__(self):
460 s = ''
461 if self.forced:
462 s += '+'
463 if self.src:
464 s += self.src
465 if self.dst:
466 s += ':'
467 s += self.dst
468 return s
469
470
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700471URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700472
David Pursehouse819827a2020-02-12 15:20:19 +0900473
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700474def GetSchemeFromUrl(url):
475 m = URI_ALL.match(url)
476 if m:
477 return m.group(1)
478 return None
479
David Pursehouse819827a2020-02-12 15:20:19 +0900480
Dan Willemsen0745bb22015-08-17 13:41:45 -0700481@contextlib.contextmanager
482def GetUrlCookieFile(url, quiet):
483 if url.startswith('persistent-'):
484 try:
485 p = subprocess.Popen(
486 ['git-remote-persistent-https', '-print_config', url],
487 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
488 stderr=subprocess.PIPE)
489 try:
490 cookieprefix = 'http.cookiefile='
491 proxyprefix = 'http.proxy='
492 cookiefile = None
493 proxy = None
494 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500495 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700496 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900497 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700498 if line.startswith(proxyprefix):
499 proxy = line[len(proxyprefix):]
500 # Leave subprocess open, as cookie file may be transient.
501 if cookiefile or proxy:
502 yield cookiefile, proxy
503 return
504 finally:
505 p.stdin.close()
506 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500507 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700508 if ' -print_config' in err_msg:
509 pass # Persistent proxy doesn't support -print_config.
510 elif not quiet:
511 print(err_msg, file=sys.stderr)
512 except OSError as e:
513 if e.errno == errno.ENOENT:
514 pass # No persistent proxy.
515 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900516 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
517 if cookiefile:
518 cookiefile = os.path.expanduser(cookiefile)
519 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700520
David Pursehouse819827a2020-02-12 15:20:19 +0900521
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522class Remote(object):
523 """Configuration options related to a remote.
524 """
David Pursehouse819827a2020-02-12 15:20:19 +0900525
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700526 def __init__(self, config, name):
527 self._config = config
528 self.name = name
529 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700530 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800532 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530533 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900534 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800535 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800536
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100537 def _InsteadOf(self):
538 globCfg = GitConfig.ForUser()
539 urlList = globCfg.GetSubSections('url')
540 longest = ""
541 longestUrl = ""
542
543 for url in urlList:
544 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900545 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100546
547 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900548 if (self.url.startswith(insteadOf)
549 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100550 longest = insteadOf
551 longestUrl = url
552
553 if len(longest) == 0:
554 return self.url
555
556 return self.url.replace(longest, longestUrl, 1)
557
Mike Frysinger339f2df2021-05-06 00:44:42 -0400558 def PreConnectFetch(self, ssh_proxy):
Mike Frysinger19e409c2021-05-05 19:44:35 -0400559 """Run any setup for this remote before we connect to it.
560
561 In practice, if the remote is using SSH, we'll attempt to create a new
562 SSH master session to it for reuse across projects.
563
Mike Frysinger339f2df2021-05-06 00:44:42 -0400564 Args:
565 ssh_proxy: The SSH settings for managing master sessions.
566
Mike Frysinger19e409c2021-05-05 19:44:35 -0400567 Returns:
568 Whether the preconnect phase for this remote was successful.
569 """
Mike Frysinger339f2df2021-05-06 00:44:42 -0400570 if not ssh_proxy:
571 return True
572
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100573 connectionUrl = self._InsteadOf()
Mike Frysinger339f2df2021-05-06 00:44:42 -0400574 return ssh_proxy.preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700575
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200576 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800577 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800578 if self.review is None:
579 return None
580
581 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700582 if u.startswith('persistent-'):
583 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100584 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800585 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700586 if u.endswith('/Gerrit'):
587 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800588 if u.endswith('/ssh_info'):
589 u = u[:len(u) - len('/ssh_info')]
590 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900591 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800592 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800593
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700594 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800595 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700596 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800597 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
598 self._review_url = self._SshReviewUrl(userEmail, host, port)
599 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100600 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800601 self._review_url = u # Assume it's right
602 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200603 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
604 self._review_url = http_url
605 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700606 else:
607 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800608 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200609 if not validate_certs:
610 context = ssl._create_unverified_context()
611 info = urllib.request.urlopen(info_url, context=context).read()
612 else:
613 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400614 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700615 # If `info` contains '<', we assume the server gave us some sort
616 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700617 #
Conley Owens745a39b2013-06-05 13:16:18 -0700618 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800619 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700620 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400621 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800622 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000623 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700624 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800625 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700626 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700627 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900628 except HTTPException as e:
629 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800630
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800631 REVIEW_CACHE[u] = self._review_url
632 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800633
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800634 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700635 username = self._config.GetString('review.%s.username' % self.review)
636 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800637 username = userEmail.split('@')[0]
638 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639
640 def ToLocal(self, rev):
641 """Convert a remote revision string to something we have locally.
642 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200643 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700644 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645
646 if not rev.startswith('refs/'):
647 rev = R_HEADS + rev
648
649 for spec in self.fetch:
650 if spec.SourceMatches(rev):
651 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600652
653 if not rev.startswith(R_HEADS):
654 return rev
655
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400656 raise GitError('%s: remote %s does not have %s' %
657 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700658
659 def WritesTo(self, ref):
660 """True if the remote stores to the tracking ref.
661 """
662 for spec in self.fetch:
663 if spec.DestMatches(ref):
664 return True
665 return False
666
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800667 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700668 """Set the fetch refspec to its default value.
669 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800670 if mirror:
671 dst = 'refs/heads/*'
672 else:
673 dst = 'refs/remotes/%s/*' % self.name
674 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700675
676 def Save(self):
677 """Save this remote to the configuration.
678 """
679 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700680 if self.pushUrl is not None:
681 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
682 else:
683 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800685 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530686 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700687
688 def _Set(self, key, value):
689 key = 'remote.%s.%s' % (self.name, key)
690 return self._config.SetString(key, value)
691
David Pursehouse8a68ff92012-09-24 12:15:13 +0900692 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700693 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900694 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695
696
697class Branch(object):
698 """Configuration options related to a single branch.
699 """
David Pursehouse819827a2020-02-12 15:20:19 +0900700
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701 def __init__(self, config, name):
702 self._config = config
703 self.name = name
704 self.merge = self._Get('merge')
705
706 r = self._Get('remote')
707 if r:
708 self.remote = self._config.GetRemote(r)
709 else:
710 self.remote = None
711
712 @property
713 def LocalMerge(self):
714 """Convert the merge spec to a local name.
715 """
716 if self.remote and self.merge:
717 return self.remote.ToLocal(self.merge)
718 return None
719
720 def Save(self):
721 """Save this branch back into the configuration.
722 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700723 if self._config.HasSection('branch', self.name):
724 if self.remote:
725 self._Set('remote', self.remote.name)
726 else:
727 self._Set('remote', None)
728 self._Set('merge', self.merge)
729
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500731 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700732 fd.write('[branch "%s"]\n' % self.name)
733 if self.remote:
734 fd.write('\tremote = %s\n' % self.remote.name)
735 if self.merge:
736 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
738 def _Set(self, key, value):
739 key = 'branch.%s.%s' % (self.name, key)
740 return self._config.SetString(key, value)
741
David Pursehouse8a68ff92012-09-24 12:15:13 +0900742 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900744 return self._config.GetString(key, all_keys=all_keys)
Raman Tenneti7954de12021-07-28 14:36:49 -0700745
746
747class SyncAnalysisState:
748 """Configuration options related to logging of sync state for analysis.
749
750 This object is versioned.
751 """
752 def __init__(self, config, options, superproject_logging_data):
753 """Initializes SyncAnalysisState.
754
755 Saves the following data into the |config| object.
756 - sys.argv, options, superproject's logging data.
757 - repo.*, branch.* and remote.* parameters from config object.
758 - Current time as synctime.
759 - Version number of the object.
760
761 All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
762
763 Args:
764 config: GitConfig object to store all options.
765 options: Options passed to sync returned from optparse. See _Options().
766 superproject_logging_data: A dictionary of superproject data that is to be logged.
767 """
768 self._config = config
769 now = datetime.datetime.utcnow()
770 self._Set('main.synctime', now.isoformat() + 'Z')
771 self._Set('main.version', '1')
772 self._Set('sys.argv', sys.argv)
773 for key, value in superproject_logging_data.items():
774 self._Set(f'superproject.{key}', value)
775 for key, value in options.__dict__.items():
776 self._Set(f'options.{key}', value)
777 config_items = config.DumpConfigDict().items()
778 EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
779 self._SetDictionary({k: v for k, v in config_items
780 if not k.startswith(SYNC_STATE_PREFIX) and
781 k.split('.', 1)[0] in EXTRACT_NAMESPACES})
782
783 def _SetDictionary(self, data):
784 """Save all key/value pairs of |data| dictionary.
785
786 Args:
787 data: A dictionary whose key/value are to be saved.
788 """
789 for key, value in data.items():
790 self._Set(key, value)
791
792 def _Set(self, key, value):
793 """Set the |value| for a |key| in the |_config| member.
794
795 |key| is prepended with the value of SYNC_STATE_PREFIX constant.
796
797 Args:
798 key: Name of the key.
799 value: |value| could be of any type. If it is 'bool', it will be saved
800 as a Boolean and for all other types, it will be saved as a String.
801 """
802 if value is None:
803 return
804 sync_key = f'{SYNC_STATE_PREFIX}{key}'
Raman Tenneti9122bfc2021-07-29 15:11:23 -0700805 sync_key = sync_key.replace('_', '')
Raman Tenneti7954de12021-07-28 14:36:49 -0700806 if isinstance(value, str):
807 self._config.SetString(sync_key, value)
808 elif isinstance(value, bool):
809 self._config.SetBoolean(sync_key, value)
810 else:
811 self._config.SetString(sync_key, str(value))