blob: 5fb61d21705759c2a7877b4b2686765b43a22191 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Chirayu Desai217ea7d2013-03-01 19:14:38 +053018
Dan Willemsen0745bb22015-08-17 13:41:45 -070019import contextlib
20import errno
Anthony King85b24ac2014-05-06 15:57:48 +010021import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
23import re
Łukasz Gardońbed59ce2017-08-08 10:18:11 +020024import ssl
Shawn O. Pearcefb231612009-04-10 18:53:46 -070025import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026import sys
Doug Anderson0048b692010-12-21 13:39:23 -080027try:
28 import threading as _threading
29except ImportError:
30 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070031import time
David Pursehouse59bbb582013-05-17 10:49:33 +090032
33from pyversion import is_python3
34if is_python3():
Sarah Owens1f7627f2012-10-31 09:21:55 -070035 import urllib.request
36 import urllib.error
37else:
David Pursehouse59bbb582013-05-17 10:49:33 +090038 import urllib2
Sarah Owens1f7627f2012-10-31 09:21:55 -070039 import imp
40 urllib = imp.new_module('urllib')
41 urllib.request = urllib2
42 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070043
Shawn O. Pearcefb231612009-04-10 18:53:46 -070044from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080045from error import GitError, UploadError
Renaud Paquay010fed72016-11-11 14:25:29 -080046import platform_utils
Mike Frysinger8a11f6f2019-08-27 00:26:15 -040047from repo_trace import Trace
David Pursehouseecf8f2b2013-05-24 12:12:23 +090048if is_python3():
49 from http.client import HTTPException
50else:
51 from httplib import HTTPException
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070052
53from git_command import GitCommand
54from git_command import ssh_sock
55from git_command import terminate_ssh_clients
Zac Livingston9ead97b2017-06-13 08:29:04 -060056from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057
David Pursehouse1d947b32012-10-25 12:23:11 +090058ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070059
Shawn O. Pearce146fe902009-03-25 14:06:43 -070060REVIEW_CACHE = dict()
61
David Pursehouse819827a2020-02-12 15:20:19 +090062
Zac Livingston9ead97b2017-06-13 08:29:04 -060063def IsChange(rev):
64 return rev.startswith(R_CHANGES)
65
David Pursehouse819827a2020-02-12 15:20:19 +090066
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067def IsId(rev):
68 return ID_RE.match(rev)
69
David Pursehouse819827a2020-02-12 15:20:19 +090070
Zac Livingston9ead97b2017-06-13 08:29:04 -060071def IsTag(rev):
72 return rev.startswith(R_TAGS)
73
David Pursehouse819827a2020-02-12 15:20:19 +090074
Zac Livingston9ead97b2017-06-13 08:29:04 -060075def IsImmutable(rev):
76 return IsChange(rev) or IsId(rev) or IsTag(rev)
77
David Pursehouse819827a2020-02-12 15:20:19 +090078
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070079def _key(name):
80 parts = name.split('.')
81 if len(parts) < 2:
82 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090083 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070084 parts[-1] = parts[-1].lower()
85 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070086
David Pursehouse819827a2020-02-12 15:20:19 +090087
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070088class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070089 _ForUser = None
90
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070091 @classmethod
92 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070093 if cls._ForUser is None:
David Pursehousee5913ae2020-02-12 13:56:59 +090094 cls._ForUser = cls(configfile=os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070095 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070096
97 @classmethod
98 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090099 return cls(configfile=os.path.join(gitdir, 'config'),
100 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101
Anthony King85b24ac2014-05-06 15:57:48 +0100102 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900103 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700104 self.defaults = defaults
105 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700106 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700107 self._remotes = {}
108 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700109
Anthony King85b24ac2014-05-06 15:57:48 +0100110 self._json = jsonFile
111 if self._json is None:
112 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900113 os.path.dirname(self.file),
114 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115
David Pursehousee5913ae2020-02-12 13:56:59 +0900116 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117 """Return true if this configuration file has the key.
118 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700119 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700120 return True
121 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900122 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 return False
124
125 def GetBoolean(self, name):
126 """Returns a boolean from the configuration file.
127 None : The value was not defined, or is not a boolean.
128 True : The value was set to true or yes.
129 False: The value was set to false or no.
130 """
131 v = self.GetString(name)
132 if v is None:
133 return None
134 v = v.lower()
135 if v in ('true', 'yes'):
136 return True
137 if v in ('false', 'no'):
138 return False
139 return None
140
David Pursehouse8a68ff92012-09-24 12:15:13 +0900141 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142 """Get the first value for a key, or None if it is not defined.
143
144 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900145 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700147 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700148 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700149 except KeyError:
150 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900151 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700152 v = []
153
David Pursehouse8a68ff92012-09-24 12:15:13 +0900154 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700155 if v:
156 return v[0]
157 return None
158
159 r = []
160 r.extend(v)
161 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900162 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700163 return r
164
165 def SetString(self, name, value):
166 """Set the value(s) for a key.
167 Only this configuration file is modified.
168
169 The supplied value should be either a string,
170 or a list of strings (to store multiple values).
171 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700172 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173
174 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700175 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 except KeyError:
177 old = []
178
179 if value is None:
180 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700181 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182 self._do('--unset-all', name)
183
184 elif isinstance(value, list):
185 if len(value) == 0:
186 self.SetString(name, None)
187
188 elif len(value) == 1:
189 self.SetString(name, value[0])
190
191 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700192 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700194 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700195 self._do('--add', name, value[i])
196
197 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700198 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199 self._do('--replace-all', name, value)
200
201 def GetRemote(self, name):
202 """Get the remote.$name.* configuration values as an object.
203 """
204 try:
205 r = self._remotes[name]
206 except KeyError:
207 r = Remote(self, name)
208 self._remotes[r.name] = r
209 return r
210
211 def GetBranch(self, name):
212 """Get the branch.$name.* configuration values as an object.
213 """
214 try:
215 b = self._branches[name]
216 except KeyError:
217 b = Branch(self, name)
218 self._branches[b.name] = b
219 return b
220
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700221 def GetSubSections(self, section):
222 """List all subsection names matching $section.*.*
223 """
224 return self._sections.get(section, set())
225
David Pursehousee5913ae2020-02-12 13:56:59 +0900226 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700227 """Does at least one key in section.subsection exist?
228 """
229 try:
230 return subsection in self._sections[section]
231 except KeyError:
232 return False
233
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700234 def UrlInsteadOf(self, url):
235 """Resolve any url.*.insteadof references.
236 """
237 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700238 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
239 if old_url is not None and url.startswith(old_url):
240 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700241 return url
242
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700243 @property
244 def _sections(self):
245 d = self._section_dict
246 if d is None:
247 d = {}
248 for name in self._cache.keys():
249 p = name.split('.')
250 if 2 == len(p):
251 section = p[0]
252 subsect = ''
253 else:
254 section = p[0]
255 subsect = '.'.join(p[1:-1])
256 if section not in d:
257 d[section] = set()
258 d[section].add(subsect)
259 self._section_dict = d
260 return d
261
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700262 @property
263 def _cache(self):
264 if self._cache_dict is None:
265 self._cache_dict = self._Read()
266 return self._cache_dict
267
268 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100269 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700270 if d is None:
271 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100272 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700273 return d
274
Anthony King85b24ac2014-05-06 15:57:48 +0100275 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700276 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100277 if os.path.getmtime(self._json) \
David Pursehouseabdf7502020-02-12 14:58:39 +0900278 <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800279 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700280 return None
281 except OSError:
282 return None
283 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100284 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500285 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100286 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100287 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800288 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700289 return None
290
Anthony King85b24ac2014-05-06 15:57:48 +0100291 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700292 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500293 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100294 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100295 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100296 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800297 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700298
299 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700300 """
301 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700302
David Aguilar438c5472009-06-28 15:09:16 -0700303 This internal method populates the GitConfig cache.
304
305 """
David Aguilar438c5472009-06-28 15:09:16 -0700306 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700307 d = self._do('--null', '--list')
308 if d is None:
309 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800310 if not is_python3():
311 d = d.decode('utf-8')
312 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700313 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900314 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700315 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900316 key = line
317 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700318
319 if key in c:
320 c[key].append(val)
321 else:
322 c[key] = [val]
323
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700324 return c
325
326 def _do(self, *args):
327 command = ['config', '--file', self.file]
328 command.extend(args)
329
330 p = GitCommand(None,
331 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900332 capture_stdout=True,
333 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700334 if p.Wait() == 0:
335 return p.stdout
336 else:
337 GitError('git config %s: %s' % (str(args), p.stderr))
338
339
340class RefSpec(object):
341 """A Git refspec line, split into its components:
342
343 forced: True if the line starts with '+'
344 src: Left side of the line
345 dst: Right side of the line
346 """
347
348 @classmethod
349 def FromString(cls, rs):
350 lhs, rhs = rs.split(':', 2)
351 if lhs.startswith('+'):
352 lhs = lhs[1:]
353 forced = True
354 else:
355 forced = False
356 return cls(forced, lhs, rhs)
357
358 def __init__(self, forced, lhs, rhs):
359 self.forced = forced
360 self.src = lhs
361 self.dst = rhs
362
363 def SourceMatches(self, rev):
364 if self.src:
365 if rev == self.src:
366 return True
367 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
368 return True
369 return False
370
371 def DestMatches(self, ref):
372 if self.dst:
373 if ref == self.dst:
374 return True
375 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
376 return True
377 return False
378
379 def MapSource(self, rev):
380 if self.src.endswith('/*'):
381 return self.dst[:-1] + rev[len(self.src) - 1:]
382 return self.dst
383
384 def __str__(self):
385 s = ''
386 if self.forced:
387 s += '+'
388 if self.src:
389 s += self.src
390 if self.dst:
391 s += ':'
392 s += self.dst
393 return s
394
395
Doug Anderson06d029c2010-10-27 17:06:01 -0700396_master_processes = []
397_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700398_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800399_master_keys_lock = None
400
David Pursehouse819827a2020-02-12 15:20:19 +0900401
Doug Anderson0048b692010-12-21 13:39:23 -0800402def init_ssh():
403 """Should be called once at the start of repo to init ssh master handling.
404
405 At the moment, all we do is to create our lock.
406 """
407 global _master_keys_lock
408 assert _master_keys_lock is None, "Should only call init_ssh once"
409 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700410
David Pursehouse819827a2020-02-12 15:20:19 +0900411
Josh Guilfoyle71985722009-08-16 09:44:40 -0700412def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700413 global _ssh_master
414
Doug Anderson0048b692010-12-21 13:39:23 -0800415 # Acquire the lock. This is needed to prevent opening multiple masters for
416 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
417 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
418 # one that was passed to repo init.
419 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700420 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700421
Doug Anderson0048b692010-12-21 13:39:23 -0800422 # Check to see whether we already think that the master is running; if we
423 # think it's already running, return right away.
424 if port is not None:
425 key = '%s:%s' % (host, port)
426 else:
427 key = host
428
429 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700430 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700431
Doug Anderson0048b692010-12-21 13:39:23 -0800432 if not _ssh_master \
David Pursehouseabdf7502020-02-12 14:58:39 +0900433 or 'GIT_SSH' in os.environ \
434 or sys.platform in ('win32', 'cygwin'):
Doug Anderson0048b692010-12-21 13:39:23 -0800435 # failed earlier, or cygwin ssh can't do this
436 #
437 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700438
Doug Anderson0048b692010-12-21 13:39:23 -0800439 # We will make two calls to ssh; this is the common part of both calls.
440 command_base = ['ssh',
David Pursehouseabdf7502020-02-12 14:58:39 +0900441 '-o', 'ControlPath %s' % ssh_sock(),
442 host]
Doug Anderson0048b692010-12-21 13:39:23 -0800443 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900444 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800445
446 # Since the key wasn't in _master_keys, we think that master isn't running.
447 # ...but before actually starting a master, we'll double-check. This can
448 # be important because we can't tell that that 'git@myhost.com' is the same
449 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
David Pursehouse54a4e602020-02-12 14:31:05 +0900450 check_command = command_base + ['-O', 'check']
Doug Anderson0048b692010-12-21 13:39:23 -0800451 try:
452 Trace(': %s', ' '.join(check_command))
453 check_process = subprocess.Popen(check_command,
454 stdout=subprocess.PIPE,
455 stderr=subprocess.PIPE)
David Pursehouse54a4e602020-02-12 14:31:05 +0900456 check_process.communicate() # read output, but ignore it...
Doug Anderson0048b692010-12-21 13:39:23 -0800457 isnt_running = check_process.wait()
458
459 if not isnt_running:
460 # Our double-check found that the master _was_ infact running. Add to
461 # the list of keys.
462 _master_keys.add(key)
463 return True
464 except Exception:
465 # Ignore excpetions. We we will fall back to the normal command and print
466 # to the log there.
467 pass
468
David Pursehouse0ab95ba2020-02-12 15:01:59 +0900469 command = command_base[:1] + ['-M', '-N'] + command_base[1:]
Doug Anderson0048b692010-12-21 13:39:23 -0800470 try:
471 Trace(': %s', ' '.join(command))
472 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700473 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800474 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700475 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
David Pursehouseabdf7502020-02-12 14:58:39 +0900476 % (host, port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800477 return False
478
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200479 time.sleep(1)
480 ssh_died = (p.poll() is not None)
481 if ssh_died:
482 return False
483
Doug Anderson0048b692010-12-21 13:39:23 -0800484 _master_processes.append(p)
485 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800486 return True
487 finally:
488 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700489
David Pursehouse819827a2020-02-12 15:20:19 +0900490
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700491def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800492 global _master_keys_lock
493
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700494 terminate_ssh_clients()
495
Doug Anderson06d029c2010-10-27 17:06:01 -0700496 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700497 try:
498 os.kill(p.pid, SIGTERM)
499 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700500 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700501 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700502 del _master_processes[:]
503 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700504
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700505 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700506 if d:
507 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700508 platform_utils.rmdir(os.path.dirname(d))
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700509 except OSError:
510 pass
511
Doug Anderson0048b692010-12-21 13:39:23 -0800512 # We're done with the lock, so we can delete it.
513 _master_keys_lock = None
514
David Pursehouse819827a2020-02-12 15:20:19 +0900515
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700516URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700517URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700518
David Pursehouse819827a2020-02-12 15:20:19 +0900519
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700520def GetSchemeFromUrl(url):
521 m = URI_ALL.match(url)
522 if m:
523 return m.group(1)
524 return None
525
David Pursehouse819827a2020-02-12 15:20:19 +0900526
Dan Willemsen0745bb22015-08-17 13:41:45 -0700527@contextlib.contextmanager
528def GetUrlCookieFile(url, quiet):
529 if url.startswith('persistent-'):
530 try:
531 p = subprocess.Popen(
532 ['git-remote-persistent-https', '-print_config', url],
533 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
534 stderr=subprocess.PIPE)
535 try:
536 cookieprefix = 'http.cookiefile='
537 proxyprefix = 'http.proxy='
538 cookiefile = None
539 proxy = None
540 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500541 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700542 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900543 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700544 if line.startswith(proxyprefix):
545 proxy = line[len(proxyprefix):]
546 # Leave subprocess open, as cookie file may be transient.
547 if cookiefile or proxy:
548 yield cookiefile, proxy
549 return
550 finally:
551 p.stdin.close()
552 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500553 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700554 if ' -print_config' in err_msg:
555 pass # Persistent proxy doesn't support -print_config.
556 elif not quiet:
557 print(err_msg, file=sys.stderr)
558 except OSError as e:
559 if e.errno == errno.ENOENT:
560 pass # No persistent proxy.
561 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900562 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
563 if cookiefile:
564 cookiefile = os.path.expanduser(cookiefile)
565 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700566
David Pursehouse819827a2020-02-12 15:20:19 +0900567
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700568def _preconnect(url):
569 m = URI_ALL.match(url)
570 if m:
571 scheme = m.group(1)
572 host = m.group(2)
573 if ':' in host:
574 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700575 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700576 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700577 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
578 return _open_ssh(host, port)
579 return False
580
581 m = URI_SCP.match(url)
582 if m:
583 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700584 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700585
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700586 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700587
David Pursehouse819827a2020-02-12 15:20:19 +0900588
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700589class Remote(object):
590 """Configuration options related to a remote.
591 """
David Pursehouse819827a2020-02-12 15:20:19 +0900592
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700593 def __init__(self, config, name):
594 self._config = config
595 self.name = name
596 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700597 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700598 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800599 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530600 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900601 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800602 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800603
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100604 def _InsteadOf(self):
605 globCfg = GitConfig.ForUser()
606 urlList = globCfg.GetSubSections('url')
607 longest = ""
608 longestUrl = ""
609
610 for url in urlList:
611 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900612 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100613
614 for insteadOf in insteadOfList:
615 if self.url.startswith(insteadOf) \
David Pursehouseabdf7502020-02-12 14:58:39 +0900616 and len(insteadOf) > len(longest):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100617 longest = insteadOf
618 longestUrl = url
619
620 if len(longest) == 0:
621 return self.url
622
623 return self.url.replace(longest, longestUrl, 1)
624
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700625 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100626 connectionUrl = self._InsteadOf()
627 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700628
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200629 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800630 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800631 if self.review is None:
632 return None
633
634 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700635 if u.startswith('persistent-'):
636 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100637 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800638 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700639 if u.endswith('/Gerrit'):
640 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800641 if u.endswith('/ssh_info'):
642 u = u[:len(u) - len('/ssh_info')]
643 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900644 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800645 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800646
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700647 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800648 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700649 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800650 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
651 self._review_url = self._SshReviewUrl(userEmail, host, port)
652 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100653 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800654 self._review_url = u # Assume it's right
655 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200656 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
657 self._review_url = http_url
658 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700659 else:
660 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800661 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200662 if not validate_certs:
663 context = ssl._create_unverified_context()
664 info = urllib.request.urlopen(info_url, context=context).read()
665 else:
666 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400667 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700668 # If `info` contains '<', we assume the server gave us some sort
669 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700670 #
Conley Owens745a39b2013-06-05 13:16:18 -0700671 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800672 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700673 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400674 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800675 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000676 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700677 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800678 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700679 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700680 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900681 except HTTPException as e:
682 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800683
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800684 REVIEW_CACHE[u] = self._review_url
685 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800686
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800687 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700688 username = self._config.GetString('review.%s.username' % self.review)
689 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800690 username = userEmail.split('@')[0]
691 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700692
693 def ToLocal(self, rev):
694 """Convert a remote revision string to something we have locally.
695 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200696 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698
699 if not rev.startswith('refs/'):
700 rev = R_HEADS + rev
701
702 for spec in self.fetch:
703 if spec.SourceMatches(rev):
704 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600705
706 if not rev.startswith(R_HEADS):
707 return rev
708
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400709 raise GitError('%s: remote %s does not have %s' %
710 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700711
712 def WritesTo(self, ref):
713 """True if the remote stores to the tracking ref.
714 """
715 for spec in self.fetch:
716 if spec.DestMatches(ref):
717 return True
718 return False
719
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800720 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700721 """Set the fetch refspec to its default value.
722 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800723 if mirror:
724 dst = 'refs/heads/*'
725 else:
726 dst = 'refs/remotes/%s/*' % self.name
727 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728
729 def Save(self):
730 """Save this remote to the configuration.
731 """
732 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700733 if self.pushUrl is not None:
734 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
735 else:
736 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800738 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530739 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700740
741 def _Set(self, key, value):
742 key = 'remote.%s.%s' % (self.name, key)
743 return self._config.SetString(key, value)
744
David Pursehouse8a68ff92012-09-24 12:15:13 +0900745 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900747 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700748
749
750class Branch(object):
751 """Configuration options related to a single branch.
752 """
David Pursehouse819827a2020-02-12 15:20:19 +0900753
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700754 def __init__(self, config, name):
755 self._config = config
756 self.name = name
757 self.merge = self._Get('merge')
758
759 r = self._Get('remote')
760 if r:
761 self.remote = self._config.GetRemote(r)
762 else:
763 self.remote = None
764
765 @property
766 def LocalMerge(self):
767 """Convert the merge spec to a local name.
768 """
769 if self.remote and self.merge:
770 return self.remote.ToLocal(self.merge)
771 return None
772
773 def Save(self):
774 """Save this branch back into the configuration.
775 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700776 if self._config.HasSection('branch', self.name):
777 if self.remote:
778 self._Set('remote', self.remote.name)
779 else:
780 self._Set('remote', None)
781 self._Set('merge', self.merge)
782
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500784 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700785 fd.write('[branch "%s"]\n' % self.name)
786 if self.remote:
787 fd.write('\tremote = %s\n' % self.remote.name)
788 if self.merge:
789 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790
791 def _Set(self, key, value):
792 key = 'branch.%s.%s' % (self.name, key)
793 return self._config.SetString(key, value)
794
David Pursehouse8a68ff92012-09-24 12:15:13 +0900795 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700796 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900797 return self._config.GetString(key, all_keys=all_keys)