blob: d7fef8ca10f721eec3bebd09c55bbe1eb96ef28b [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
16import errno
Mike Frysingeracf63b22019-06-13 02:24:21 -040017from http.client import HTTPException
Anthony King85b24ac2014-05-06 15:57:48 +010018import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
20import re
Łukasz Gardońbed59ce2017-08-08 10:18:11 +020021import ssl
Shawn O. Pearcefb231612009-04-10 18:53:46 -070022import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import sys
Mike Frysingeracf63b22019-06-13 02:24:21 -040024import urllib.error
25import urllib.request
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070026
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080027from error import GitError, UploadError
Renaud Paquay010fed72016-11-11 14:25:29 -080028import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040029from repo_trace import Trace
Mike Frysinger5291eaf2021-05-05 15:53:03 -040030import ssh
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
David Pursehouse1d947b32012-10-25 12:23:11 +090034ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035
Shawn O. Pearce146fe902009-03-25 14:06:43 -070036REVIEW_CACHE = dict()
37
David Pursehouse819827a2020-02-12 15:20:19 +090038
Zac Livingston9ead97b2017-06-13 08:29:04 -060039def IsChange(rev):
40 return rev.startswith(R_CHANGES)
41
David Pursehouse819827a2020-02-12 15:20:19 +090042
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043def IsId(rev):
44 return ID_RE.match(rev)
45
David Pursehouse819827a2020-02-12 15:20:19 +090046
Zac Livingston9ead97b2017-06-13 08:29:04 -060047def IsTag(rev):
48 return rev.startswith(R_TAGS)
49
David Pursehouse819827a2020-02-12 15:20:19 +090050
Zac Livingston9ead97b2017-06-13 08:29:04 -060051def IsImmutable(rev):
52 return IsChange(rev) or IsId(rev) or IsTag(rev)
53
David Pursehouse819827a2020-02-12 15:20:19 +090054
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070055def _key(name):
56 parts = name.split('.')
57 if len(parts) < 2:
58 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090059 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070060 parts[-1] = parts[-1].lower()
61 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070062
David Pursehouse819827a2020-02-12 15:20:19 +090063
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070065 _ForUser = None
66
Mike Frysingerf841ca42020-02-18 21:31:51 -050067 _USER_CONFIG = '~/.gitconfig'
68
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070069 @classmethod
70 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070071 if cls._ForUser is None:
Mike Frysingerf841ca42020-02-18 21:31:51 -050072 cls._ForUser = cls(configfile=os.path.expanduser(cls._USER_CONFIG))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070073 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074
75 @classmethod
76 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090077 return cls(configfile=os.path.join(gitdir, 'config'),
78 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070079
Anthony King85b24ac2014-05-06 15:57:48 +010080 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090081 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082 self.defaults = defaults
83 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070084 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070085 self._remotes = {}
86 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070087
Anthony King85b24ac2014-05-06 15:57:48 +010088 self._json = jsonFile
89 if self._json is None:
90 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +090091 os.path.dirname(self.file),
92 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093
David Pursehousee5913ae2020-02-12 13:56:59 +090094 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095 """Return true if this configuration file has the key.
96 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070097 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098 return True
99 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900100 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101 return False
102
Mike Frysinger77b43972020-02-19 17:55:22 -0500103 def GetInt(self, name):
104 """Returns an integer from the configuration file.
105
106 This follows the git config syntax.
107
108 Args:
109 name: The key to lookup.
110
111 Returns:
112 None if the value was not defined, or is not a boolean.
113 Otherwise, the number itself.
114 """
115 v = self.GetString(name)
116 if v is None:
117 return None
118 v = v.strip()
119
120 mult = 1
121 if v.endswith('k'):
122 v = v[:-1]
123 mult = 1024
124 elif v.endswith('m'):
125 v = v[:-1]
126 mult = 1024 * 1024
127 elif v.endswith('g'):
128 v = v[:-1]
129 mult = 1024 * 1024 * 1024
130
131 base = 10
132 if v.startswith('0x'):
133 base = 16
134
135 try:
136 return int(v, base=base) * mult
137 except ValueError:
138 return None
139
Ian Kasprzak835a34b2021-03-05 11:04:49 -0800140 def DumpConfigDict(self):
141 """Returns the current configuration dict.
142
143 Configuration data is information only (e.g. logging) and
144 should not be considered a stable data-source.
145
146 Returns:
147 dict of {<key>, <value>} for git configuration cache.
148 <value> are strings converted by GetString.
149 """
150 config_dict = {}
151 for key in self._cache:
152 config_dict[key] = self.GetString(key)
153 return config_dict
154
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700155 def GetBoolean(self, name):
156 """Returns a boolean from the configuration file.
157 None : The value was not defined, or is not a boolean.
158 True : The value was set to true or yes.
159 False: The value was set to false or no.
160 """
161 v = self.GetString(name)
162 if v is None:
163 return None
164 v = v.lower()
165 if v in ('true', 'yes'):
166 return True
167 if v in ('false', 'no'):
168 return False
169 return None
170
Mike Frysinger38867fb2021-02-09 23:14:41 -0500171 def SetBoolean(self, name, value):
172 """Set the truthy value for a key."""
173 if value is not None:
174 value = 'true' if value else 'false'
175 self.SetString(name, value)
176
David Pursehouse8a68ff92012-09-24 12:15:13 +0900177 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 """Get the first value for a key, or None if it is not defined.
179
180 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900181 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700183 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700184 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700185 except KeyError:
186 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900187 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188 v = []
189
David Pursehouse8a68ff92012-09-24 12:15:13 +0900190 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191 if v:
192 return v[0]
193 return None
194
195 r = []
196 r.extend(v)
197 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900198 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 return r
200
201 def SetString(self, name, value):
202 """Set the value(s) for a key.
203 Only this configuration file is modified.
204
205 The supplied value should be either a string,
206 or a list of strings (to store multiple values).
207 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700208 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700209
210 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700211 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700212 except KeyError:
213 old = []
214
215 if value is None:
216 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700217 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 self._do('--unset-all', name)
219
220 elif isinstance(value, list):
221 if len(value) == 0:
222 self.SetString(name, None)
223
224 elif len(value) == 1:
225 self.SetString(name, value[0])
226
227 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700228 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700230 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700231 self._do('--add', name, value[i])
232
233 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700234 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700235 self._do('--replace-all', name, value)
236
237 def GetRemote(self, name):
238 """Get the remote.$name.* configuration values as an object.
239 """
240 try:
241 r = self._remotes[name]
242 except KeyError:
243 r = Remote(self, name)
244 self._remotes[r.name] = r
245 return r
246
247 def GetBranch(self, name):
248 """Get the branch.$name.* configuration values as an object.
249 """
250 try:
251 b = self._branches[name]
252 except KeyError:
253 b = Branch(self, name)
254 self._branches[b.name] = b
255 return b
256
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700257 def GetSubSections(self, section):
258 """List all subsection names matching $section.*.*
259 """
260 return self._sections.get(section, set())
261
David Pursehousee5913ae2020-02-12 13:56:59 +0900262 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700263 """Does at least one key in section.subsection exist?
264 """
265 try:
266 return subsection in self._sections[section]
267 except KeyError:
268 return False
269
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700270 def UrlInsteadOf(self, url):
271 """Resolve any url.*.insteadof references.
272 """
273 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700274 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
275 if old_url is not None and url.startswith(old_url):
276 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700277 return url
278
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700279 @property
280 def _sections(self):
281 d = self._section_dict
282 if d is None:
283 d = {}
284 for name in self._cache.keys():
285 p = name.split('.')
286 if 2 == len(p):
287 section = p[0]
288 subsect = ''
289 else:
290 section = p[0]
291 subsect = '.'.join(p[1:-1])
292 if section not in d:
293 d[section] = set()
294 d[section].add(subsect)
295 self._section_dict = d
296 return d
297
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298 @property
299 def _cache(self):
300 if self._cache_dict is None:
301 self._cache_dict = self._Read()
302 return self._cache_dict
303
304 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100305 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700306 if d is None:
307 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100308 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700309 return d
310
Anthony King85b24ac2014-05-06 15:57:48 +0100311 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700312 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900313 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800314 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700315 return None
316 except OSError:
317 return None
318 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100319 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500320 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100321 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100322 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800323 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700324 return None
325
Anthony King85b24ac2014-05-06 15:57:48 +0100326 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700327 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500328 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100329 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100330 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100331 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800332 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700333
334 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700335 """
336 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700337
David Aguilar438c5472009-06-28 15:09:16 -0700338 This internal method populates the GitConfig cache.
339
340 """
David Aguilar438c5472009-06-28 15:09:16 -0700341 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700342 d = self._do('--null', '--list')
343 if d is None:
344 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800345 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700346 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900347 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700348 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900349 key = line
350 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700351
352 if key in c:
353 c[key].append(val)
354 else:
355 c[key] = [val]
356
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700357 return c
358
359 def _do(self, *args):
Ulrik Laurénd0ca0f62020-04-28 01:09:57 +0200360 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700361 command.extend(args)
362
363 p = GitCommand(None,
364 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900365 capture_stdout=True,
366 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700367 if p.Wait() == 0:
368 return p.stdout
369 else:
370 GitError('git config %s: %s' % (str(args), p.stderr))
371
372
Mike Frysingerf841ca42020-02-18 21:31:51 -0500373class RepoConfig(GitConfig):
374 """User settings for repo itself."""
375
376 _USER_CONFIG = '~/.repoconfig/config'
377
378
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700379class RefSpec(object):
380 """A Git refspec line, split into its components:
381
382 forced: True if the line starts with '+'
383 src: Left side of the line
384 dst: Right side of the line
385 """
386
387 @classmethod
388 def FromString(cls, rs):
389 lhs, rhs = rs.split(':', 2)
390 if lhs.startswith('+'):
391 lhs = lhs[1:]
392 forced = True
393 else:
394 forced = False
395 return cls(forced, lhs, rhs)
396
397 def __init__(self, forced, lhs, rhs):
398 self.forced = forced
399 self.src = lhs
400 self.dst = rhs
401
402 def SourceMatches(self, rev):
403 if self.src:
404 if rev == self.src:
405 return True
406 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
407 return True
408 return False
409
410 def DestMatches(self, ref):
411 if self.dst:
412 if ref == self.dst:
413 return True
414 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
415 return True
416 return False
417
418 def MapSource(self, rev):
419 if self.src.endswith('/*'):
420 return self.dst[:-1] + rev[len(self.src) - 1:]
421 return self.dst
422
423 def __str__(self):
424 s = ''
425 if self.forced:
426 s += '+'
427 if self.src:
428 s += self.src
429 if self.dst:
430 s += ':'
431 s += self.dst
432 return s
433
434
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700435URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700436
David Pursehouse819827a2020-02-12 15:20:19 +0900437
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700438def GetSchemeFromUrl(url):
439 m = URI_ALL.match(url)
440 if m:
441 return m.group(1)
442 return None
443
David Pursehouse819827a2020-02-12 15:20:19 +0900444
Dan Willemsen0745bb22015-08-17 13:41:45 -0700445@contextlib.contextmanager
446def GetUrlCookieFile(url, quiet):
447 if url.startswith('persistent-'):
448 try:
449 p = subprocess.Popen(
450 ['git-remote-persistent-https', '-print_config', url],
451 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
452 stderr=subprocess.PIPE)
453 try:
454 cookieprefix = 'http.cookiefile='
455 proxyprefix = 'http.proxy='
456 cookiefile = None
457 proxy = None
458 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500459 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700460 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900461 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700462 if line.startswith(proxyprefix):
463 proxy = line[len(proxyprefix):]
464 # Leave subprocess open, as cookie file may be transient.
465 if cookiefile or proxy:
466 yield cookiefile, proxy
467 return
468 finally:
469 p.stdin.close()
470 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500471 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700472 if ' -print_config' in err_msg:
473 pass # Persistent proxy doesn't support -print_config.
474 elif not quiet:
475 print(err_msg, file=sys.stderr)
476 except OSError as e:
477 if e.errno == errno.ENOENT:
478 pass # No persistent proxy.
479 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900480 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
481 if cookiefile:
482 cookiefile = os.path.expanduser(cookiefile)
483 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700484
David Pursehouse819827a2020-02-12 15:20:19 +0900485
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700486class Remote(object):
487 """Configuration options related to a remote.
488 """
David Pursehouse819827a2020-02-12 15:20:19 +0900489
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700490 def __init__(self, config, name):
491 self._config = config
492 self.name = name
493 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700494 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700495 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800496 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530497 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900498 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800499 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800500
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100501 def _InsteadOf(self):
502 globCfg = GitConfig.ForUser()
503 urlList = globCfg.GetSubSections('url')
504 longest = ""
505 longestUrl = ""
506
507 for url in urlList:
508 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900509 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100510
511 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900512 if (self.url.startswith(insteadOf)
513 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100514 longest = insteadOf
515 longestUrl = url
516
517 if len(longest) == 0:
518 return self.url
519
520 return self.url.replace(longest, longestUrl, 1)
521
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700522 def PreConnectFetch(self):
Mike Frysinger19e409c2021-05-05 19:44:35 -0400523 """Run any setup for this remote before we connect to it.
524
525 In practice, if the remote is using SSH, we'll attempt to create a new
526 SSH master session to it for reuse across projects.
527
528 Returns:
529 Whether the preconnect phase for this remote was successful.
530 """
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100531 connectionUrl = self._InsteadOf()
Mike Frysinger5291eaf2021-05-05 15:53:03 -0400532 return ssh.preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700533
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200534 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800535 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800536 if self.review is None:
537 return None
538
539 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700540 if u.startswith('persistent-'):
541 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100542 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800543 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700544 if u.endswith('/Gerrit'):
545 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800546 if u.endswith('/ssh_info'):
547 u = u[:len(u) - len('/ssh_info')]
548 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900549 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800550 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800551
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700552 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800553 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700554 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800555 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
556 self._review_url = self._SshReviewUrl(userEmail, host, port)
557 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100558 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800559 self._review_url = u # Assume it's right
560 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200561 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
562 self._review_url = http_url
563 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700564 else:
565 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800566 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200567 if not validate_certs:
568 context = ssl._create_unverified_context()
569 info = urllib.request.urlopen(info_url, context=context).read()
570 else:
571 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400572 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700573 # If `info` contains '<', we assume the server gave us some sort
574 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700575 #
Conley Owens745a39b2013-06-05 13:16:18 -0700576 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800577 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700578 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400579 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800580 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000581 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700582 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800583 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700584 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700585 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900586 except HTTPException as e:
587 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800588
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800589 REVIEW_CACHE[u] = self._review_url
590 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800591
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800592 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700593 username = self._config.GetString('review.%s.username' % self.review)
594 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800595 username = userEmail.split('@')[0]
596 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700597
598 def ToLocal(self, rev):
599 """Convert a remote revision string to something we have locally.
600 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200601 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700602 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700603
604 if not rev.startswith('refs/'):
605 rev = R_HEADS + rev
606
607 for spec in self.fetch:
608 if spec.SourceMatches(rev):
609 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600610
611 if not rev.startswith(R_HEADS):
612 return rev
613
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400614 raise GitError('%s: remote %s does not have %s' %
615 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700616
617 def WritesTo(self, ref):
618 """True if the remote stores to the tracking ref.
619 """
620 for spec in self.fetch:
621 if spec.DestMatches(ref):
622 return True
623 return False
624
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800625 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700626 """Set the fetch refspec to its default value.
627 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800628 if mirror:
629 dst = 'refs/heads/*'
630 else:
631 dst = 'refs/remotes/%s/*' % self.name
632 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633
634 def Save(self):
635 """Save this remote to the configuration.
636 """
637 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700638 if self.pushUrl is not None:
639 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
640 else:
641 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800643 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530644 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645
646 def _Set(self, key, value):
647 key = 'remote.%s.%s' % (self.name, key)
648 return self._config.SetString(key, value)
649
David Pursehouse8a68ff92012-09-24 12:15:13 +0900650 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900652 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654
655class Branch(object):
656 """Configuration options related to a single branch.
657 """
David Pursehouse819827a2020-02-12 15:20:19 +0900658
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659 def __init__(self, config, name):
660 self._config = config
661 self.name = name
662 self.merge = self._Get('merge')
663
664 r = self._Get('remote')
665 if r:
666 self.remote = self._config.GetRemote(r)
667 else:
668 self.remote = None
669
670 @property
671 def LocalMerge(self):
672 """Convert the merge spec to a local name.
673 """
674 if self.remote and self.merge:
675 return self.remote.ToLocal(self.merge)
676 return None
677
678 def Save(self):
679 """Save this branch back into the configuration.
680 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700681 if self._config.HasSection('branch', self.name):
682 if self.remote:
683 self._Set('remote', self.remote.name)
684 else:
685 self._Set('remote', None)
686 self._Set('merge', self.merge)
687
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500689 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700690 fd.write('[branch "%s"]\n' % self.name)
691 if self.remote:
692 fd.write('\tremote = %s\n' % self.remote.name)
693 if self.merge:
694 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700695
696 def _Set(self, key, value):
697 key = 'branch.%s.%s' % (self.name, key)
698 return self._config.SetString(key, value)
699
David Pursehouse8a68ff92012-09-24 12:15:13 +0900700 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900702 return self._config.GetString(key, all_keys=all_keys)