blob: fd8e92689469b7108947e3d8465964133dff2547 [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
Anthony King85b24ac2014-05-06 15:57:48 +010017import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
19import re
Mike Frysingeraf1e5de2020-02-17 14:58:37 -050020import signal
Ł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
Doug Anderson0048b692010-12-21 13:39:23 -080024try:
25 import threading as _threading
26except ImportError:
27 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070028import time
David Pursehouse59bbb582013-05-17 10:49:33 +090029
30from pyversion import is_python3
31if is_python3():
Sarah Owens1f7627f2012-10-31 09:21:55 -070032 import urllib.request
33 import urllib.error
34else:
David Pursehouse59bbb582013-05-17 10:49:33 +090035 import urllib2
Sarah Owens1f7627f2012-10-31 09:21:55 -070036 import imp
37 urllib = imp.new_module('urllib')
38 urllib.request = urllib2
39 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070040
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080041from error import GitError, UploadError
Renaud Paquay010fed72016-11-11 14:25:29 -080042import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040043from repo_trace import Trace
David Pursehouseecf8f2b2013-05-24 12:12:23 +090044if is_python3():
45 from http.client import HTTPException
46else:
47 from httplib import HTTPException
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070048
49from git_command import GitCommand
50from git_command import ssh_sock
51from git_command import terminate_ssh_clients
Zac Livingston9ead97b2017-06-13 08:29:04 -060052from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070053
David Pursehouse1d947b32012-10-25 12:23:11 +090054ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055
Shawn O. Pearce146fe902009-03-25 14:06:43 -070056REVIEW_CACHE = dict()
57
David Pursehouse819827a2020-02-12 15:20:19 +090058
Zac Livingston9ead97b2017-06-13 08:29:04 -060059def IsChange(rev):
60 return rev.startswith(R_CHANGES)
61
David Pursehouse819827a2020-02-12 15:20:19 +090062
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063def IsId(rev):
64 return ID_RE.match(rev)
65
David Pursehouse819827a2020-02-12 15:20:19 +090066
Zac Livingston9ead97b2017-06-13 08:29:04 -060067def IsTag(rev):
68 return rev.startswith(R_TAGS)
69
David Pursehouse819827a2020-02-12 15:20:19 +090070
Zac Livingston9ead97b2017-06-13 08:29:04 -060071def IsImmutable(rev):
72 return IsChange(rev) or IsId(rev) or IsTag(rev)
73
David Pursehouse819827a2020-02-12 15:20:19 +090074
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070075def _key(name):
76 parts = name.split('.')
77 if len(parts) < 2:
78 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090079 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070080 parts[-1] = parts[-1].lower()
81 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082
David Pursehouse819827a2020-02-12 15:20:19 +090083
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070084class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070085 _ForUser = None
86
Mike Frysingerf841ca42020-02-18 21:31:51 -050087 _USER_CONFIG = '~/.gitconfig'
88
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070089 @classmethod
90 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070091 if cls._ForUser is None:
Mike Frysingerf841ca42020-02-18 21:31:51 -050092 cls._ForUser = cls(configfile=os.path.expanduser(cls._USER_CONFIG))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070093 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070094
95 @classmethod
96 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090097 return cls(configfile=os.path.join(gitdir, 'config'),
98 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070099
Anthony King85b24ac2014-05-06 15:57:48 +0100100 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900101 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700102 self.defaults = defaults
103 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700104 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700105 self._remotes = {}
106 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700107
Anthony King85b24ac2014-05-06 15:57:48 +0100108 self._json = jsonFile
109 if self._json is None:
110 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900111 os.path.dirname(self.file),
112 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700113
David Pursehousee5913ae2020-02-12 13:56:59 +0900114 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115 """Return true if this configuration file has the key.
116 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700117 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118 return True
119 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900120 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 return False
122
Mike Frysinger77b43972020-02-19 17:55:22 -0500123 def GetInt(self, name):
124 """Returns an integer from the configuration file.
125
126 This follows the git config syntax.
127
128 Args:
129 name: The key to lookup.
130
131 Returns:
132 None if the value was not defined, or is not a boolean.
133 Otherwise, the number itself.
134 """
135 v = self.GetString(name)
136 if v is None:
137 return None
138 v = v.strip()
139
140 mult = 1
141 if v.endswith('k'):
142 v = v[:-1]
143 mult = 1024
144 elif v.endswith('m'):
145 v = v[:-1]
146 mult = 1024 * 1024
147 elif v.endswith('g'):
148 v = v[:-1]
149 mult = 1024 * 1024 * 1024
150
151 base = 10
152 if v.startswith('0x'):
153 base = 16
154
155 try:
156 return int(v, base=base) * mult
157 except ValueError:
158 return None
159
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160 def GetBoolean(self, name):
161 """Returns a boolean from the configuration file.
162 None : The value was not defined, or is not a boolean.
163 True : The value was set to true or yes.
164 False: The value was set to false or no.
165 """
166 v = self.GetString(name)
167 if v is None:
168 return None
169 v = v.lower()
170 if v in ('true', 'yes'):
171 return True
172 if v in ('false', 'no'):
173 return False
174 return None
175
David Pursehouse8a68ff92012-09-24 12:15:13 +0900176 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 """Get the first value for a key, or None if it is not defined.
178
179 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900180 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700183 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184 except KeyError:
185 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900186 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700187 v = []
188
David Pursehouse8a68ff92012-09-24 12:15:13 +0900189 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190 if v:
191 return v[0]
192 return None
193
194 r = []
195 r.extend(v)
196 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900197 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198 return r
199
200 def SetString(self, name, value):
201 """Set the value(s) for a key.
202 Only this configuration file is modified.
203
204 The supplied value should be either a string,
205 or a list of strings (to store multiple values).
206 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700207 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700208
209 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700210 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 except KeyError:
212 old = []
213
214 if value is None:
215 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700216 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700217 self._do('--unset-all', name)
218
219 elif isinstance(value, list):
220 if len(value) == 0:
221 self.SetString(name, None)
222
223 elif len(value) == 1:
224 self.SetString(name, value[0])
225
226 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700227 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700229 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700230 self._do('--add', name, value[i])
231
232 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700233 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700234 self._do('--replace-all', name, value)
235
236 def GetRemote(self, name):
237 """Get the remote.$name.* configuration values as an object.
238 """
239 try:
240 r = self._remotes[name]
241 except KeyError:
242 r = Remote(self, name)
243 self._remotes[r.name] = r
244 return r
245
246 def GetBranch(self, name):
247 """Get the branch.$name.* configuration values as an object.
248 """
249 try:
250 b = self._branches[name]
251 except KeyError:
252 b = Branch(self, name)
253 self._branches[b.name] = b
254 return b
255
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700256 def GetSubSections(self, section):
257 """List all subsection names matching $section.*.*
258 """
259 return self._sections.get(section, set())
260
David Pursehousee5913ae2020-02-12 13:56:59 +0900261 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700262 """Does at least one key in section.subsection exist?
263 """
264 try:
265 return subsection in self._sections[section]
266 except KeyError:
267 return False
268
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700269 def UrlInsteadOf(self, url):
270 """Resolve any url.*.insteadof references.
271 """
272 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700273 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
274 if old_url is not None and url.startswith(old_url):
275 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700276 return url
277
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700278 @property
279 def _sections(self):
280 d = self._section_dict
281 if d is None:
282 d = {}
283 for name in self._cache.keys():
284 p = name.split('.')
285 if 2 == len(p):
286 section = p[0]
287 subsect = ''
288 else:
289 section = p[0]
290 subsect = '.'.join(p[1:-1])
291 if section not in d:
292 d[section] = set()
293 d[section].add(subsect)
294 self._section_dict = d
295 return d
296
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700297 @property
298 def _cache(self):
299 if self._cache_dict is None:
300 self._cache_dict = self._Read()
301 return self._cache_dict
302
303 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100304 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700305 if d is None:
306 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100307 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700308 return d
309
Anthony King85b24ac2014-05-06 15:57:48 +0100310 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700311 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900312 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800313 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700314 return None
315 except OSError:
316 return None
317 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100318 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500319 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100320 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100321 except (IOError, ValueError):
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
Anthony King85b24ac2014-05-06 15:57:48 +0100325 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700326 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500327 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100328 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100329 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100330 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800331 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700332
333 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700334 """
335 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700336
David Aguilar438c5472009-06-28 15:09:16 -0700337 This internal method populates the GitConfig cache.
338
339 """
David Aguilar438c5472009-06-28 15:09:16 -0700340 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700341 d = self._do('--null', '--list')
342 if d is None:
343 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800344 if not is_python3():
345 d = d.decode('utf-8')
346 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700347 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900348 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700349 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900350 key = line
351 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700352
353 if key in c:
354 c[key].append(val)
355 else:
356 c[key] = [val]
357
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700358 return c
359
360 def _do(self, *args):
Ulrik Laurénd0ca0f62020-04-28 01:09:57 +0200361 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700362 command.extend(args)
363
364 p = GitCommand(None,
365 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900366 capture_stdout=True,
367 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700368 if p.Wait() == 0:
369 return p.stdout
370 else:
371 GitError('git config %s: %s' % (str(args), p.stderr))
372
373
Mike Frysingerf841ca42020-02-18 21:31:51 -0500374class RepoConfig(GitConfig):
375 """User settings for repo itself."""
376
377 _USER_CONFIG = '~/.repoconfig/config'
378
379
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700380class RefSpec(object):
381 """A Git refspec line, split into its components:
382
383 forced: True if the line starts with '+'
384 src: Left side of the line
385 dst: Right side of the line
386 """
387
388 @classmethod
389 def FromString(cls, rs):
390 lhs, rhs = rs.split(':', 2)
391 if lhs.startswith('+'):
392 lhs = lhs[1:]
393 forced = True
394 else:
395 forced = False
396 return cls(forced, lhs, rhs)
397
398 def __init__(self, forced, lhs, rhs):
399 self.forced = forced
400 self.src = lhs
401 self.dst = rhs
402
403 def SourceMatches(self, rev):
404 if self.src:
405 if rev == self.src:
406 return True
407 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
408 return True
409 return False
410
411 def DestMatches(self, ref):
412 if self.dst:
413 if ref == self.dst:
414 return True
415 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
416 return True
417 return False
418
419 def MapSource(self, rev):
420 if self.src.endswith('/*'):
421 return self.dst[:-1] + rev[len(self.src) - 1:]
422 return self.dst
423
424 def __str__(self):
425 s = ''
426 if self.forced:
427 s += '+'
428 if self.src:
429 s += self.src
430 if self.dst:
431 s += ':'
432 s += self.dst
433 return s
434
435
Doug Anderson06d029c2010-10-27 17:06:01 -0700436_master_processes = []
437_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700438_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800439_master_keys_lock = None
440
David Pursehouse819827a2020-02-12 15:20:19 +0900441
Doug Anderson0048b692010-12-21 13:39:23 -0800442def init_ssh():
443 """Should be called once at the start of repo to init ssh master handling.
444
445 At the moment, all we do is to create our lock.
446 """
447 global _master_keys_lock
448 assert _master_keys_lock is None, "Should only call init_ssh once"
449 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700450
David Pursehouse819827a2020-02-12 15:20:19 +0900451
Josh Guilfoyle71985722009-08-16 09:44:40 -0700452def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700453 global _ssh_master
454
Doug Anderson0048b692010-12-21 13:39:23 -0800455 # Acquire the lock. This is needed to prevent opening multiple masters for
456 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
457 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
458 # one that was passed to repo init.
459 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700460 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700461
Doug Anderson0048b692010-12-21 13:39:23 -0800462 # Check to see whether we already think that the master is running; if we
463 # think it's already running, return right away.
464 if port is not None:
465 key = '%s:%s' % (host, port)
466 else:
467 key = host
468
469 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700470 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700471
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900472 if (not _ssh_master
473 or 'GIT_SSH' in os.environ
474 or sys.platform in ('win32', 'cygwin')):
Doug Anderson0048b692010-12-21 13:39:23 -0800475 # failed earlier, or cygwin ssh can't do this
476 #
477 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700478
Doug Anderson0048b692010-12-21 13:39:23 -0800479 # We will make two calls to ssh; this is the common part of both calls.
480 command_base = ['ssh',
David Pursehouseabdf7502020-02-12 14:58:39 +0900481 '-o', 'ControlPath %s' % ssh_sock(),
482 host]
Doug Anderson0048b692010-12-21 13:39:23 -0800483 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900484 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800485
486 # Since the key wasn't in _master_keys, we think that master isn't running.
487 # ...but before actually starting a master, we'll double-check. This can
488 # be important because we can't tell that that 'git@myhost.com' is the same
489 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
David Pursehouse54a4e602020-02-12 14:31:05 +0900490 check_command = command_base + ['-O', 'check']
Doug Anderson0048b692010-12-21 13:39:23 -0800491 try:
492 Trace(': %s', ' '.join(check_command))
493 check_process = subprocess.Popen(check_command,
494 stdout=subprocess.PIPE,
495 stderr=subprocess.PIPE)
David Pursehouse54a4e602020-02-12 14:31:05 +0900496 check_process.communicate() # read output, but ignore it...
Doug Anderson0048b692010-12-21 13:39:23 -0800497 isnt_running = check_process.wait()
498
499 if not isnt_running:
500 # Our double-check found that the master _was_ infact running. Add to
501 # the list of keys.
502 _master_keys.add(key)
503 return True
504 except Exception:
505 # Ignore excpetions. We we will fall back to the normal command and print
506 # to the log there.
507 pass
508
David Pursehouse0ab95ba2020-02-12 15:01:59 +0900509 command = command_base[:1] + ['-M', '-N'] + command_base[1:]
Doug Anderson0048b692010-12-21 13:39:23 -0800510 try:
511 Trace(': %s', ' '.join(command))
512 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700513 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800514 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700515 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
David Pursehouseabdf7502020-02-12 14:58:39 +0900516 % (host, port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800517 return False
518
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200519 time.sleep(1)
520 ssh_died = (p.poll() is not None)
521 if ssh_died:
522 return False
523
Doug Anderson0048b692010-12-21 13:39:23 -0800524 _master_processes.append(p)
525 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800526 return True
527 finally:
528 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700529
David Pursehouse819827a2020-02-12 15:20:19 +0900530
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700531def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800532 global _master_keys_lock
533
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700534 terminate_ssh_clients()
535
Doug Anderson06d029c2010-10-27 17:06:01 -0700536 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700537 try:
Mike Frysingeraf1e5de2020-02-17 14:58:37 -0500538 os.kill(p.pid, signal.SIGTERM)
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700539 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700540 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700541 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700542 del _master_processes[:]
543 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700544
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700545 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700546 if d:
547 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700548 platform_utils.rmdir(os.path.dirname(d))
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700549 except OSError:
550 pass
551
Doug Anderson0048b692010-12-21 13:39:23 -0800552 # We're done with the lock, so we can delete it.
553 _master_keys_lock = None
554
David Pursehouse819827a2020-02-12 15:20:19 +0900555
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700556URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700557URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700558
David Pursehouse819827a2020-02-12 15:20:19 +0900559
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700560def GetSchemeFromUrl(url):
561 m = URI_ALL.match(url)
562 if m:
563 return m.group(1)
564 return None
565
David Pursehouse819827a2020-02-12 15:20:19 +0900566
Dan Willemsen0745bb22015-08-17 13:41:45 -0700567@contextlib.contextmanager
568def GetUrlCookieFile(url, quiet):
569 if url.startswith('persistent-'):
570 try:
571 p = subprocess.Popen(
572 ['git-remote-persistent-https', '-print_config', url],
573 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
574 stderr=subprocess.PIPE)
575 try:
576 cookieprefix = 'http.cookiefile='
577 proxyprefix = 'http.proxy='
578 cookiefile = None
579 proxy = None
580 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500581 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700582 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900583 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700584 if line.startswith(proxyprefix):
585 proxy = line[len(proxyprefix):]
586 # Leave subprocess open, as cookie file may be transient.
587 if cookiefile or proxy:
588 yield cookiefile, proxy
589 return
590 finally:
591 p.stdin.close()
592 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500593 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700594 if ' -print_config' in err_msg:
595 pass # Persistent proxy doesn't support -print_config.
596 elif not quiet:
597 print(err_msg, file=sys.stderr)
598 except OSError as e:
599 if e.errno == errno.ENOENT:
600 pass # No persistent proxy.
601 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900602 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
603 if cookiefile:
604 cookiefile = os.path.expanduser(cookiefile)
605 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700606
David Pursehouse819827a2020-02-12 15:20:19 +0900607
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700608def _preconnect(url):
609 m = URI_ALL.match(url)
610 if m:
611 scheme = m.group(1)
612 host = m.group(2)
613 if ':' in host:
614 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700615 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700616 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700617 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
618 return _open_ssh(host, port)
619 return False
620
621 m = URI_SCP.match(url)
622 if m:
623 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700624 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700625
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700626 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700627
David Pursehouse819827a2020-02-12 15:20:19 +0900628
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629class Remote(object):
630 """Configuration options related to a remote.
631 """
David Pursehouse819827a2020-02-12 15:20:19 +0900632
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700633 def __init__(self, config, name):
634 self._config = config
635 self.name = name
636 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700637 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800639 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530640 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900641 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800642 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800643
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100644 def _InsteadOf(self):
645 globCfg = GitConfig.ForUser()
646 urlList = globCfg.GetSubSections('url')
647 longest = ""
648 longestUrl = ""
649
650 for url in urlList:
651 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900652 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100653
654 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900655 if (self.url.startswith(insteadOf)
656 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100657 longest = insteadOf
658 longestUrl = url
659
660 if len(longest) == 0:
661 return self.url
662
663 return self.url.replace(longest, longestUrl, 1)
664
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700665 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100666 connectionUrl = self._InsteadOf()
667 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700668
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200669 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800670 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800671 if self.review is None:
672 return None
673
674 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700675 if u.startswith('persistent-'):
676 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100677 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800678 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700679 if u.endswith('/Gerrit'):
680 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800681 if u.endswith('/ssh_info'):
682 u = u[:len(u) - len('/ssh_info')]
683 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900684 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800685 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800686
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700687 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800688 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700689 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800690 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
691 self._review_url = self._SshReviewUrl(userEmail, host, port)
692 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100693 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800694 self._review_url = u # Assume it's right
695 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200696 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
697 self._review_url = http_url
698 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700699 else:
700 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800701 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200702 if not validate_certs:
703 context = ssl._create_unverified_context()
704 info = urllib.request.urlopen(info_url, context=context).read()
705 else:
706 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400707 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700708 # If `info` contains '<', we assume the server gave us some sort
709 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700710 #
Conley Owens745a39b2013-06-05 13:16:18 -0700711 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800712 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700713 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400714 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800715 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000716 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700717 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800718 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700719 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700720 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900721 except HTTPException as e:
722 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800723
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800724 REVIEW_CACHE[u] = self._review_url
725 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800726
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800727 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700728 username = self._config.GetString('review.%s.username' % self.review)
729 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800730 username = userEmail.split('@')[0]
731 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732
733 def ToLocal(self, rev):
734 """Convert a remote revision string to something we have locally.
735 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200736 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700738
739 if not rev.startswith('refs/'):
740 rev = R_HEADS + rev
741
742 for spec in self.fetch:
743 if spec.SourceMatches(rev):
744 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600745
746 if not rev.startswith(R_HEADS):
747 return rev
748
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400749 raise GitError('%s: remote %s does not have %s' %
750 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751
752 def WritesTo(self, ref):
753 """True if the remote stores to the tracking ref.
754 """
755 for spec in self.fetch:
756 if spec.DestMatches(ref):
757 return True
758 return False
759
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800760 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700761 """Set the fetch refspec to its default value.
762 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800763 if mirror:
764 dst = 'refs/heads/*'
765 else:
766 dst = 'refs/remotes/%s/*' % self.name
767 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768
769 def Save(self):
770 """Save this remote to the configuration.
771 """
772 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700773 if self.pushUrl is not None:
774 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
775 else:
776 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700777 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800778 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530779 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
781 def _Set(self, key, value):
782 key = 'remote.%s.%s' % (self.name, key)
783 return self._config.SetString(key, value)
784
David Pursehouse8a68ff92012-09-24 12:15:13 +0900785 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900787 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700788
789
790class Branch(object):
791 """Configuration options related to a single branch.
792 """
David Pursehouse819827a2020-02-12 15:20:19 +0900793
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794 def __init__(self, config, name):
795 self._config = config
796 self.name = name
797 self.merge = self._Get('merge')
798
799 r = self._Get('remote')
800 if r:
801 self.remote = self._config.GetRemote(r)
802 else:
803 self.remote = None
804
805 @property
806 def LocalMerge(self):
807 """Convert the merge spec to a local name.
808 """
809 if self.remote and self.merge:
810 return self.remote.ToLocal(self.merge)
811 return None
812
813 def Save(self):
814 """Save this branch back into the configuration.
815 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700816 if self._config.HasSection('branch', self.name):
817 if self.remote:
818 self._Set('remote', self.remote.name)
819 else:
820 self._Set('remote', None)
821 self._Set('merge', self.merge)
822
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700823 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500824 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700825 fd.write('[branch "%s"]\n' % self.name)
826 if self.remote:
827 fd.write('\tremote = %s\n' % self.remote.name)
828 if self.merge:
829 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700830
831 def _Set(self, key, value):
832 key = 'branch.%s.%s' % (self.name, key)
833 return self._config.SetString(key, value)
834
David Pursehouse8a68ff92012-09-24 12:15:13 +0900835 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700836 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900837 return self._config.GetString(key, all_keys=all_keys)