blob: 05d824cb45ef50e70b176a02cd56521035c8c39a [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
David Pursehousee5913ae2020-02-12 13:56:59 +0900107 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108 """Return true if this configuration file has the key.
109 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700110 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700111 return True
112 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900113 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114 return False
115
Mike Frysinger77b43972020-02-19 17:55:22 -0500116 def GetInt(self, name):
117 """Returns an integer from the configuration file.
118
119 This follows the git config syntax.
120
121 Args:
122 name: The key to lookup.
123
124 Returns:
125 None if the value was not defined, or is not a boolean.
126 Otherwise, the number itself.
127 """
128 v = self.GetString(name)
129 if v is None:
130 return None
131 v = v.strip()
132
133 mult = 1
134 if v.endswith('k'):
135 v = v[:-1]
136 mult = 1024
137 elif v.endswith('m'):
138 v = v[:-1]
139 mult = 1024 * 1024
140 elif v.endswith('g'):
141 v = v[:-1]
142 mult = 1024 * 1024 * 1024
143
144 base = 10
145 if v.startswith('0x'):
146 base = 16
147
148 try:
149 return int(v, base=base) * mult
150 except ValueError:
151 return None
152
Ian Kasprzak835a34b2021-03-05 11:04:49 -0800153 def DumpConfigDict(self):
154 """Returns the current configuration dict.
155
156 Configuration data is information only (e.g. logging) and
157 should not be considered a stable data-source.
158
159 Returns:
160 dict of {<key>, <value>} for git configuration cache.
161 <value> are strings converted by GetString.
162 """
163 config_dict = {}
164 for key in self._cache:
165 config_dict[key] = self.GetString(key)
166 return config_dict
167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168 def GetBoolean(self, name):
169 """Returns a boolean from the configuration file.
170 None : The value was not defined, or is not a boolean.
171 True : The value was set to true or yes.
172 False: The value was set to false or no.
173 """
174 v = self.GetString(name)
175 if v is None:
176 return None
177 v = v.lower()
178 if v in ('true', 'yes'):
179 return True
180 if v in ('false', 'no'):
181 return False
182 return None
183
Mike Frysinger38867fb2021-02-09 23:14:41 -0500184 def SetBoolean(self, name, value):
185 """Set the truthy value for a key."""
186 if value is not None:
187 value = 'true' if value else 'false'
188 self.SetString(name, value)
189
David Pursehouse8a68ff92012-09-24 12:15:13 +0900190 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191 """Get the first value for a key, or None if it is not defined.
192
193 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900194 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700196 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700197 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198 except KeyError:
199 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900200 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201 v = []
202
David Pursehouse8a68ff92012-09-24 12:15:13 +0900203 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700204 if v:
205 return v[0]
206 return None
207
208 r = []
209 r.extend(v)
210 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900211 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212 return r
213
214 def SetString(self, name, value):
215 """Set the value(s) for a key.
216 Only this configuration file is modified.
217
218 The supplied value should be either a string,
219 or a list of strings (to store multiple values).
220 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700221 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222
223 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700224 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700225 except KeyError:
226 old = []
227
228 if value is None:
229 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700230 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231 self._do('--unset-all', name)
232
233 elif isinstance(value, list):
234 if len(value) == 0:
235 self.SetString(name, None)
236
237 elif len(value) == 1:
238 self.SetString(name, value[0])
239
240 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700241 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700242 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700243 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244 self._do('--add', name, value[i])
245
246 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700247 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700248 self._do('--replace-all', name, value)
249
250 def GetRemote(self, name):
251 """Get the remote.$name.* configuration values as an object.
252 """
253 try:
254 r = self._remotes[name]
255 except KeyError:
256 r = Remote(self, name)
257 self._remotes[r.name] = r
258 return r
259
260 def GetBranch(self, name):
261 """Get the branch.$name.* configuration values as an object.
262 """
263 try:
264 b = self._branches[name]
265 except KeyError:
266 b = Branch(self, name)
267 self._branches[b.name] = b
268 return b
269
Raman Tenneti7954de12021-07-28 14:36:49 -0700270 def GetSyncAnalysisStateData(self):
271 """Returns data to be logged for the analysis of sync performance."""
272 return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)}
273
274 def UpdateSyncAnalysisState(self, options, superproject_logging_data):
275 """Update Config's SYNC_STATE_PREFIX* data with the latest sync data.
276
277 Args:
278 options: Options passed to sync returned from optparse. See _Options().
279 superproject_logging_data: A dictionary of superproject data that is to be logged.
280
281 Returns:
282 SyncAnalysisState object.
283 """
284 return SyncAnalysisState(self, options, superproject_logging_data)
285
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700286 def GetSubSections(self, section):
287 """List all subsection names matching $section.*.*
288 """
289 return self._sections.get(section, set())
290
David Pursehousee5913ae2020-02-12 13:56:59 +0900291 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700292 """Does at least one key in section.subsection exist?
293 """
294 try:
295 return subsection in self._sections[section]
296 except KeyError:
297 return False
298
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700299 def UrlInsteadOf(self, url):
300 """Resolve any url.*.insteadof references.
301 """
302 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700303 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
304 if old_url is not None and url.startswith(old_url):
305 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700306 return url
307
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700308 @property
309 def _sections(self):
310 d = self._section_dict
311 if d is None:
312 d = {}
313 for name in self._cache.keys():
314 p = name.split('.')
315 if 2 == len(p):
316 section = p[0]
317 subsect = ''
318 else:
319 section = p[0]
320 subsect = '.'.join(p[1:-1])
321 if section not in d:
322 d[section] = set()
323 d[section].add(subsect)
324 self._section_dict = d
325 return d
326
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700327 @property
328 def _cache(self):
329 if self._cache_dict is None:
330 self._cache_dict = self._Read()
331 return self._cache_dict
332
333 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100334 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700335 if d is None:
336 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100337 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700338 return d
339
Anthony King85b24ac2014-05-06 15:57:48 +0100340 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700341 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900342 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800343 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700344 return None
345 except OSError:
346 return None
347 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100348 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500349 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100350 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100351 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800352 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700353 return None
354
Anthony King85b24ac2014-05-06 15:57:48 +0100355 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700356 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500357 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100358 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100359 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100360 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800361 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700362
363 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700364 """
365 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700366
David Aguilar438c5472009-06-28 15:09:16 -0700367 This internal method populates the GitConfig cache.
368
369 """
David Aguilar438c5472009-06-28 15:09:16 -0700370 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700371 d = self._do('--null', '--list')
372 if d is None:
373 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800374 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700375 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900376 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700377 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900378 key = line
379 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700380
381 if key in c:
382 c[key].append(val)
383 else:
384 c[key] = [val]
385
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700386 return c
387
388 def _do(self, *args):
Xin Li0cb6e922021-06-16 10:19:00 -0700389 if self.file == self._SYSTEM_CONFIG:
390 command = ['config', '--system', '--includes']
391 else:
392 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700393 command.extend(args)
394
395 p = GitCommand(None,
396 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900397 capture_stdout=True,
398 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700399 if p.Wait() == 0:
400 return p.stdout
401 else:
402 GitError('git config %s: %s' % (str(args), p.stderr))
403
404
Mike Frysingerf841ca42020-02-18 21:31:51 -0500405class RepoConfig(GitConfig):
406 """User settings for repo itself."""
407
408 _USER_CONFIG = '~/.repoconfig/config'
409
410
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700411class RefSpec(object):
412 """A Git refspec line, split into its components:
413
414 forced: True if the line starts with '+'
415 src: Left side of the line
416 dst: Right side of the line
417 """
418
419 @classmethod
420 def FromString(cls, rs):
421 lhs, rhs = rs.split(':', 2)
422 if lhs.startswith('+'):
423 lhs = lhs[1:]
424 forced = True
425 else:
426 forced = False
427 return cls(forced, lhs, rhs)
428
429 def __init__(self, forced, lhs, rhs):
430 self.forced = forced
431 self.src = lhs
432 self.dst = rhs
433
434 def SourceMatches(self, rev):
435 if self.src:
436 if rev == self.src:
437 return True
438 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
439 return True
440 return False
441
442 def DestMatches(self, ref):
443 if self.dst:
444 if ref == self.dst:
445 return True
446 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
447 return True
448 return False
449
450 def MapSource(self, rev):
451 if self.src.endswith('/*'):
452 return self.dst[:-1] + rev[len(self.src) - 1:]
453 return self.dst
454
455 def __str__(self):
456 s = ''
457 if self.forced:
458 s += '+'
459 if self.src:
460 s += self.src
461 if self.dst:
462 s += ':'
463 s += self.dst
464 return s
465
466
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700467URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700468
David Pursehouse819827a2020-02-12 15:20:19 +0900469
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700470def GetSchemeFromUrl(url):
471 m = URI_ALL.match(url)
472 if m:
473 return m.group(1)
474 return None
475
David Pursehouse819827a2020-02-12 15:20:19 +0900476
Dan Willemsen0745bb22015-08-17 13:41:45 -0700477@contextlib.contextmanager
478def GetUrlCookieFile(url, quiet):
479 if url.startswith('persistent-'):
480 try:
481 p = subprocess.Popen(
482 ['git-remote-persistent-https', '-print_config', url],
483 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
484 stderr=subprocess.PIPE)
485 try:
486 cookieprefix = 'http.cookiefile='
487 proxyprefix = 'http.proxy='
488 cookiefile = None
489 proxy = None
490 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500491 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700492 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900493 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700494 if line.startswith(proxyprefix):
495 proxy = line[len(proxyprefix):]
496 # Leave subprocess open, as cookie file may be transient.
497 if cookiefile or proxy:
498 yield cookiefile, proxy
499 return
500 finally:
501 p.stdin.close()
502 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500503 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700504 if ' -print_config' in err_msg:
505 pass # Persistent proxy doesn't support -print_config.
506 elif not quiet:
507 print(err_msg, file=sys.stderr)
508 except OSError as e:
509 if e.errno == errno.ENOENT:
510 pass # No persistent proxy.
511 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900512 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
513 if cookiefile:
514 cookiefile = os.path.expanduser(cookiefile)
515 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700516
David Pursehouse819827a2020-02-12 15:20:19 +0900517
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700518class Remote(object):
519 """Configuration options related to a remote.
520 """
David Pursehouse819827a2020-02-12 15:20:19 +0900521
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 def __init__(self, config, name):
523 self._config = config
524 self.name = name
525 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700526 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700527 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800528 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530529 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900530 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800531 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800532
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100533 def _InsteadOf(self):
534 globCfg = GitConfig.ForUser()
535 urlList = globCfg.GetSubSections('url')
536 longest = ""
537 longestUrl = ""
538
539 for url in urlList:
540 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900541 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100542
543 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900544 if (self.url.startswith(insteadOf)
545 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100546 longest = insteadOf
547 longestUrl = url
548
549 if len(longest) == 0:
550 return self.url
551
552 return self.url.replace(longest, longestUrl, 1)
553
Mike Frysinger339f2df2021-05-06 00:44:42 -0400554 def PreConnectFetch(self, ssh_proxy):
Mike Frysinger19e409c2021-05-05 19:44:35 -0400555 """Run any setup for this remote before we connect to it.
556
557 In practice, if the remote is using SSH, we'll attempt to create a new
558 SSH master session to it for reuse across projects.
559
Mike Frysinger339f2df2021-05-06 00:44:42 -0400560 Args:
561 ssh_proxy: The SSH settings for managing master sessions.
562
Mike Frysinger19e409c2021-05-05 19:44:35 -0400563 Returns:
564 Whether the preconnect phase for this remote was successful.
565 """
Mike Frysinger339f2df2021-05-06 00:44:42 -0400566 if not ssh_proxy:
567 return True
568
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100569 connectionUrl = self._InsteadOf()
Mike Frysinger339f2df2021-05-06 00:44:42 -0400570 return ssh_proxy.preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700571
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200572 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800573 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800574 if self.review is None:
575 return None
576
577 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700578 if u.startswith('persistent-'):
579 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100580 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800581 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700582 if u.endswith('/Gerrit'):
583 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800584 if u.endswith('/ssh_info'):
585 u = u[:len(u) - len('/ssh_info')]
586 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900587 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800588 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800589
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700590 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800591 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700592 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800593 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
594 self._review_url = self._SshReviewUrl(userEmail, host, port)
595 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100596 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800597 self._review_url = u # Assume it's right
598 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200599 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
600 self._review_url = http_url
601 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700602 else:
603 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800604 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200605 if not validate_certs:
606 context = ssl._create_unverified_context()
607 info = urllib.request.urlopen(info_url, context=context).read()
608 else:
609 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400610 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700611 # If `info` contains '<', we assume the server gave us some sort
612 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700613 #
Conley Owens745a39b2013-06-05 13:16:18 -0700614 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800615 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700616 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400617 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800618 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000619 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700620 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800621 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700622 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700623 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900624 except HTTPException as e:
625 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800626
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800627 REVIEW_CACHE[u] = self._review_url
628 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800629
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800630 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700631 username = self._config.GetString('review.%s.username' % self.review)
632 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800633 username = userEmail.split('@')[0]
634 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635
636 def ToLocal(self, rev):
637 """Convert a remote revision string to something we have locally.
638 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200639 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641
642 if not rev.startswith('refs/'):
643 rev = R_HEADS + rev
644
645 for spec in self.fetch:
646 if spec.SourceMatches(rev):
647 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600648
649 if not rev.startswith(R_HEADS):
650 return rev
651
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400652 raise GitError('%s: remote %s does not have %s' %
653 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654
655 def WritesTo(self, ref):
656 """True if the remote stores to the tracking ref.
657 """
658 for spec in self.fetch:
659 if spec.DestMatches(ref):
660 return True
661 return False
662
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800663 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700664 """Set the fetch refspec to its default value.
665 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800666 if mirror:
667 dst = 'refs/heads/*'
668 else:
669 dst = 'refs/remotes/%s/*' % self.name
670 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700671
672 def Save(self):
673 """Save this remote to the configuration.
674 """
675 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700676 if self.pushUrl is not None:
677 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
678 else:
679 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700680 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800681 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530682 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683
684 def _Set(self, key, value):
685 key = 'remote.%s.%s' % (self.name, key)
686 return self._config.SetString(key, value)
687
David Pursehouse8a68ff92012-09-24 12:15:13 +0900688 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900690 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691
692
693class Branch(object):
694 """Configuration options related to a single branch.
695 """
David Pursehouse819827a2020-02-12 15:20:19 +0900696
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697 def __init__(self, config, name):
698 self._config = config
699 self.name = name
700 self.merge = self._Get('merge')
701
702 r = self._Get('remote')
703 if r:
704 self.remote = self._config.GetRemote(r)
705 else:
706 self.remote = None
707
708 @property
709 def LocalMerge(self):
710 """Convert the merge spec to a local name.
711 """
712 if self.remote and self.merge:
713 return self.remote.ToLocal(self.merge)
714 return None
715
716 def Save(self):
717 """Save this branch back into the configuration.
718 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700719 if self._config.HasSection('branch', self.name):
720 if self.remote:
721 self._Set('remote', self.remote.name)
722 else:
723 self._Set('remote', None)
724 self._Set('merge', self.merge)
725
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700726 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500727 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700728 fd.write('[branch "%s"]\n' % self.name)
729 if self.remote:
730 fd.write('\tremote = %s\n' % self.remote.name)
731 if self.merge:
732 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733
734 def _Set(self, key, value):
735 key = 'branch.%s.%s' % (self.name, key)
736 return self._config.SetString(key, value)
737
David Pursehouse8a68ff92012-09-24 12:15:13 +0900738 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900740 return self._config.GetString(key, all_keys=all_keys)
Raman Tenneti7954de12021-07-28 14:36:49 -0700741
742
743class SyncAnalysisState:
744 """Configuration options related to logging of sync state for analysis.
745
746 This object is versioned.
747 """
748 def __init__(self, config, options, superproject_logging_data):
749 """Initializes SyncAnalysisState.
750
751 Saves the following data into the |config| object.
752 - sys.argv, options, superproject's logging data.
753 - repo.*, branch.* and remote.* parameters from config object.
754 - Current time as synctime.
755 - Version number of the object.
756
757 All the keys saved by this object are prepended with SYNC_STATE_PREFIX.
758
759 Args:
760 config: GitConfig object to store all options.
761 options: Options passed to sync returned from optparse. See _Options().
762 superproject_logging_data: A dictionary of superproject data that is to be logged.
763 """
764 self._config = config
765 now = datetime.datetime.utcnow()
766 self._Set('main.synctime', now.isoformat() + 'Z')
767 self._Set('main.version', '1')
768 self._Set('sys.argv', sys.argv)
769 for key, value in superproject_logging_data.items():
770 self._Set(f'superproject.{key}', value)
771 for key, value in options.__dict__.items():
772 self._Set(f'options.{key}', value)
773 config_items = config.DumpConfigDict().items()
774 EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'}
775 self._SetDictionary({k: v for k, v in config_items
776 if not k.startswith(SYNC_STATE_PREFIX) and
777 k.split('.', 1)[0] in EXTRACT_NAMESPACES})
778
779 def _SetDictionary(self, data):
780 """Save all key/value pairs of |data| dictionary.
781
782 Args:
783 data: A dictionary whose key/value are to be saved.
784 """
785 for key, value in data.items():
786 self._Set(key, value)
787
788 def _Set(self, key, value):
789 """Set the |value| for a |key| in the |_config| member.
790
791 |key| is prepended with the value of SYNC_STATE_PREFIX constant.
792
793 Args:
794 key: Name of the key.
795 value: |value| could be of any type. If it is 'bool', it will be saved
796 as a Boolean and for all other types, it will be saved as a String.
797 """
798 if value is None:
799 return
800 sync_key = f'{SYNC_STATE_PREFIX}{key}'
801 if isinstance(value, str):
802 self._config.SetString(sync_key, value)
803 elif isinstance(value, bool):
804 self._config.SetBoolean(sync_key, value)
805 else:
806 self._config.SetString(sync_key, str(value))