blob: 3eaf201c25b751bc5e9a6e77e486e75225e537d6 [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
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070030from git_command import GitCommand
Zac Livingston9ead97b2017-06-13 08:29:04 -060031from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
David Pursehouse1d947b32012-10-25 12:23:11 +090033ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Shawn O. Pearce146fe902009-03-25 14:06:43 -070035REVIEW_CACHE = dict()
36
David Pursehouse819827a2020-02-12 15:20:19 +090037
Zac Livingston9ead97b2017-06-13 08:29:04 -060038def IsChange(rev):
39 return rev.startswith(R_CHANGES)
40
David Pursehouse819827a2020-02-12 15:20:19 +090041
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042def IsId(rev):
43 return ID_RE.match(rev)
44
David Pursehouse819827a2020-02-12 15:20:19 +090045
Zac Livingston9ead97b2017-06-13 08:29:04 -060046def IsTag(rev):
47 return rev.startswith(R_TAGS)
48
David Pursehouse819827a2020-02-12 15:20:19 +090049
Zac Livingston9ead97b2017-06-13 08:29:04 -060050def IsImmutable(rev):
51 return IsChange(rev) or IsId(rev) or IsTag(rev)
52
David Pursehouse819827a2020-02-12 15:20:19 +090053
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070054def _key(name):
55 parts = name.split('.')
56 if len(parts) < 2:
57 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090058 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070059 parts[-1] = parts[-1].lower()
60 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070061
David Pursehouse819827a2020-02-12 15:20:19 +090062
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070064 _ForUser = None
65
Mike Frysingerf841ca42020-02-18 21:31:51 -050066 _USER_CONFIG = '~/.gitconfig'
67
Xin Li0cb6e922021-06-16 10:19:00 -070068 _ForSystem = None
69 _SYSTEM_CONFIG = '/etc/gitconfig'
70
71 @classmethod
72 def ForSystem(cls):
73 if cls._ForSystem is None:
74 cls._ForSystem = cls(configfile=cls._SYSTEM_CONFIG)
75 return cls._ForSystem
76
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077 @classmethod
78 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070079 if cls._ForUser is None:
Mike Frysingerf841ca42020-02-18 21:31:51 -050080 cls._ForUser = cls(configfile=os.path.expanduser(cls._USER_CONFIG))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070081 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082
83 @classmethod
84 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090085 return cls(configfile=os.path.join(gitdir, 'config'),
86 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070087
Anthony King85b24ac2014-05-06 15:57:48 +010088 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090089 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070090 self.defaults = defaults
91 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070092 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093 self._remotes = {}
94 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070095
Anthony King85b24ac2014-05-06 15:57:48 +010096 self._json = jsonFile
97 if self._json is None:
98 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +090099 os.path.dirname(self.file),
100 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101
David Pursehousee5913ae2020-02-12 13:56:59 +0900102 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700103 """Return true if this configuration file has the key.
104 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700105 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700106 return True
107 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900108 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700109 return False
110
Mike Frysinger77b43972020-02-19 17:55:22 -0500111 def GetInt(self, name):
112 """Returns an integer from the configuration file.
113
114 This follows the git config syntax.
115
116 Args:
117 name: The key to lookup.
118
119 Returns:
120 None if the value was not defined, or is not a boolean.
121 Otherwise, the number itself.
122 """
123 v = self.GetString(name)
124 if v is None:
125 return None
126 v = v.strip()
127
128 mult = 1
129 if v.endswith('k'):
130 v = v[:-1]
131 mult = 1024
132 elif v.endswith('m'):
133 v = v[:-1]
134 mult = 1024 * 1024
135 elif v.endswith('g'):
136 v = v[:-1]
137 mult = 1024 * 1024 * 1024
138
139 base = 10
140 if v.startswith('0x'):
141 base = 16
142
143 try:
144 return int(v, base=base) * mult
145 except ValueError:
146 return None
147
Ian Kasprzak835a34b2021-03-05 11:04:49 -0800148 def DumpConfigDict(self):
149 """Returns the current configuration dict.
150
151 Configuration data is information only (e.g. logging) and
152 should not be considered a stable data-source.
153
154 Returns:
155 dict of {<key>, <value>} for git configuration cache.
156 <value> are strings converted by GetString.
157 """
158 config_dict = {}
159 for key in self._cache:
160 config_dict[key] = self.GetString(key)
161 return config_dict
162
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700163 def GetBoolean(self, name):
164 """Returns a boolean from the configuration file.
165 None : The value was not defined, or is not a boolean.
166 True : The value was set to true or yes.
167 False: The value was set to false or no.
168 """
169 v = self.GetString(name)
170 if v is None:
171 return None
172 v = v.lower()
173 if v in ('true', 'yes'):
174 return True
175 if v in ('false', 'no'):
176 return False
177 return None
178
Mike Frysinger38867fb2021-02-09 23:14:41 -0500179 def SetBoolean(self, name, value):
180 """Set the truthy value for a key."""
181 if value is not None:
182 value = 'true' if value else 'false'
183 self.SetString(name, value)
184
David Pursehouse8a68ff92012-09-24 12:15:13 +0900185 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186 """Get the first value for a key, or None if it is not defined.
187
188 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900189 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700191 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700192 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193 except KeyError:
194 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900195 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700196 v = []
197
David Pursehouse8a68ff92012-09-24 12:15:13 +0900198 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 if v:
200 return v[0]
201 return None
202
203 r = []
204 r.extend(v)
205 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900206 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700207 return r
208
209 def SetString(self, name, value):
210 """Set the value(s) for a key.
211 Only this configuration file is modified.
212
213 The supplied value should be either a string,
214 or a list of strings (to store multiple values).
215 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700216 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217
218 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700219 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220 except KeyError:
221 old = []
222
223 if value is None:
224 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700225 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700226 self._do('--unset-all', name)
227
228 elif isinstance(value, list):
229 if len(value) == 0:
230 self.SetString(name, None)
231
232 elif len(value) == 1:
233 self.SetString(name, value[0])
234
235 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700236 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700237 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700238 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239 self._do('--add', name, value[i])
240
241 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700242 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 self._do('--replace-all', name, value)
244
245 def GetRemote(self, name):
246 """Get the remote.$name.* configuration values as an object.
247 """
248 try:
249 r = self._remotes[name]
250 except KeyError:
251 r = Remote(self, name)
252 self._remotes[r.name] = r
253 return r
254
255 def GetBranch(self, name):
256 """Get the branch.$name.* configuration values as an object.
257 """
258 try:
259 b = self._branches[name]
260 except KeyError:
261 b = Branch(self, name)
262 self._branches[b.name] = b
263 return b
264
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700265 def GetSubSections(self, section):
266 """List all subsection names matching $section.*.*
267 """
268 return self._sections.get(section, set())
269
David Pursehousee5913ae2020-02-12 13:56:59 +0900270 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700271 """Does at least one key in section.subsection exist?
272 """
273 try:
274 return subsection in self._sections[section]
275 except KeyError:
276 return False
277
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700278 def UrlInsteadOf(self, url):
279 """Resolve any url.*.insteadof references.
280 """
281 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700282 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
283 if old_url is not None and url.startswith(old_url):
284 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700285 return url
286
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700287 @property
288 def _sections(self):
289 d = self._section_dict
290 if d is None:
291 d = {}
292 for name in self._cache.keys():
293 p = name.split('.')
294 if 2 == len(p):
295 section = p[0]
296 subsect = ''
297 else:
298 section = p[0]
299 subsect = '.'.join(p[1:-1])
300 if section not in d:
301 d[section] = set()
302 d[section].add(subsect)
303 self._section_dict = d
304 return d
305
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700306 @property
307 def _cache(self):
308 if self._cache_dict is None:
309 self._cache_dict = self._Read()
310 return self._cache_dict
311
312 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100313 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700314 if d is None:
315 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100316 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700317 return d
318
Anthony King85b24ac2014-05-06 15:57:48 +0100319 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700320 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900321 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800322 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700323 return None
324 except OSError:
325 return None
326 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100327 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500328 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100329 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100330 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800331 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700332 return None
333
Anthony King85b24ac2014-05-06 15:57:48 +0100334 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700335 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500336 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100337 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100338 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100339 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800340 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700341
342 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700343 """
344 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700345
David Aguilar438c5472009-06-28 15:09:16 -0700346 This internal method populates the GitConfig cache.
347
348 """
David Aguilar438c5472009-06-28 15:09:16 -0700349 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700350 d = self._do('--null', '--list')
351 if d is None:
352 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800353 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700354 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900355 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700356 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900357 key = line
358 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700359
360 if key in c:
361 c[key].append(val)
362 else:
363 c[key] = [val]
364
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700365 return c
366
367 def _do(self, *args):
Xin Li0cb6e922021-06-16 10:19:00 -0700368 if self.file == self._SYSTEM_CONFIG:
369 command = ['config', '--system', '--includes']
370 else:
371 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372 command.extend(args)
373
374 p = GitCommand(None,
375 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900376 capture_stdout=True,
377 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700378 if p.Wait() == 0:
379 return p.stdout
380 else:
381 GitError('git config %s: %s' % (str(args), p.stderr))
382
383
Mike Frysingerf841ca42020-02-18 21:31:51 -0500384class RepoConfig(GitConfig):
385 """User settings for repo itself."""
386
387 _USER_CONFIG = '~/.repoconfig/config'
388
389
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700390class RefSpec(object):
391 """A Git refspec line, split into its components:
392
393 forced: True if the line starts with '+'
394 src: Left side of the line
395 dst: Right side of the line
396 """
397
398 @classmethod
399 def FromString(cls, rs):
400 lhs, rhs = rs.split(':', 2)
401 if lhs.startswith('+'):
402 lhs = lhs[1:]
403 forced = True
404 else:
405 forced = False
406 return cls(forced, lhs, rhs)
407
408 def __init__(self, forced, lhs, rhs):
409 self.forced = forced
410 self.src = lhs
411 self.dst = rhs
412
413 def SourceMatches(self, rev):
414 if self.src:
415 if rev == self.src:
416 return True
417 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
418 return True
419 return False
420
421 def DestMatches(self, ref):
422 if self.dst:
423 if ref == self.dst:
424 return True
425 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
426 return True
427 return False
428
429 def MapSource(self, rev):
430 if self.src.endswith('/*'):
431 return self.dst[:-1] + rev[len(self.src) - 1:]
432 return self.dst
433
434 def __str__(self):
435 s = ''
436 if self.forced:
437 s += '+'
438 if self.src:
439 s += self.src
440 if self.dst:
441 s += ':'
442 s += self.dst
443 return s
444
445
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700446URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700447
David Pursehouse819827a2020-02-12 15:20:19 +0900448
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700449def GetSchemeFromUrl(url):
450 m = URI_ALL.match(url)
451 if m:
452 return m.group(1)
453 return None
454
David Pursehouse819827a2020-02-12 15:20:19 +0900455
Dan Willemsen0745bb22015-08-17 13:41:45 -0700456@contextlib.contextmanager
457def GetUrlCookieFile(url, quiet):
458 if url.startswith('persistent-'):
459 try:
460 p = subprocess.Popen(
461 ['git-remote-persistent-https', '-print_config', url],
462 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
463 stderr=subprocess.PIPE)
464 try:
465 cookieprefix = 'http.cookiefile='
466 proxyprefix = 'http.proxy='
467 cookiefile = None
468 proxy = None
469 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500470 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700471 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900472 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700473 if line.startswith(proxyprefix):
474 proxy = line[len(proxyprefix):]
475 # Leave subprocess open, as cookie file may be transient.
476 if cookiefile or proxy:
477 yield cookiefile, proxy
478 return
479 finally:
480 p.stdin.close()
481 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500482 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700483 if ' -print_config' in err_msg:
484 pass # Persistent proxy doesn't support -print_config.
485 elif not quiet:
486 print(err_msg, file=sys.stderr)
487 except OSError as e:
488 if e.errno == errno.ENOENT:
489 pass # No persistent proxy.
490 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900491 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
492 if cookiefile:
493 cookiefile = os.path.expanduser(cookiefile)
494 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700495
David Pursehouse819827a2020-02-12 15:20:19 +0900496
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700497class Remote(object):
498 """Configuration options related to a remote.
499 """
David Pursehouse819827a2020-02-12 15:20:19 +0900500
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700501 def __init__(self, config, name):
502 self._config = config
503 self.name = name
504 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700505 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700506 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800507 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530508 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900509 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800510 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800511
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100512 def _InsteadOf(self):
513 globCfg = GitConfig.ForUser()
514 urlList = globCfg.GetSubSections('url')
515 longest = ""
516 longestUrl = ""
517
518 for url in urlList:
519 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900520 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100521
522 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900523 if (self.url.startswith(insteadOf)
524 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100525 longest = insteadOf
526 longestUrl = url
527
528 if len(longest) == 0:
529 return self.url
530
531 return self.url.replace(longest, longestUrl, 1)
532
Mike Frysinger339f2df2021-05-06 00:44:42 -0400533 def PreConnectFetch(self, ssh_proxy):
Mike Frysinger19e409c2021-05-05 19:44:35 -0400534 """Run any setup for this remote before we connect to it.
535
536 In practice, if the remote is using SSH, we'll attempt to create a new
537 SSH master session to it for reuse across projects.
538
Mike Frysinger339f2df2021-05-06 00:44:42 -0400539 Args:
540 ssh_proxy: The SSH settings for managing master sessions.
541
Mike Frysinger19e409c2021-05-05 19:44:35 -0400542 Returns:
543 Whether the preconnect phase for this remote was successful.
544 """
Mike Frysinger339f2df2021-05-06 00:44:42 -0400545 if not ssh_proxy:
546 return True
547
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100548 connectionUrl = self._InsteadOf()
Mike Frysinger339f2df2021-05-06 00:44:42 -0400549 return ssh_proxy.preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700550
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200551 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800552 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800553 if self.review is None:
554 return None
555
556 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700557 if u.startswith('persistent-'):
558 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100559 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800560 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700561 if u.endswith('/Gerrit'):
562 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800563 if u.endswith('/ssh_info'):
564 u = u[:len(u) - len('/ssh_info')]
565 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900566 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800567 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800568
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700569 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800570 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700571 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800572 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
573 self._review_url = self._SshReviewUrl(userEmail, host, port)
574 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100575 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800576 self._review_url = u # Assume it's right
577 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200578 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
579 self._review_url = http_url
580 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700581 else:
582 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800583 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200584 if not validate_certs:
585 context = ssl._create_unverified_context()
586 info = urllib.request.urlopen(info_url, context=context).read()
587 else:
588 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400589 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700590 # If `info` contains '<', we assume the server gave us some sort
591 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700592 #
Conley Owens745a39b2013-06-05 13:16:18 -0700593 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800594 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700595 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400596 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800597 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000598 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700599 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800600 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700601 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700602 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900603 except HTTPException as e:
604 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800605
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800606 REVIEW_CACHE[u] = self._review_url
607 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800608
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800609 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700610 username = self._config.GetString('review.%s.username' % self.review)
611 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800612 username = userEmail.split('@')[0]
613 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700614
615 def ToLocal(self, rev):
616 """Convert a remote revision string to something we have locally.
617 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200618 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700620
621 if not rev.startswith('refs/'):
622 rev = R_HEADS + rev
623
624 for spec in self.fetch:
625 if spec.SourceMatches(rev):
626 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600627
628 if not rev.startswith(R_HEADS):
629 return rev
630
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400631 raise GitError('%s: remote %s does not have %s' %
632 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633
634 def WritesTo(self, ref):
635 """True if the remote stores to the tracking ref.
636 """
637 for spec in self.fetch:
638 if spec.DestMatches(ref):
639 return True
640 return False
641
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800642 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700643 """Set the fetch refspec to its default value.
644 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800645 if mirror:
646 dst = 'refs/heads/*'
647 else:
648 dst = 'refs/remotes/%s/*' % self.name
649 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700650
651 def Save(self):
652 """Save this remote to the configuration.
653 """
654 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700655 if self.pushUrl is not None:
656 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
657 else:
658 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800660 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530661 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662
663 def _Set(self, key, value):
664 key = 'remote.%s.%s' % (self.name, key)
665 return self._config.SetString(key, value)
666
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700668 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900669 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700670
671
672class Branch(object):
673 """Configuration options related to a single branch.
674 """
David Pursehouse819827a2020-02-12 15:20:19 +0900675
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700676 def __init__(self, config, name):
677 self._config = config
678 self.name = name
679 self.merge = self._Get('merge')
680
681 r = self._Get('remote')
682 if r:
683 self.remote = self._config.GetRemote(r)
684 else:
685 self.remote = None
686
687 @property
688 def LocalMerge(self):
689 """Convert the merge spec to a local name.
690 """
691 if self.remote and self.merge:
692 return self.remote.ToLocal(self.merge)
693 return None
694
695 def Save(self):
696 """Save this branch back into the configuration.
697 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700698 if self._config.HasSection('branch', self.name):
699 if self.remote:
700 self._Set('remote', self.remote.name)
701 else:
702 self._Set('remote', None)
703 self._Set('merge', self.merge)
704
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700705 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500706 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700707 fd.write('[branch "%s"]\n' % self.name)
708 if self.remote:
709 fd.write('\tremote = %s\n' % self.remote.name)
710 if self.merge:
711 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712
713 def _Set(self, key, value):
714 key = 'branch.%s.%s' % (self.name, key)
715 return self._config.SetString(key, value)
716
David Pursehouse8a68ff92012-09-24 12:15:13 +0900717 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900719 return self._config.GetString(key, all_keys=all_keys)