blob: bc70d160898e2f83da891834f82a34a7d728306d [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)
Mike Frysinger9d96f582021-09-28 11:27:24 -0400355 except (IOError, ValueErrorl):
356 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 = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700374 d = self._do('--null', '--list')
375 if d is None:
376 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800377 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700378 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900379 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700380 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900381 key = line
382 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700383
384 if key in c:
385 c[key].append(val)
386 else:
387 c[key] = [val]
388
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700389 return c
390
391 def _do(self, *args):
Xin Li0cb6e922021-06-16 10:19:00 -0700392 if self.file == self._SYSTEM_CONFIG:
393 command = ['config', '--system', '--includes']
394 else:
395 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700396 command.extend(args)
397
398 p = GitCommand(None,
399 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900400 capture_stdout=True,
401 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700402 if p.Wait() == 0:
403 return p.stdout
404 else:
Jack Neusc474c9c2021-07-26 23:08:54 +0000405 raise GitError('git config %s: %s' % (str(args), p.stderr))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700406
407
Mike Frysingerf841ca42020-02-18 21:31:51 -0500408class RepoConfig(GitConfig):
409 """User settings for repo itself."""
410
411 _USER_CONFIG = '~/.repoconfig/config'
412
413
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700414class RefSpec(object):
415 """A Git refspec line, split into its components:
416
417 forced: True if the line starts with '+'
418 src: Left side of the line
419 dst: Right side of the line
420 """
421
422 @classmethod
423 def FromString(cls, rs):
424 lhs, rhs = rs.split(':', 2)
425 if lhs.startswith('+'):
426 lhs = lhs[1:]
427 forced = True
428 else:
429 forced = False
430 return cls(forced, lhs, rhs)
431
432 def __init__(self, forced, lhs, rhs):
433 self.forced = forced
434 self.src = lhs
435 self.dst = rhs
436
437 def SourceMatches(self, rev):
438 if self.src:
439 if rev == self.src:
440 return True
441 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
442 return True
443 return False
444
445 def DestMatches(self, ref):
446 if self.dst:
447 if ref == self.dst:
448 return True
449 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
450 return True
451 return False
452
453 def MapSource(self, rev):
454 if self.src.endswith('/*'):
455 return self.dst[:-1] + rev[len(self.src) - 1:]
456 return self.dst
457
458 def __str__(self):
459 s = ''
460 if self.forced:
461 s += '+'
462 if self.src:
463 s += self.src
464 if self.dst:
465 s += ':'
466 s += self.dst
467 return s
468
469
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700470URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700471
David Pursehouse819827a2020-02-12 15:20:19 +0900472
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700473def GetSchemeFromUrl(url):
474 m = URI_ALL.match(url)
475 if m:
476 return m.group(1)
477 return None
478
David Pursehouse819827a2020-02-12 15:20:19 +0900479
Dan Willemsen0745bb22015-08-17 13:41:45 -0700480@contextlib.contextmanager
481def GetUrlCookieFile(url, quiet):
482 if url.startswith('persistent-'):
483 try:
484 p = subprocess.Popen(
485 ['git-remote-persistent-https', '-print_config', url],
486 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
487 stderr=subprocess.PIPE)
488 try:
489 cookieprefix = 'http.cookiefile='
490 proxyprefix = 'http.proxy='
491 cookiefile = None
492 proxy = None
493 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500494 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700495 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900496 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700497 if line.startswith(proxyprefix):
498 proxy = line[len(proxyprefix):]
499 # Leave subprocess open, as cookie file may be transient.
500 if cookiefile or proxy:
501 yield cookiefile, proxy
502 return
503 finally:
504 p.stdin.close()
505 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500506 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700507 if ' -print_config' in err_msg:
508 pass # Persistent proxy doesn't support -print_config.
509 elif not quiet:
510 print(err_msg, file=sys.stderr)
511 except OSError as e:
512 if e.errno == errno.ENOENT:
513 pass # No persistent proxy.
514 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900515 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
516 if cookiefile:
517 cookiefile = os.path.expanduser(cookiefile)
518 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700519
David Pursehouse819827a2020-02-12 15:20:19 +0900520
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700521class Remote(object):
522 """Configuration options related to a remote.
523 """
David Pursehouse819827a2020-02-12 15:20:19 +0900524
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525 def __init__(self, config, name):
526 self._config = config
527 self.name = name
528 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700529 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700530 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800531 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530532 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900533 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800534 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800535
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100536 def _InsteadOf(self):
537 globCfg = GitConfig.ForUser()
538 urlList = globCfg.GetSubSections('url')
539 longest = ""
540 longestUrl = ""
541
542 for url in urlList:
543 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900544 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100545
546 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900547 if (self.url.startswith(insteadOf)
548 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100549 longest = insteadOf
550 longestUrl = url
551
552 if len(longest) == 0:
553 return self.url
554
555 return self.url.replace(longest, longestUrl, 1)
556
Mike Frysinger339f2df2021-05-06 00:44:42 -0400557 def PreConnectFetch(self, ssh_proxy):
Mike Frysinger19e409c2021-05-05 19:44:35 -0400558 """Run any setup for this remote before we connect to it.
559
560 In practice, if the remote is using SSH, we'll attempt to create a new
561 SSH master session to it for reuse across projects.
562
Mike Frysinger339f2df2021-05-06 00:44:42 -0400563 Args:
564 ssh_proxy: The SSH settings for managing master sessions.
565
Mike Frysinger19e409c2021-05-05 19:44:35 -0400566 Returns:
567 Whether the preconnect phase for this remote was successful.
568 """
Mike Frysinger339f2df2021-05-06 00:44:42 -0400569 if not ssh_proxy:
570 return True
571
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100572 connectionUrl = self._InsteadOf()
Mike Frysinger339f2df2021-05-06 00:44:42 -0400573 return ssh_proxy.preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700574
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200575 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800576 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800577 if self.review is None:
578 return None
579
580 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700581 if u.startswith('persistent-'):
582 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100583 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800584 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700585 if u.endswith('/Gerrit'):
586 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800587 if u.endswith('/ssh_info'):
588 u = u[:len(u) - len('/ssh_info')]
589 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900590 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800591 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800592
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700593 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800594 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700595 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800596 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
597 self._review_url = self._SshReviewUrl(userEmail, host, port)
598 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100599 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800600 self._review_url = u # Assume it's right
601 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200602 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
603 self._review_url = http_url
604 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700605 else:
606 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800607 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200608 if not validate_certs:
609 context = ssl._create_unverified_context()
610 info = urllib.request.urlopen(info_url, context=context).read()
611 else:
612 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400613 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700614 # If `info` contains '<', we assume the server gave us some sort
615 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700616 #
Conley Owens745a39b2013-06-05 13:16:18 -0700617 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800618 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700619 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400620 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800621 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000622 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700623 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800624 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700625 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700626 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900627 except HTTPException as e:
628 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800629
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800630 REVIEW_CACHE[u] = self._review_url
631 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800632
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800633 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700634 username = self._config.GetString('review.%s.username' % self.review)
635 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800636 username = userEmail.split('@')[0]
637 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638
639 def ToLocal(self, rev):
640 """Convert a remote revision string to something we have locally.
641 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200642 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700643 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700644
645 if not rev.startswith('refs/'):
646 rev = R_HEADS + rev
647
648 for spec in self.fetch:
649 if spec.SourceMatches(rev):
650 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600651
652 if not rev.startswith(R_HEADS):
653 return rev
654
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400655 raise GitError('%s: remote %s does not have %s' %
656 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700657
658 def WritesTo(self, ref):
659 """True if the remote stores to the tracking ref.
660 """
661 for spec in self.fetch:
662 if spec.DestMatches(ref):
663 return True
664 return False
665
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800666 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 """Set the fetch refspec to its default value.
668 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800669 if mirror:
670 dst = 'refs/heads/*'
671 else:
672 dst = 'refs/remotes/%s/*' % self.name
673 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674
675 def Save(self):
676 """Save this remote to the configuration.
677 """
678 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700679 if self.pushUrl is not None:
680 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
681 else:
682 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800684 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530685 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700686
687 def _Set(self, key, value):
688 key = 'remote.%s.%s' % (self.name, key)
689 return self._config.SetString(key, value)
690
David Pursehouse8a68ff92012-09-24 12:15:13 +0900691 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700692 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900693 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694
695
696class Branch(object):
697 """Configuration options related to a single branch.
698 """
David Pursehouse819827a2020-02-12 15:20:19 +0900699
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700 def __init__(self, config, name):
701 self._config = config
702 self.name = name
703 self.merge = self._Get('merge')
704
705 r = self._Get('remote')
706 if r:
707 self.remote = self._config.GetRemote(r)
708 else:
709 self.remote = None
710
711 @property
712 def LocalMerge(self):
713 """Convert the merge spec to a local name.
714 """
715 if self.remote and self.merge:
716 return self.remote.ToLocal(self.merge)
717 return None
718
719 def Save(self):
720 """Save this branch back into the configuration.
721 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700722 if self._config.HasSection('branch', self.name):
723 if self.remote:
724 self._Set('remote', self.remote.name)
725 else:
726 self._Set('remote', None)
727 self._Set('merge', self.merge)
728
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500730 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700731 fd.write('[branch "%s"]\n' % self.name)
732 if self.remote:
733 fd.write('\tremote = %s\n' % self.remote.name)
734 if self.merge:
735 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700736
737 def _Set(self, key, value):
738 key = 'branch.%s.%s' % (self.name, key)
739 return self._config.SetString(key, value)
740
David Pursehouse8a68ff92012-09-24 12:15:13 +0900741 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900743 return self._config.GetString(key, all_keys=all_keys)
Raman Tenneti7954de12021-07-28 14:36:49 -0700744
745
746class SyncAnalysisState:
747 """Configuration options related to logging of sync state for analysis.
748
749 This object is versioned.
750 """
751 def __init__(self, config, options, superproject_logging_data):
752 """Initializes SyncAnalysisState.
753
754 Saves the following data into the |config| object.
755 - sys.argv, options, superproject's logging data.
756 - repo.*, branch.* and remote.* parameters from config object.
757 - Current time as synctime.
758 - Version number of the object.
759
760 All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
761
762 Args:
763 config: GitConfig object to store all options.
764 options: Options passed to sync returned from optparse. See _Options().
765 superproject_logging_data: A dictionary of superproject data that is to be logged.
766 """
767 self._config = config
768 now = datetime.datetime.utcnow()
769 self._Set('main.synctime', now.isoformat() + 'Z')
770 self._Set('main.version', '1')
771 self._Set('sys.argv', sys.argv)
772 for key, value in superproject_logging_data.items():
773 self._Set(f'superproject.{key}', value)
774 for key, value in options.__dict__.items():
775 self._Set(f'options.{key}', value)
776 config_items = config.DumpConfigDict().items()
777 EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
778 self._SetDictionary({k: v for k, v in config_items
779 if not k.startswith(SYNC_STATE_PREFIX) and
780 k.split('.', 1)[0] in EXTRACT_NAMESPACES})
781
782 def _SetDictionary(self, data):
783 """Save all key/value pairs of |data| dictionary.
784
785 Args:
786 data: A dictionary whose key/value are to be saved.
787 """
788 for key, value in data.items():
789 self._Set(key, value)
790
791 def _Set(self, key, value):
792 """Set the |value| for a |key| in the |_config| member.
793
794 |key| is prepended with the value of SYNC_STATE_PREFIX constant.
795
796 Args:
797 key: Name of the key.
798 value: |value| could be of any type. If it is 'bool', it will be saved
799 as a Boolean and for all other types, it will be saved as a String.
800 """
801 if value is None:
802 return
803 sync_key = f'{SYNC_STATE_PREFIX}{key}'
Raman Tenneti9122bfc2021-07-29 15:11:23 -0700804 sync_key = sync_key.replace('_', '')
Raman Tenneti7954de12021-07-28 14:36:49 -0700805 if isinstance(value, str):
806 self._config.SetString(sync_key, value)
807 elif isinstance(value, bool):
808 self._config.SetBoolean(sync_key, value)
809 else:
810 self._config.SetString(sync_key, str(value))