blob: fcd0446cb4bbf648c2149af0ddaa30aee31b4e3c [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
Mike Frysingeraf1e5de2020-02-17 14:58:37 -050021import signal
Ł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
Doug Anderson0048b692010-12-21 13:39:23 -080025try:
26 import threading as _threading
27except ImportError:
28 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070029import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040030import urllib.error
31import urllib.request
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070032
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080033from error import GitError, UploadError
Renaud Paquay010fed72016-11-11 14:25:29 -080034import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040035from repo_trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070036
37from git_command import GitCommand
38from git_command import ssh_sock
39from git_command import terminate_ssh_clients
Zac Livingston9ead97b2017-06-13 08:29:04 -060040from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041
David Pursehouse1d947b32012-10-25 12:23:11 +090042ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043
Shawn O. Pearce146fe902009-03-25 14:06:43 -070044REVIEW_CACHE = dict()
45
David Pursehouse819827a2020-02-12 15:20:19 +090046
Zac Livingston9ead97b2017-06-13 08:29:04 -060047def IsChange(rev):
48 return rev.startswith(R_CHANGES)
49
David Pursehouse819827a2020-02-12 15:20:19 +090050
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051def IsId(rev):
52 return ID_RE.match(rev)
53
David Pursehouse819827a2020-02-12 15:20:19 +090054
Zac Livingston9ead97b2017-06-13 08:29:04 -060055def IsTag(rev):
56 return rev.startswith(R_TAGS)
57
David Pursehouse819827a2020-02-12 15:20:19 +090058
Zac Livingston9ead97b2017-06-13 08:29:04 -060059def IsImmutable(rev):
60 return IsChange(rev) or IsId(rev) or IsTag(rev)
61
David Pursehouse819827a2020-02-12 15:20:19 +090062
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070063def _key(name):
64 parts = name.split('.')
65 if len(parts) < 2:
66 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090067 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070068 parts[-1] = parts[-1].lower()
69 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070070
David Pursehouse819827a2020-02-12 15:20:19 +090071
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070073 _ForUser = None
74
Mike Frysingerf841ca42020-02-18 21:31:51 -050075 _USER_CONFIG = '~/.gitconfig'
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):
Ulrik Laurénd0ca0f62020-04-28 01:09:57 +0200368 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700369 command.extend(args)
370
371 p = GitCommand(None,
372 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900373 capture_stdout=True,
374 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700375 if p.Wait() == 0:
376 return p.stdout
377 else:
378 GitError('git config %s: %s' % (str(args), p.stderr))
379
380
Mike Frysingerf841ca42020-02-18 21:31:51 -0500381class RepoConfig(GitConfig):
382 """User settings for repo itself."""
383
384 _USER_CONFIG = '~/.repoconfig/config'
385
386
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700387class RefSpec(object):
388 """A Git refspec line, split into its components:
389
390 forced: True if the line starts with '+'
391 src: Left side of the line
392 dst: Right side of the line
393 """
394
395 @classmethod
396 def FromString(cls, rs):
397 lhs, rhs = rs.split(':', 2)
398 if lhs.startswith('+'):
399 lhs = lhs[1:]
400 forced = True
401 else:
402 forced = False
403 return cls(forced, lhs, rhs)
404
405 def __init__(self, forced, lhs, rhs):
406 self.forced = forced
407 self.src = lhs
408 self.dst = rhs
409
410 def SourceMatches(self, rev):
411 if self.src:
412 if rev == self.src:
413 return True
414 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
415 return True
416 return False
417
418 def DestMatches(self, ref):
419 if self.dst:
420 if ref == self.dst:
421 return True
422 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
423 return True
424 return False
425
426 def MapSource(self, rev):
427 if self.src.endswith('/*'):
428 return self.dst[:-1] + rev[len(self.src) - 1:]
429 return self.dst
430
431 def __str__(self):
432 s = ''
433 if self.forced:
434 s += '+'
435 if self.src:
436 s += self.src
437 if self.dst:
438 s += ':'
439 s += self.dst
440 return s
441
442
Doug Anderson06d029c2010-10-27 17:06:01 -0700443_master_processes = []
444_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700445_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800446_master_keys_lock = None
447
David Pursehouse819827a2020-02-12 15:20:19 +0900448
Doug Anderson0048b692010-12-21 13:39:23 -0800449def init_ssh():
450 """Should be called once at the start of repo to init ssh master handling.
451
452 At the moment, all we do is to create our lock.
453 """
454 global _master_keys_lock
455 assert _master_keys_lock is None, "Should only call init_ssh once"
456 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700457
David Pursehouse819827a2020-02-12 15:20:19 +0900458
Josh Guilfoyle71985722009-08-16 09:44:40 -0700459def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700460 global _ssh_master
461
Mike Frysinger5ba80d42021-05-04 17:58:26 -0400462 # Bail before grabbing the lock if we already know that we aren't going to
463 # try creating new masters below.
464 if sys.platform in ('win32', 'cygwin'):
465 return False
466
Doug Anderson0048b692010-12-21 13:39:23 -0800467 # Acquire the lock. This is needed to prevent opening multiple masters for
468 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
469 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
470 # one that was passed to repo init.
471 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700472 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700473
Doug Anderson0048b692010-12-21 13:39:23 -0800474 # Check to see whether we already think that the master is running; if we
475 # think it's already running, return right away.
476 if port is not None:
477 key = '%s:%s' % (host, port)
478 else:
479 key = host
480
481 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700482 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700483
Mike Frysinger5ba80d42021-05-04 17:58:26 -0400484 if not _ssh_master or 'GIT_SSH' in os.environ:
485 # Failed earlier, so don't retry.
Doug Anderson0048b692010-12-21 13:39:23 -0800486 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700487
Doug Anderson0048b692010-12-21 13:39:23 -0800488 # We will make two calls to ssh; this is the common part of both calls.
489 command_base = ['ssh',
David Pursehouseabdf7502020-02-12 14:58:39 +0900490 '-o', 'ControlPath %s' % ssh_sock(),
491 host]
Doug Anderson0048b692010-12-21 13:39:23 -0800492 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900493 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800494
495 # Since the key wasn't in _master_keys, we think that master isn't running.
496 # ...but before actually starting a master, we'll double-check. This can
497 # be important because we can't tell that that 'git@myhost.com' is the same
498 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
David Pursehouse54a4e602020-02-12 14:31:05 +0900499 check_command = command_base + ['-O', 'check']
Doug Anderson0048b692010-12-21 13:39:23 -0800500 try:
501 Trace(': %s', ' '.join(check_command))
502 check_process = subprocess.Popen(check_command,
503 stdout=subprocess.PIPE,
504 stderr=subprocess.PIPE)
David Pursehouse54a4e602020-02-12 14:31:05 +0900505 check_process.communicate() # read output, but ignore it...
Doug Anderson0048b692010-12-21 13:39:23 -0800506 isnt_running = check_process.wait()
507
508 if not isnt_running:
509 # Our double-check found that the master _was_ infact running. Add to
510 # the list of keys.
511 _master_keys.add(key)
512 return True
513 except Exception:
514 # Ignore excpetions. We we will fall back to the normal command and print
515 # to the log there.
516 pass
517
David Pursehouse0ab95ba2020-02-12 15:01:59 +0900518 command = command_base[:1] + ['-M', '-N'] + command_base[1:]
Doug Anderson0048b692010-12-21 13:39:23 -0800519 try:
520 Trace(': %s', ' '.join(command))
521 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700522 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800523 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700524 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
David Pursehouseabdf7502020-02-12 14:58:39 +0900525 % (host, port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800526 return False
527
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200528 time.sleep(1)
529 ssh_died = (p.poll() is not None)
530 if ssh_died:
531 return False
532
Doug Anderson0048b692010-12-21 13:39:23 -0800533 _master_processes.append(p)
534 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800535 return True
536 finally:
537 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700538
David Pursehouse819827a2020-02-12 15:20:19 +0900539
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700540def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800541 global _master_keys_lock
542
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700543 terminate_ssh_clients()
544
Doug Anderson06d029c2010-10-27 17:06:01 -0700545 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700546 try:
Mike Frysingeraf1e5de2020-02-17 14:58:37 -0500547 os.kill(p.pid, signal.SIGTERM)
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700548 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700549 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700550 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700551 del _master_processes[:]
552 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700553
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700554 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700555 if d:
556 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700557 platform_utils.rmdir(os.path.dirname(d))
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700558 except OSError:
559 pass
560
Doug Anderson0048b692010-12-21 13:39:23 -0800561 # We're done with the lock, so we can delete it.
562 _master_keys_lock = None
563
David Pursehouse819827a2020-02-12 15:20:19 +0900564
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700565URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700566URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700567
David Pursehouse819827a2020-02-12 15:20:19 +0900568
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700569def GetSchemeFromUrl(url):
570 m = URI_ALL.match(url)
571 if m:
572 return m.group(1)
573 return None
574
David Pursehouse819827a2020-02-12 15:20:19 +0900575
Dan Willemsen0745bb22015-08-17 13:41:45 -0700576@contextlib.contextmanager
577def GetUrlCookieFile(url, quiet):
578 if url.startswith('persistent-'):
579 try:
580 p = subprocess.Popen(
581 ['git-remote-persistent-https', '-print_config', url],
582 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
583 stderr=subprocess.PIPE)
584 try:
585 cookieprefix = 'http.cookiefile='
586 proxyprefix = 'http.proxy='
587 cookiefile = None
588 proxy = None
589 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500590 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700591 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900592 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700593 if line.startswith(proxyprefix):
594 proxy = line[len(proxyprefix):]
595 # Leave subprocess open, as cookie file may be transient.
596 if cookiefile or proxy:
597 yield cookiefile, proxy
598 return
599 finally:
600 p.stdin.close()
601 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500602 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700603 if ' -print_config' in err_msg:
604 pass # Persistent proxy doesn't support -print_config.
605 elif not quiet:
606 print(err_msg, file=sys.stderr)
607 except OSError as e:
608 if e.errno == errno.ENOENT:
609 pass # No persistent proxy.
610 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900611 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
612 if cookiefile:
613 cookiefile = os.path.expanduser(cookiefile)
614 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700615
David Pursehouse819827a2020-02-12 15:20:19 +0900616
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700617def _preconnect(url):
618 m = URI_ALL.match(url)
619 if m:
620 scheme = m.group(1)
621 host = m.group(2)
622 if ':' in host:
623 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700624 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700625 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700626 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
627 return _open_ssh(host, port)
628 return False
629
630 m = URI_SCP.match(url)
631 if m:
632 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700633 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700634
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700635 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700636
David Pursehouse819827a2020-02-12 15:20:19 +0900637
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638class Remote(object):
639 """Configuration options related to a remote.
640 """
David Pursehouse819827a2020-02-12 15:20:19 +0900641
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642 def __init__(self, config, name):
643 self._config = config
644 self.name = name
645 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700646 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800648 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530649 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900650 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800651 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800652
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100653 def _InsteadOf(self):
654 globCfg = GitConfig.ForUser()
655 urlList = globCfg.GetSubSections('url')
656 longest = ""
657 longestUrl = ""
658
659 for url in urlList:
660 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900661 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100662
663 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900664 if (self.url.startswith(insteadOf)
665 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100666 longest = insteadOf
667 longestUrl = url
668
669 if len(longest) == 0:
670 return self.url
671
672 return self.url.replace(longest, longestUrl, 1)
673
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700674 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100675 connectionUrl = self._InsteadOf()
676 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700677
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200678 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800679 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800680 if self.review is None:
681 return None
682
683 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700684 if u.startswith('persistent-'):
685 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100686 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800687 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700688 if u.endswith('/Gerrit'):
689 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800690 if u.endswith('/ssh_info'):
691 u = u[:len(u) - len('/ssh_info')]
692 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900693 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800694 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800695
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700696 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800697 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700698 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800699 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
700 self._review_url = self._SshReviewUrl(userEmail, host, port)
701 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100702 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800703 self._review_url = u # Assume it's right
704 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200705 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
706 self._review_url = http_url
707 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700708 else:
709 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800710 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200711 if not validate_certs:
712 context = ssl._create_unverified_context()
713 info = urllib.request.urlopen(info_url, context=context).read()
714 else:
715 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400716 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700717 # If `info` contains '<', we assume the server gave us some sort
718 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700719 #
Conley Owens745a39b2013-06-05 13:16:18 -0700720 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800721 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700722 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400723 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800724 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000725 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700726 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800727 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700728 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700729 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900730 except HTTPException as e:
731 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800732
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800733 REVIEW_CACHE[u] = self._review_url
734 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800735
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800736 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700737 username = self._config.GetString('review.%s.username' % self.review)
738 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800739 username = userEmail.split('@')[0]
740 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700741
742 def ToLocal(self, rev):
743 """Convert a remote revision string to something we have locally.
744 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200745 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747
748 if not rev.startswith('refs/'):
749 rev = R_HEADS + rev
750
751 for spec in self.fetch:
752 if spec.SourceMatches(rev):
753 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600754
755 if not rev.startswith(R_HEADS):
756 return rev
757
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400758 raise GitError('%s: remote %s does not have %s' %
759 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760
761 def WritesTo(self, ref):
762 """True if the remote stores to the tracking ref.
763 """
764 for spec in self.fetch:
765 if spec.DestMatches(ref):
766 return True
767 return False
768
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800769 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700770 """Set the fetch refspec to its default value.
771 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800772 if mirror:
773 dst = 'refs/heads/*'
774 else:
775 dst = 'refs/remotes/%s/*' % self.name
776 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777
778 def Save(self):
779 """Save this remote to the configuration.
780 """
781 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700782 if self.pushUrl is not None:
783 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
784 else:
785 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800787 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530788 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789
790 def _Set(self, key, value):
791 key = 'remote.%s.%s' % (self.name, key)
792 return self._config.SetString(key, value)
793
David Pursehouse8a68ff92012-09-24 12:15:13 +0900794 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900796 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700797
798
799class Branch(object):
800 """Configuration options related to a single branch.
801 """
David Pursehouse819827a2020-02-12 15:20:19 +0900802
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700803 def __init__(self, config, name):
804 self._config = config
805 self.name = name
806 self.merge = self._Get('merge')
807
808 r = self._Get('remote')
809 if r:
810 self.remote = self._config.GetRemote(r)
811 else:
812 self.remote = None
813
814 @property
815 def LocalMerge(self):
816 """Convert the merge spec to a local name.
817 """
818 if self.remote and self.merge:
819 return self.remote.ToLocal(self.merge)
820 return None
821
822 def Save(self):
823 """Save this branch back into the configuration.
824 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700825 if self._config.HasSection('branch', self.name):
826 if self.remote:
827 self._Set('remote', self.remote.name)
828 else:
829 self._Set('remote', None)
830 self._Set('merge', self.merge)
831
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700832 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500833 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700834 fd.write('[branch "%s"]\n' % self.name)
835 if self.remote:
836 fd.write('\tremote = %s\n' % self.remote.name)
837 if self.merge:
838 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839
840 def _Set(self, key, value):
841 key = 'branch.%s.%s' % (self.name, key)
842 return self._config.SetString(key, value)
843
David Pursehouse8a68ff92012-09-24 12:15:13 +0900844 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700845 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900846 return self._config.GetString(key, all_keys=all_keys)