blob: 2fa43a1e06d47cfe53fd0de9c47d882ef36641d1 [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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700148 def GetBoolean(self, name):
149 """Returns a boolean from the configuration file.
150 None : The value was not defined, or is not a boolean.
151 True : The value was set to true or yes.
152 False: The value was set to false or no.
153 """
154 v = self.GetString(name)
155 if v is None:
156 return None
157 v = v.lower()
158 if v in ('true', 'yes'):
159 return True
160 if v in ('false', 'no'):
161 return False
162 return None
163
David Pursehouse8a68ff92012-09-24 12:15:13 +0900164 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 """Get the first value for a key, or None if it is not defined.
166
167 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900168 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700171 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 except KeyError:
173 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900174 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175 v = []
176
David Pursehouse8a68ff92012-09-24 12:15:13 +0900177 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 if v:
179 return v[0]
180 return None
181
182 r = []
183 r.extend(v)
184 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900185 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186 return r
187
188 def SetString(self, name, value):
189 """Set the value(s) for a key.
190 Only this configuration file is modified.
191
192 The supplied value should be either a string,
193 or a list of strings (to store multiple values).
194 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700195 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700196
197 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700198 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 except KeyError:
200 old = []
201
202 if value is None:
203 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700204 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 self._do('--unset-all', name)
206
207 elif isinstance(value, list):
208 if len(value) == 0:
209 self.SetString(name, None)
210
211 elif len(value) == 1:
212 self.SetString(name, value[0])
213
214 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700215 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700216 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700217 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 self._do('--add', name, value[i])
219
220 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700221 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 self._do('--replace-all', name, value)
223
224 def GetRemote(self, name):
225 """Get the remote.$name.* configuration values as an object.
226 """
227 try:
228 r = self._remotes[name]
229 except KeyError:
230 r = Remote(self, name)
231 self._remotes[r.name] = r
232 return r
233
234 def GetBranch(self, name):
235 """Get the branch.$name.* configuration values as an object.
236 """
237 try:
238 b = self._branches[name]
239 except KeyError:
240 b = Branch(self, name)
241 self._branches[b.name] = b
242 return b
243
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700244 def GetSubSections(self, section):
245 """List all subsection names matching $section.*.*
246 """
247 return self._sections.get(section, set())
248
David Pursehousee5913ae2020-02-12 13:56:59 +0900249 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700250 """Does at least one key in section.subsection exist?
251 """
252 try:
253 return subsection in self._sections[section]
254 except KeyError:
255 return False
256
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700257 def UrlInsteadOf(self, url):
258 """Resolve any url.*.insteadof references.
259 """
260 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700261 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
262 if old_url is not None and url.startswith(old_url):
263 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700264 return url
265
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700266 @property
267 def _sections(self):
268 d = self._section_dict
269 if d is None:
270 d = {}
271 for name in self._cache.keys():
272 p = name.split('.')
273 if 2 == len(p):
274 section = p[0]
275 subsect = ''
276 else:
277 section = p[0]
278 subsect = '.'.join(p[1:-1])
279 if section not in d:
280 d[section] = set()
281 d[section].add(subsect)
282 self._section_dict = d
283 return d
284
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700285 @property
286 def _cache(self):
287 if self._cache_dict is None:
288 self._cache_dict = self._Read()
289 return self._cache_dict
290
291 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100292 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700293 if d is None:
294 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100295 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700296 return d
297
Anthony King85b24ac2014-05-06 15:57:48 +0100298 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700299 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900300 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800301 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700302 return None
303 except OSError:
304 return None
305 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100306 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500307 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100308 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100309 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800310 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700311 return None
312
Anthony King85b24ac2014-05-06 15:57:48 +0100313 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700314 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500315 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100316 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100317 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100318 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800319 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700320
321 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700322 """
323 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700324
David Aguilar438c5472009-06-28 15:09:16 -0700325 This internal method populates the GitConfig cache.
326
327 """
David Aguilar438c5472009-06-28 15:09:16 -0700328 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700329 d = self._do('--null', '--list')
330 if d is None:
331 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800332 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700333 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900334 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700335 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900336 key = line
337 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700338
339 if key in c:
340 c[key].append(val)
341 else:
342 c[key] = [val]
343
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700344 return c
345
346 def _do(self, *args):
Ulrik Laurénd0ca0f62020-04-28 01:09:57 +0200347 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348 command.extend(args)
349
350 p = GitCommand(None,
351 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900352 capture_stdout=True,
353 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354 if p.Wait() == 0:
355 return p.stdout
356 else:
357 GitError('git config %s: %s' % (str(args), p.stderr))
358
359
Mike Frysingerf841ca42020-02-18 21:31:51 -0500360class RepoConfig(GitConfig):
361 """User settings for repo itself."""
362
363 _USER_CONFIG = '~/.repoconfig/config'
364
365
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700366class RefSpec(object):
367 """A Git refspec line, split into its components:
368
369 forced: True if the line starts with '+'
370 src: Left side of the line
371 dst: Right side of the line
372 """
373
374 @classmethod
375 def FromString(cls, rs):
376 lhs, rhs = rs.split(':', 2)
377 if lhs.startswith('+'):
378 lhs = lhs[1:]
379 forced = True
380 else:
381 forced = False
382 return cls(forced, lhs, rhs)
383
384 def __init__(self, forced, lhs, rhs):
385 self.forced = forced
386 self.src = lhs
387 self.dst = rhs
388
389 def SourceMatches(self, rev):
390 if self.src:
391 if rev == self.src:
392 return True
393 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
394 return True
395 return False
396
397 def DestMatches(self, ref):
398 if self.dst:
399 if ref == self.dst:
400 return True
401 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
402 return True
403 return False
404
405 def MapSource(self, rev):
406 if self.src.endswith('/*'):
407 return self.dst[:-1] + rev[len(self.src) - 1:]
408 return self.dst
409
410 def __str__(self):
411 s = ''
412 if self.forced:
413 s += '+'
414 if self.src:
415 s += self.src
416 if self.dst:
417 s += ':'
418 s += self.dst
419 return s
420
421
Doug Anderson06d029c2010-10-27 17:06:01 -0700422_master_processes = []
423_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700424_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800425_master_keys_lock = None
426
David Pursehouse819827a2020-02-12 15:20:19 +0900427
Doug Anderson0048b692010-12-21 13:39:23 -0800428def init_ssh():
429 """Should be called once at the start of repo to init ssh master handling.
430
431 At the moment, all we do is to create our lock.
432 """
433 global _master_keys_lock
434 assert _master_keys_lock is None, "Should only call init_ssh once"
435 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700436
David Pursehouse819827a2020-02-12 15:20:19 +0900437
Josh Guilfoyle71985722009-08-16 09:44:40 -0700438def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700439 global _ssh_master
440
Doug Anderson0048b692010-12-21 13:39:23 -0800441 # Acquire the lock. This is needed to prevent opening multiple masters for
442 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
443 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
444 # one that was passed to repo init.
445 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700446 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700447
Doug Anderson0048b692010-12-21 13:39:23 -0800448 # Check to see whether we already think that the master is running; if we
449 # think it's already running, return right away.
450 if port is not None:
451 key = '%s:%s' % (host, port)
452 else:
453 key = host
454
455 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700456 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700457
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900458 if (not _ssh_master
459 or 'GIT_SSH' in os.environ
460 or sys.platform in ('win32', 'cygwin')):
Doug Anderson0048b692010-12-21 13:39:23 -0800461 # failed earlier, or cygwin ssh can't do this
462 #
463 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700464
Doug Anderson0048b692010-12-21 13:39:23 -0800465 # We will make two calls to ssh; this is the common part of both calls.
466 command_base = ['ssh',
David Pursehouseabdf7502020-02-12 14:58:39 +0900467 '-o', 'ControlPath %s' % ssh_sock(),
468 host]
Doug Anderson0048b692010-12-21 13:39:23 -0800469 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900470 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800471
472 # Since the key wasn't in _master_keys, we think that master isn't running.
473 # ...but before actually starting a master, we'll double-check. This can
474 # be important because we can't tell that that 'git@myhost.com' is the same
475 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
David Pursehouse54a4e602020-02-12 14:31:05 +0900476 check_command = command_base + ['-O', 'check']
Doug Anderson0048b692010-12-21 13:39:23 -0800477 try:
478 Trace(': %s', ' '.join(check_command))
479 check_process = subprocess.Popen(check_command,
480 stdout=subprocess.PIPE,
481 stderr=subprocess.PIPE)
David Pursehouse54a4e602020-02-12 14:31:05 +0900482 check_process.communicate() # read output, but ignore it...
Doug Anderson0048b692010-12-21 13:39:23 -0800483 isnt_running = check_process.wait()
484
485 if not isnt_running:
486 # Our double-check found that the master _was_ infact running. Add to
487 # the list of keys.
488 _master_keys.add(key)
489 return True
490 except Exception:
491 # Ignore excpetions. We we will fall back to the normal command and print
492 # to the log there.
493 pass
494
David Pursehouse0ab95ba2020-02-12 15:01:59 +0900495 command = command_base[:1] + ['-M', '-N'] + command_base[1:]
Doug Anderson0048b692010-12-21 13:39:23 -0800496 try:
497 Trace(': %s', ' '.join(command))
498 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700499 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800500 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700501 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
David Pursehouseabdf7502020-02-12 14:58:39 +0900502 % (host, port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800503 return False
504
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200505 time.sleep(1)
506 ssh_died = (p.poll() is not None)
507 if ssh_died:
508 return False
509
Doug Anderson0048b692010-12-21 13:39:23 -0800510 _master_processes.append(p)
511 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800512 return True
513 finally:
514 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700515
David Pursehouse819827a2020-02-12 15:20:19 +0900516
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700517def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800518 global _master_keys_lock
519
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700520 terminate_ssh_clients()
521
Doug Anderson06d029c2010-10-27 17:06:01 -0700522 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700523 try:
Mike Frysingeraf1e5de2020-02-17 14:58:37 -0500524 os.kill(p.pid, signal.SIGTERM)
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700525 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700526 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700527 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700528 del _master_processes[:]
529 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700530
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700531 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700532 if d:
533 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700534 platform_utils.rmdir(os.path.dirname(d))
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700535 except OSError:
536 pass
537
Doug Anderson0048b692010-12-21 13:39:23 -0800538 # We're done with the lock, so we can delete it.
539 _master_keys_lock = None
540
David Pursehouse819827a2020-02-12 15:20:19 +0900541
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700542URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700543URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700544
David Pursehouse819827a2020-02-12 15:20:19 +0900545
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700546def GetSchemeFromUrl(url):
547 m = URI_ALL.match(url)
548 if m:
549 return m.group(1)
550 return None
551
David Pursehouse819827a2020-02-12 15:20:19 +0900552
Dan Willemsen0745bb22015-08-17 13:41:45 -0700553@contextlib.contextmanager
554def GetUrlCookieFile(url, quiet):
555 if url.startswith('persistent-'):
556 try:
557 p = subprocess.Popen(
558 ['git-remote-persistent-https', '-print_config', url],
559 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
560 stderr=subprocess.PIPE)
561 try:
562 cookieprefix = 'http.cookiefile='
563 proxyprefix = 'http.proxy='
564 cookiefile = None
565 proxy = None
566 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500567 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700568 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900569 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700570 if line.startswith(proxyprefix):
571 proxy = line[len(proxyprefix):]
572 # Leave subprocess open, as cookie file may be transient.
573 if cookiefile or proxy:
574 yield cookiefile, proxy
575 return
576 finally:
577 p.stdin.close()
578 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500579 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700580 if ' -print_config' in err_msg:
581 pass # Persistent proxy doesn't support -print_config.
582 elif not quiet:
583 print(err_msg, file=sys.stderr)
584 except OSError as e:
585 if e.errno == errno.ENOENT:
586 pass # No persistent proxy.
587 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900588 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
589 if cookiefile:
590 cookiefile = os.path.expanduser(cookiefile)
591 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700592
David Pursehouse819827a2020-02-12 15:20:19 +0900593
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700594def _preconnect(url):
595 m = URI_ALL.match(url)
596 if m:
597 scheme = m.group(1)
598 host = m.group(2)
599 if ':' in host:
600 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700601 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700602 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700603 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
604 return _open_ssh(host, port)
605 return False
606
607 m = URI_SCP.match(url)
608 if m:
609 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700610 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700611
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700612 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700613
David Pursehouse819827a2020-02-12 15:20:19 +0900614
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700615class Remote(object):
616 """Configuration options related to a remote.
617 """
David Pursehouse819827a2020-02-12 15:20:19 +0900618
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619 def __init__(self, config, name):
620 self._config = config
621 self.name = name
622 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700623 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700624 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800625 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530626 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900627 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800628 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800629
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100630 def _InsteadOf(self):
631 globCfg = GitConfig.ForUser()
632 urlList = globCfg.GetSubSections('url')
633 longest = ""
634 longestUrl = ""
635
636 for url in urlList:
637 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900638 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100639
640 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900641 if (self.url.startswith(insteadOf)
642 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100643 longest = insteadOf
644 longestUrl = url
645
646 if len(longest) == 0:
647 return self.url
648
649 return self.url.replace(longest, longestUrl, 1)
650
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700651 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100652 connectionUrl = self._InsteadOf()
653 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700654
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200655 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800656 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800657 if self.review is None:
658 return None
659
660 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700661 if u.startswith('persistent-'):
662 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100663 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800664 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700665 if u.endswith('/Gerrit'):
666 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800667 if u.endswith('/ssh_info'):
668 u = u[:len(u) - len('/ssh_info')]
669 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900670 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800671 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800672
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700673 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800674 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700675 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800676 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
677 self._review_url = self._SshReviewUrl(userEmail, host, port)
678 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100679 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800680 self._review_url = u # Assume it's right
681 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200682 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
683 self._review_url = http_url
684 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700685 else:
686 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800687 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200688 if not validate_certs:
689 context = ssl._create_unverified_context()
690 info = urllib.request.urlopen(info_url, context=context).read()
691 else:
692 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400693 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700694 # If `info` contains '<', we assume the server gave us some sort
695 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700696 #
Conley Owens745a39b2013-06-05 13:16:18 -0700697 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800698 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700699 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400700 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800701 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000702 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700703 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800704 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700705 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700706 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900707 except HTTPException as e:
708 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800709
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800710 REVIEW_CACHE[u] = self._review_url
711 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800712
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800713 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700714 username = self._config.GetString('review.%s.username' % self.review)
715 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800716 username = userEmail.split('@')[0]
717 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700718
719 def ToLocal(self, rev):
720 """Convert a remote revision string to something we have locally.
721 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200722 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724
725 if not rev.startswith('refs/'):
726 rev = R_HEADS + rev
727
728 for spec in self.fetch:
729 if spec.SourceMatches(rev):
730 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600731
732 if not rev.startswith(R_HEADS):
733 return rev
734
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400735 raise GitError('%s: remote %s does not have %s' %
736 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
738 def WritesTo(self, ref):
739 """True if the remote stores to the tracking ref.
740 """
741 for spec in self.fetch:
742 if spec.DestMatches(ref):
743 return True
744 return False
745
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800746 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700747 """Set the fetch refspec to its default value.
748 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800749 if mirror:
750 dst = 'refs/heads/*'
751 else:
752 dst = 'refs/remotes/%s/*' % self.name
753 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754
755 def Save(self):
756 """Save this remote to the configuration.
757 """
758 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700759 if self.pushUrl is not None:
760 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
761 else:
762 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800764 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530765 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766
767 def _Set(self, key, value):
768 key = 'remote.%s.%s' % (self.name, key)
769 return self._config.SetString(key, value)
770
David Pursehouse8a68ff92012-09-24 12:15:13 +0900771 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900773 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774
775
776class Branch(object):
777 """Configuration options related to a single branch.
778 """
David Pursehouse819827a2020-02-12 15:20:19 +0900779
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 def __init__(self, config, name):
781 self._config = config
782 self.name = name
783 self.merge = self._Get('merge')
784
785 r = self._Get('remote')
786 if r:
787 self.remote = self._config.GetRemote(r)
788 else:
789 self.remote = None
790
791 @property
792 def LocalMerge(self):
793 """Convert the merge spec to a local name.
794 """
795 if self.remote and self.merge:
796 return self.remote.ToLocal(self.merge)
797 return None
798
799 def Save(self):
800 """Save this branch back into the configuration.
801 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700802 if self._config.HasSection('branch', self.name):
803 if self.remote:
804 self._Set('remote', self.remote.name)
805 else:
806 self._Set('remote', None)
807 self._Set('merge', self.merge)
808
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700809 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500810 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700811 fd.write('[branch "%s"]\n' % self.name)
812 if self.remote:
813 fd.write('\tremote = %s\n' % self.remote.name)
814 if self.merge:
815 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700816
817 def _Set(self, key, value):
818 key = 'branch.%s.%s' % (self.name, key)
819 return self._config.SetString(key, value)
820
David Pursehouse8a68ff92012-09-24 12:15:13 +0900821 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900823 return self._config.GetString(key, all_keys=all_keys)