blob: 282c0802dca8c0e38ba1c9ab8538982a4889226f [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
Mike Frysinger38867fb2021-02-09 23:14:41 -0500164 def SetBoolean(self, name, value):
165 """Set the truthy value for a key."""
166 if value is not None:
167 value = 'true' if value else 'false'
168 self.SetString(name, value)
169
David Pursehouse8a68ff92012-09-24 12:15:13 +0900170 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 """Get the first value for a key, or None if it is not defined.
172
173 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900174 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700177 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 except KeyError:
179 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900180 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181 v = []
182
David Pursehouse8a68ff92012-09-24 12:15:13 +0900183 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184 if v:
185 return v[0]
186 return None
187
188 r = []
189 r.extend(v)
190 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900191 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192 return r
193
194 def SetString(self, name, value):
195 """Set the value(s) for a key.
196 Only this configuration file is modified.
197
198 The supplied value should be either a string,
199 or a list of strings (to store multiple values).
200 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700201 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202
203 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700204 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700205 except KeyError:
206 old = []
207
208 if value is None:
209 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700210 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211 self._do('--unset-all', name)
212
213 elif isinstance(value, list):
214 if len(value) == 0:
215 self.SetString(name, None)
216
217 elif len(value) == 1:
218 self.SetString(name, value[0])
219
220 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700221 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700222 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700223 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700224 self._do('--add', name, value[i])
225
226 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700227 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 self._do('--replace-all', name, value)
229
230 def GetRemote(self, name):
231 """Get the remote.$name.* configuration values as an object.
232 """
233 try:
234 r = self._remotes[name]
235 except KeyError:
236 r = Remote(self, name)
237 self._remotes[r.name] = r
238 return r
239
240 def GetBranch(self, name):
241 """Get the branch.$name.* configuration values as an object.
242 """
243 try:
244 b = self._branches[name]
245 except KeyError:
246 b = Branch(self, name)
247 self._branches[b.name] = b
248 return b
249
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700250 def GetSubSections(self, section):
251 """List all subsection names matching $section.*.*
252 """
253 return self._sections.get(section, set())
254
David Pursehousee5913ae2020-02-12 13:56:59 +0900255 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700256 """Does at least one key in section.subsection exist?
257 """
258 try:
259 return subsection in self._sections[section]
260 except KeyError:
261 return False
262
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700263 def UrlInsteadOf(self, url):
264 """Resolve any url.*.insteadof references.
265 """
266 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700267 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
268 if old_url is not None and url.startswith(old_url):
269 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700270 return url
271
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700272 @property
273 def _sections(self):
274 d = self._section_dict
275 if d is None:
276 d = {}
277 for name in self._cache.keys():
278 p = name.split('.')
279 if 2 == len(p):
280 section = p[0]
281 subsect = ''
282 else:
283 section = p[0]
284 subsect = '.'.join(p[1:-1])
285 if section not in d:
286 d[section] = set()
287 d[section].add(subsect)
288 self._section_dict = d
289 return d
290
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700291 @property
292 def _cache(self):
293 if self._cache_dict is None:
294 self._cache_dict = self._Read()
295 return self._cache_dict
296
297 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100298 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700299 if d is None:
300 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100301 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700302 return d
303
Anthony King85b24ac2014-05-06 15:57:48 +0100304 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700305 try:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900306 if os.path.getmtime(self._json) <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800307 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700308 return None
309 except OSError:
310 return None
311 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100312 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500313 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100314 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100315 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800316 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700317 return None
318
Anthony King85b24ac2014-05-06 15:57:48 +0100319 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700320 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500321 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100322 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100323 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100324 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800325 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700326
327 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700328 """
329 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700330
David Aguilar438c5472009-06-28 15:09:16 -0700331 This internal method populates the GitConfig cache.
332
333 """
David Aguilar438c5472009-06-28 15:09:16 -0700334 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700335 d = self._do('--null', '--list')
336 if d is None:
337 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800338 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700339 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900340 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700341 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900342 key = line
343 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700344
345 if key in c:
346 c[key].append(val)
347 else:
348 c[key] = [val]
349
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700350 return c
351
352 def _do(self, *args):
Ulrik Laurénd0ca0f62020-04-28 01:09:57 +0200353 command = ['config', '--file', self.file, '--includes']
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700354 command.extend(args)
355
356 p = GitCommand(None,
357 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900358 capture_stdout=True,
359 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700360 if p.Wait() == 0:
361 return p.stdout
362 else:
363 GitError('git config %s: %s' % (str(args), p.stderr))
364
365
Mike Frysingerf841ca42020-02-18 21:31:51 -0500366class RepoConfig(GitConfig):
367 """User settings for repo itself."""
368
369 _USER_CONFIG = '~/.repoconfig/config'
370
371
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372class RefSpec(object):
373 """A Git refspec line, split into its components:
374
375 forced: True if the line starts with '+'
376 src: Left side of the line
377 dst: Right side of the line
378 """
379
380 @classmethod
381 def FromString(cls, rs):
382 lhs, rhs = rs.split(':', 2)
383 if lhs.startswith('+'):
384 lhs = lhs[1:]
385 forced = True
386 else:
387 forced = False
388 return cls(forced, lhs, rhs)
389
390 def __init__(self, forced, lhs, rhs):
391 self.forced = forced
392 self.src = lhs
393 self.dst = rhs
394
395 def SourceMatches(self, rev):
396 if self.src:
397 if rev == self.src:
398 return True
399 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
400 return True
401 return False
402
403 def DestMatches(self, ref):
404 if self.dst:
405 if ref == self.dst:
406 return True
407 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
408 return True
409 return False
410
411 def MapSource(self, rev):
412 if self.src.endswith('/*'):
413 return self.dst[:-1] + rev[len(self.src) - 1:]
414 return self.dst
415
416 def __str__(self):
417 s = ''
418 if self.forced:
419 s += '+'
420 if self.src:
421 s += self.src
422 if self.dst:
423 s += ':'
424 s += self.dst
425 return s
426
427
Doug Anderson06d029c2010-10-27 17:06:01 -0700428_master_processes = []
429_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700430_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800431_master_keys_lock = None
432
David Pursehouse819827a2020-02-12 15:20:19 +0900433
Doug Anderson0048b692010-12-21 13:39:23 -0800434def init_ssh():
435 """Should be called once at the start of repo to init ssh master handling.
436
437 At the moment, all we do is to create our lock.
438 """
439 global _master_keys_lock
440 assert _master_keys_lock is None, "Should only call init_ssh once"
441 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700442
David Pursehouse819827a2020-02-12 15:20:19 +0900443
Josh Guilfoyle71985722009-08-16 09:44:40 -0700444def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700445 global _ssh_master
446
Doug Anderson0048b692010-12-21 13:39:23 -0800447 # Acquire the lock. This is needed to prevent opening multiple masters for
448 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
449 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
450 # one that was passed to repo init.
451 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700452 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700453
Doug Anderson0048b692010-12-21 13:39:23 -0800454 # Check to see whether we already think that the master is running; if we
455 # think it's already running, return right away.
456 if port is not None:
457 key = '%s:%s' % (host, port)
458 else:
459 key = host
460
461 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700462 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700463
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900464 if (not _ssh_master
465 or 'GIT_SSH' in os.environ
466 or sys.platform in ('win32', 'cygwin')):
Doug Anderson0048b692010-12-21 13:39:23 -0800467 # failed earlier, or cygwin ssh can't do this
468 #
469 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700470
Doug Anderson0048b692010-12-21 13:39:23 -0800471 # We will make two calls to ssh; this is the common part of both calls.
472 command_base = ['ssh',
David Pursehouseabdf7502020-02-12 14:58:39 +0900473 '-o', 'ControlPath %s' % ssh_sock(),
474 host]
Doug Anderson0048b692010-12-21 13:39:23 -0800475 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900476 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800477
478 # Since the key wasn't in _master_keys, we think that master isn't running.
479 # ...but before actually starting a master, we'll double-check. This can
480 # be important because we can't tell that that 'git@myhost.com' is the same
481 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
David Pursehouse54a4e602020-02-12 14:31:05 +0900482 check_command = command_base + ['-O', 'check']
Doug Anderson0048b692010-12-21 13:39:23 -0800483 try:
484 Trace(': %s', ' '.join(check_command))
485 check_process = subprocess.Popen(check_command,
486 stdout=subprocess.PIPE,
487 stderr=subprocess.PIPE)
David Pursehouse54a4e602020-02-12 14:31:05 +0900488 check_process.communicate() # read output, but ignore it...
Doug Anderson0048b692010-12-21 13:39:23 -0800489 isnt_running = check_process.wait()
490
491 if not isnt_running:
492 # Our double-check found that the master _was_ infact running. Add to
493 # the list of keys.
494 _master_keys.add(key)
495 return True
496 except Exception:
497 # Ignore excpetions. We we will fall back to the normal command and print
498 # to the log there.
499 pass
500
David Pursehouse0ab95ba2020-02-12 15:01:59 +0900501 command = command_base[:1] + ['-M', '-N'] + command_base[1:]
Doug Anderson0048b692010-12-21 13:39:23 -0800502 try:
503 Trace(': %s', ' '.join(command))
504 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700505 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800506 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700507 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
David Pursehouseabdf7502020-02-12 14:58:39 +0900508 % (host, port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800509 return False
510
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200511 time.sleep(1)
512 ssh_died = (p.poll() is not None)
513 if ssh_died:
514 return False
515
Doug Anderson0048b692010-12-21 13:39:23 -0800516 _master_processes.append(p)
517 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800518 return True
519 finally:
520 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700521
David Pursehouse819827a2020-02-12 15:20:19 +0900522
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700523def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800524 global _master_keys_lock
525
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700526 terminate_ssh_clients()
527
Doug Anderson06d029c2010-10-27 17:06:01 -0700528 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700529 try:
Mike Frysingeraf1e5de2020-02-17 14:58:37 -0500530 os.kill(p.pid, signal.SIGTERM)
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700531 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700532 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700533 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700534 del _master_processes[:]
535 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700536
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700537 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700538 if d:
539 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700540 platform_utils.rmdir(os.path.dirname(d))
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700541 except OSError:
542 pass
543
Doug Anderson0048b692010-12-21 13:39:23 -0800544 # We're done with the lock, so we can delete it.
545 _master_keys_lock = None
546
David Pursehouse819827a2020-02-12 15:20:19 +0900547
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700548URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700549URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700550
David Pursehouse819827a2020-02-12 15:20:19 +0900551
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700552def GetSchemeFromUrl(url):
553 m = URI_ALL.match(url)
554 if m:
555 return m.group(1)
556 return None
557
David Pursehouse819827a2020-02-12 15:20:19 +0900558
Dan Willemsen0745bb22015-08-17 13:41:45 -0700559@contextlib.contextmanager
560def GetUrlCookieFile(url, quiet):
561 if url.startswith('persistent-'):
562 try:
563 p = subprocess.Popen(
564 ['git-remote-persistent-https', '-print_config', url],
565 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
566 stderr=subprocess.PIPE)
567 try:
568 cookieprefix = 'http.cookiefile='
569 proxyprefix = 'http.proxy='
570 cookiefile = None
571 proxy = None
572 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500573 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700574 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900575 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700576 if line.startswith(proxyprefix):
577 proxy = line[len(proxyprefix):]
578 # Leave subprocess open, as cookie file may be transient.
579 if cookiefile or proxy:
580 yield cookiefile, proxy
581 return
582 finally:
583 p.stdin.close()
584 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500585 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700586 if ' -print_config' in err_msg:
587 pass # Persistent proxy doesn't support -print_config.
588 elif not quiet:
589 print(err_msg, file=sys.stderr)
590 except OSError as e:
591 if e.errno == errno.ENOENT:
592 pass # No persistent proxy.
593 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900594 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
595 if cookiefile:
596 cookiefile = os.path.expanduser(cookiefile)
597 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700598
David Pursehouse819827a2020-02-12 15:20:19 +0900599
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700600def _preconnect(url):
601 m = URI_ALL.match(url)
602 if m:
603 scheme = m.group(1)
604 host = m.group(2)
605 if ':' in host:
606 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700607 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700608 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700609 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
610 return _open_ssh(host, port)
611 return False
612
613 m = URI_SCP.match(url)
614 if m:
615 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700616 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700617
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700618 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700619
David Pursehouse819827a2020-02-12 15:20:19 +0900620
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700621class Remote(object):
622 """Configuration options related to a remote.
623 """
David Pursehouse819827a2020-02-12 15:20:19 +0900624
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700625 def __init__(self, config, name):
626 self._config = config
627 self.name = name
628 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700629 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700630 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800631 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530632 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900633 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800634 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800635
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100636 def _InsteadOf(self):
637 globCfg = GitConfig.ForUser()
638 urlList = globCfg.GetSubSections('url')
639 longest = ""
640 longestUrl = ""
641
642 for url in urlList:
643 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900644 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100645
646 for insteadOf in insteadOfList:
David Pursehouse16a5c3a2020-02-12 15:54:26 +0900647 if (self.url.startswith(insteadOf)
648 and len(insteadOf) > len(longest)):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100649 longest = insteadOf
650 longestUrl = url
651
652 if len(longest) == 0:
653 return self.url
654
655 return self.url.replace(longest, longestUrl, 1)
656
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700657 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100658 connectionUrl = self._InsteadOf()
659 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700660
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200661 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800662 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800663 if self.review is None:
664 return None
665
666 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700667 if u.startswith('persistent-'):
668 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100669 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800670 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700671 if u.endswith('/Gerrit'):
672 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800673 if u.endswith('/ssh_info'):
674 u = u[:len(u) - len('/ssh_info')]
675 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900676 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800677 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800678
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700679 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800680 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700681 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800682 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
683 self._review_url = self._SshReviewUrl(userEmail, host, port)
684 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100685 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800686 self._review_url = u # Assume it's right
687 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200688 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
689 self._review_url = http_url
690 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700691 else:
692 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800693 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200694 if not validate_certs:
695 context = ssl._create_unverified_context()
696 info = urllib.request.urlopen(info_url, context=context).read()
697 else:
698 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400699 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700700 # If `info` contains '<', we assume the server gave us some sort
701 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700702 #
Conley Owens745a39b2013-06-05 13:16:18 -0700703 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800704 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700705 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400706 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800707 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000708 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700709 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800710 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700711 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700712 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900713 except HTTPException as e:
714 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800715
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800716 REVIEW_CACHE[u] = self._review_url
717 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800718
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800719 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700720 username = self._config.GetString('review.%s.username' % self.review)
721 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800722 username = userEmail.split('@')[0]
723 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700724
725 def ToLocal(self, rev):
726 """Convert a remote revision string to something we have locally.
727 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200728 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700730
731 if not rev.startswith('refs/'):
732 rev = R_HEADS + rev
733
734 for spec in self.fetch:
735 if spec.SourceMatches(rev):
736 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600737
738 if not rev.startswith(R_HEADS):
739 return rev
740
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400741 raise GitError('%s: remote %s does not have %s' %
742 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700743
744 def WritesTo(self, ref):
745 """True if the remote stores to the tracking ref.
746 """
747 for spec in self.fetch:
748 if spec.DestMatches(ref):
749 return True
750 return False
751
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800752 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700753 """Set the fetch refspec to its default value.
754 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800755 if mirror:
756 dst = 'refs/heads/*'
757 else:
758 dst = 'refs/remotes/%s/*' % self.name
759 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700760
761 def Save(self):
762 """Save this remote to the configuration.
763 """
764 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700765 if self.pushUrl is not None:
766 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
767 else:
768 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700769 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800770 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530771 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772
773 def _Set(self, key, value):
774 key = 'remote.%s.%s' % (self.name, key)
775 return self._config.SetString(key, value)
776
David Pursehouse8a68ff92012-09-24 12:15:13 +0900777 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900779 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780
781
782class Branch(object):
783 """Configuration options related to a single branch.
784 """
David Pursehouse819827a2020-02-12 15:20:19 +0900785
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700786 def __init__(self, config, name):
787 self._config = config
788 self.name = name
789 self.merge = self._Get('merge')
790
791 r = self._Get('remote')
792 if r:
793 self.remote = self._config.GetRemote(r)
794 else:
795 self.remote = None
796
797 @property
798 def LocalMerge(self):
799 """Convert the merge spec to a local name.
800 """
801 if self.remote and self.merge:
802 return self.remote.ToLocal(self.merge)
803 return None
804
805 def Save(self):
806 """Save this branch back into the configuration.
807 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700808 if self._config.HasSection('branch', self.name):
809 if self.remote:
810 self._Set('remote', self.remote.name)
811 else:
812 self._Set('remote', None)
813 self._Set('merge', self.merge)
814
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700815 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500816 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700817 fd.write('[branch "%s"]\n' % self.name)
818 if self.remote:
819 fd.write('\tremote = %s\n' % self.remote.name)
820 if self.merge:
821 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700822
823 def _Set(self, key, value):
824 key = 'branch.%s.%s' % (self.name, key)
825 return self._config.SetString(key, value)
826
David Pursehouse8a68ff92012-09-24 12:15:13 +0900827 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700828 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900829 return self._config.GetString(key, all_keys=all_keys)