blob: 13fbda245576d52286d3cc138fb3819bfc1ff197 [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
Zac Livingston9ead97b2017-06-13 08:29:04 -060062def IsChange(rev):
63 return rev.startswith(R_CHANGES)
64
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065def IsId(rev):
66 return ID_RE.match(rev)
67
Zac Livingston9ead97b2017-06-13 08:29:04 -060068def IsTag(rev):
69 return rev.startswith(R_TAGS)
70
71def IsImmutable(rev):
72 return IsChange(rev) or IsId(rev) or IsTag(rev)
73
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070074def _key(name):
75 parts = name.split('.')
76 if len(parts) < 2:
77 return name.lower()
David Pursehouse54a4e602020-02-12 14:31:05 +090078 parts[0] = parts[0].lower()
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070079 parts[-1] = parts[-1].lower()
80 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081
82class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070083 _ForUser = None
84
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070085 @classmethod
86 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070087 if cls._ForUser is None:
David Pursehousee5913ae2020-02-12 13:56:59 +090088 cls._ForUser = cls(configfile=os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070089 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070090
91 @classmethod
92 def ForRepository(cls, gitdir, defaults=None):
David Pursehousee5913ae2020-02-12 13:56:59 +090093 return cls(configfile=os.path.join(gitdir, 'config'),
94 defaults=defaults)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095
Anthony King85b24ac2014-05-06 15:57:48 +010096 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090097 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098 self.defaults = defaults
99 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700100 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101 self._remotes = {}
102 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700103
Anthony King85b24ac2014-05-06 15:57:48 +0100104 self._json = jsonFile
105 if self._json is None:
106 self._json = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900107 os.path.dirname(self.file),
108 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700109
David Pursehousee5913ae2020-02-12 13:56:59 +0900110 def Has(self, name, include_defaults=True):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700111 """Return true if this configuration file has the key.
112 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700113 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114 return True
115 if include_defaults and self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900116 return self.defaults.Has(name, include_defaults=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117 return False
118
119 def GetBoolean(self, name):
120 """Returns a boolean from the configuration file.
121 None : The value was not defined, or is not a boolean.
122 True : The value was set to true or yes.
123 False: The value was set to false or no.
124 """
125 v = self.GetString(name)
126 if v is None:
127 return None
128 v = v.lower()
129 if v in ('true', 'yes'):
130 return True
131 if v in ('false', 'no'):
132 return False
133 return None
134
David Pursehouse8a68ff92012-09-24 12:15:13 +0900135 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136 """Get the first value for a key, or None if it is not defined.
137
138 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900139 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700141 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700142 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700143 except KeyError:
144 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900145 return self.defaults.GetString(name, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146 v = []
147
David Pursehouse8a68ff92012-09-24 12:15:13 +0900148 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700149 if v:
150 return v[0]
151 return None
152
153 r = []
154 r.extend(v)
155 if self.defaults:
David Pursehousee5913ae2020-02-12 13:56:59 +0900156 r.extend(self.defaults.GetString(name, all_keys=True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157 return r
158
159 def SetString(self, name, value):
160 """Set the value(s) for a key.
161 Only this configuration file is modified.
162
163 The supplied value should be either a string,
164 or a list of strings (to store multiple values).
165 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700166 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167
168 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700169 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 except KeyError:
171 old = []
172
173 if value is None:
174 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700175 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 self._do('--unset-all', name)
177
178 elif isinstance(value, list):
179 if len(value) == 0:
180 self.SetString(name, None)
181
182 elif len(value) == 1:
183 self.SetString(name, value[0])
184
185 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700186 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700187 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700188 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700189 self._do('--add', name, value[i])
190
191 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700192 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193 self._do('--replace-all', name, value)
194
195 def GetRemote(self, name):
196 """Get the remote.$name.* configuration values as an object.
197 """
198 try:
199 r = self._remotes[name]
200 except KeyError:
201 r = Remote(self, name)
202 self._remotes[r.name] = r
203 return r
204
205 def GetBranch(self, name):
206 """Get the branch.$name.* configuration values as an object.
207 """
208 try:
209 b = self._branches[name]
210 except KeyError:
211 b = Branch(self, name)
212 self._branches[b.name] = b
213 return b
214
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700215 def GetSubSections(self, section):
216 """List all subsection names matching $section.*.*
217 """
218 return self._sections.get(section, set())
219
David Pursehousee5913ae2020-02-12 13:56:59 +0900220 def HasSection(self, section, subsection=''):
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700221 """Does at least one key in section.subsection exist?
222 """
223 try:
224 return subsection in self._sections[section]
225 except KeyError:
226 return False
227
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700228 def UrlInsteadOf(self, url):
229 """Resolve any url.*.insteadof references.
230 """
231 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700232 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
233 if old_url is not None and url.startswith(old_url):
234 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700235 return url
236
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700237 @property
238 def _sections(self):
239 d = self._section_dict
240 if d is None:
241 d = {}
242 for name in self._cache.keys():
243 p = name.split('.')
244 if 2 == len(p):
245 section = p[0]
246 subsect = ''
247 else:
248 section = p[0]
249 subsect = '.'.join(p[1:-1])
250 if section not in d:
251 d[section] = set()
252 d[section].add(subsect)
253 self._section_dict = d
254 return d
255
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256 @property
257 def _cache(self):
258 if self._cache_dict is None:
259 self._cache_dict = self._Read()
260 return self._cache_dict
261
262 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100263 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700264 if d is None:
265 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100266 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700267 return d
268
Anthony King85b24ac2014-05-06 15:57:48 +0100269 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700270 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100271 if os.path.getmtime(self._json) \
David Pursehouseabdf7502020-02-12 14:58:39 +0900272 <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800273 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700274 return None
275 except OSError:
276 return None
277 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100278 Trace(': parsing %s', self.file)
Mike Frysinger3164d402019-11-11 05:40:22 -0500279 with open(self._json) as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100280 return json.load(fd)
Anthony King85b24ac2014-05-06 15:57:48 +0100281 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800282 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700283 return None
284
Anthony King85b24ac2014-05-06 15:57:48 +0100285 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700286 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500287 with open(self._json, 'w') as fd:
Anthony King85b24ac2014-05-06 15:57:48 +0100288 json.dump(cache, fd, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +0100289 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100290 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800291 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700292
293 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700294 """
295 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296
David Aguilar438c5472009-06-28 15:09:16 -0700297 This internal method populates the GitConfig cache.
298
299 """
David Aguilar438c5472009-06-28 15:09:16 -0700300 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700301 d = self._do('--null', '--list')
302 if d is None:
303 return c
Dylan Denge469a0c2018-06-23 15:02:26 +0800304 if not is_python3():
305 d = d.decode('utf-8')
306 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700307 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900308 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700309 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900310 key = line
311 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700312
313 if key in c:
314 c[key].append(val)
315 else:
316 c[key] = [val]
317
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700318 return c
319
320 def _do(self, *args):
321 command = ['config', '--file', self.file]
322 command.extend(args)
323
324 p = GitCommand(None,
325 command,
David Pursehousee5913ae2020-02-12 13:56:59 +0900326 capture_stdout=True,
327 capture_stderr=True)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700328 if p.Wait() == 0:
329 return p.stdout
330 else:
331 GitError('git config %s: %s' % (str(args), p.stderr))
332
333
334class RefSpec(object):
335 """A Git refspec line, split into its components:
336
337 forced: True if the line starts with '+'
338 src: Left side of the line
339 dst: Right side of the line
340 """
341
342 @classmethod
343 def FromString(cls, rs):
344 lhs, rhs = rs.split(':', 2)
345 if lhs.startswith('+'):
346 lhs = lhs[1:]
347 forced = True
348 else:
349 forced = False
350 return cls(forced, lhs, rhs)
351
352 def __init__(self, forced, lhs, rhs):
353 self.forced = forced
354 self.src = lhs
355 self.dst = rhs
356
357 def SourceMatches(self, rev):
358 if self.src:
359 if rev == self.src:
360 return True
361 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
362 return True
363 return False
364
365 def DestMatches(self, ref):
366 if self.dst:
367 if ref == self.dst:
368 return True
369 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
370 return True
371 return False
372
373 def MapSource(self, rev):
374 if self.src.endswith('/*'):
375 return self.dst[:-1] + rev[len(self.src) - 1:]
376 return self.dst
377
378 def __str__(self):
379 s = ''
380 if self.forced:
381 s += '+'
382 if self.src:
383 s += self.src
384 if self.dst:
385 s += ':'
386 s += self.dst
387 return s
388
389
Doug Anderson06d029c2010-10-27 17:06:01 -0700390_master_processes = []
391_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700392_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800393_master_keys_lock = None
394
395def init_ssh():
396 """Should be called once at the start of repo to init ssh master handling.
397
398 At the moment, all we do is to create our lock.
399 """
400 global _master_keys_lock
401 assert _master_keys_lock is None, "Should only call init_ssh once"
402 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700403
Josh Guilfoyle71985722009-08-16 09:44:40 -0700404def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700405 global _ssh_master
406
Doug Anderson0048b692010-12-21 13:39:23 -0800407 # Acquire the lock. This is needed to prevent opening multiple masters for
408 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
409 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
410 # one that was passed to repo init.
411 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700412 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700413
Doug Anderson0048b692010-12-21 13:39:23 -0800414 # Check to see whether we already think that the master is running; if we
415 # think it's already running, return right away.
416 if port is not None:
417 key = '%s:%s' % (host, port)
418 else:
419 key = host
420
421 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700422 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700423
Doug Anderson0048b692010-12-21 13:39:23 -0800424 if not _ssh_master \
David Pursehouseabdf7502020-02-12 14:58:39 +0900425 or 'GIT_SSH' in os.environ \
426 or sys.platform in ('win32', 'cygwin'):
Doug Anderson0048b692010-12-21 13:39:23 -0800427 # failed earlier, or cygwin ssh can't do this
428 #
429 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700430
Doug Anderson0048b692010-12-21 13:39:23 -0800431 # We will make two calls to ssh; this is the common part of both calls.
432 command_base = ['ssh',
David Pursehouseabdf7502020-02-12 14:58:39 +0900433 '-o', 'ControlPath %s' % ssh_sock(),
434 host]
Doug Anderson0048b692010-12-21 13:39:23 -0800435 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900436 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800437
438 # Since the key wasn't in _master_keys, we think that master isn't running.
439 # ...but before actually starting a master, we'll double-check. This can
440 # be important because we can't tell that that 'git@myhost.com' is the same
441 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
David Pursehouse54a4e602020-02-12 14:31:05 +0900442 check_command = command_base + ['-O', 'check']
Doug Anderson0048b692010-12-21 13:39:23 -0800443 try:
444 Trace(': %s', ' '.join(check_command))
445 check_process = subprocess.Popen(check_command,
446 stdout=subprocess.PIPE,
447 stderr=subprocess.PIPE)
David Pursehouse54a4e602020-02-12 14:31:05 +0900448 check_process.communicate() # read output, but ignore it...
Doug Anderson0048b692010-12-21 13:39:23 -0800449 isnt_running = check_process.wait()
450
451 if not isnt_running:
452 # Our double-check found that the master _was_ infact running. Add to
453 # the list of keys.
454 _master_keys.add(key)
455 return True
456 except Exception:
457 # Ignore excpetions. We we will fall back to the normal command and print
458 # to the log there.
459 pass
460
David Pursehouse0ab95ba2020-02-12 15:01:59 +0900461 command = command_base[:1] + ['-M', '-N'] + command_base[1:]
Doug Anderson0048b692010-12-21 13:39:23 -0800462 try:
463 Trace(': %s', ' '.join(command))
464 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700465 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800466 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700467 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
David Pursehouseabdf7502020-02-12 14:58:39 +0900468 % (host, port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800469 return False
470
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200471 time.sleep(1)
472 ssh_died = (p.poll() is not None)
473 if ssh_died:
474 return False
475
Doug Anderson0048b692010-12-21 13:39:23 -0800476 _master_processes.append(p)
477 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800478 return True
479 finally:
480 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700481
482def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800483 global _master_keys_lock
484
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700485 terminate_ssh_clients()
486
Doug Anderson06d029c2010-10-27 17:06:01 -0700487 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700488 try:
489 os.kill(p.pid, SIGTERM)
490 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700491 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700492 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700493 del _master_processes[:]
494 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700495
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700496 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700497 if d:
498 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700499 platform_utils.rmdir(os.path.dirname(d))
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700500 except OSError:
501 pass
502
Doug Anderson0048b692010-12-21 13:39:23 -0800503 # We're done with the lock, so we can delete it.
504 _master_keys_lock = None
505
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700506URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700507URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700508
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700509def GetSchemeFromUrl(url):
510 m = URI_ALL.match(url)
511 if m:
512 return m.group(1)
513 return None
514
Dan Willemsen0745bb22015-08-17 13:41:45 -0700515@contextlib.contextmanager
516def GetUrlCookieFile(url, quiet):
517 if url.startswith('persistent-'):
518 try:
519 p = subprocess.Popen(
520 ['git-remote-persistent-https', '-print_config', url],
521 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
522 stderr=subprocess.PIPE)
523 try:
524 cookieprefix = 'http.cookiefile='
525 proxyprefix = 'http.proxy='
526 cookiefile = None
527 proxy = None
528 for line in p.stdout:
Mike Frysingerded477d2020-02-07 23:18:23 -0500529 line = line.strip().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700530 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900531 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700532 if line.startswith(proxyprefix):
533 proxy = line[len(proxyprefix):]
534 # Leave subprocess open, as cookie file may be transient.
535 if cookiefile or proxy:
536 yield cookiefile, proxy
537 return
538 finally:
539 p.stdin.close()
540 if p.wait():
Mike Frysingerded477d2020-02-07 23:18:23 -0500541 err_msg = p.stderr.read().decode('utf-8')
Dan Willemsen0745bb22015-08-17 13:41:45 -0700542 if ' -print_config' in err_msg:
543 pass # Persistent proxy doesn't support -print_config.
544 elif not quiet:
545 print(err_msg, file=sys.stderr)
546 except OSError as e:
547 if e.errno == errno.ENOENT:
548 pass # No persistent proxy.
549 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900550 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
551 if cookiefile:
552 cookiefile = os.path.expanduser(cookiefile)
553 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700554
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700555def _preconnect(url):
556 m = URI_ALL.match(url)
557 if m:
558 scheme = m.group(1)
559 host = m.group(2)
560 if ':' in host:
561 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700562 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700563 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700564 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
565 return _open_ssh(host, port)
566 return False
567
568 m = URI_SCP.match(url)
569 if m:
570 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700571 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700572
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700573 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700574
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700575class Remote(object):
576 """Configuration options related to a remote.
577 """
578 def __init__(self, config, name):
579 self._config = config
580 self.name = name
581 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700582 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800584 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530585 self.fetch = list(map(RefSpec.FromString,
David Pursehouseabdf7502020-02-12 14:58:39 +0900586 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800587 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800588
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100589 def _InsteadOf(self):
590 globCfg = GitConfig.ForUser()
591 urlList = globCfg.GetSubSections('url')
592 longest = ""
593 longestUrl = ""
594
595 for url in urlList:
596 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900597 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100598
599 for insteadOf in insteadOfList:
600 if self.url.startswith(insteadOf) \
David Pursehouseabdf7502020-02-12 14:58:39 +0900601 and len(insteadOf) > len(longest):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100602 longest = insteadOf
603 longestUrl = url
604
605 if len(longest) == 0:
606 return self.url
607
608 return self.url.replace(longest, longestUrl, 1)
609
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700610 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100611 connectionUrl = self._InsteadOf()
612 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700613
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200614 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800615 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800616 if self.review is None:
617 return None
618
619 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700620 if u.startswith('persistent-'):
621 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100622 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800623 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700624 if u.endswith('/Gerrit'):
625 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800626 if u.endswith('/ssh_info'):
627 u = u[:len(u) - len('/ssh_info')]
628 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900629 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800630 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800631
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700632 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800633 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700634 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800635 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
636 self._review_url = self._SshReviewUrl(userEmail, host, port)
637 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100638 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800639 self._review_url = u # Assume it's right
640 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200641 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
642 self._review_url = http_url
643 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700644 else:
645 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800646 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200647 if not validate_certs:
648 context = ssl._create_unverified_context()
649 info = urllib.request.urlopen(info_url, context=context).read()
650 else:
651 info = urllib.request.urlopen(info_url).read()
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400652 if info == b'NOT_AVAILABLE' or b'<' in info:
Conley Owens745a39b2013-06-05 13:16:18 -0700653 # If `info` contains '<', we assume the server gave us some sort
654 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700655 #
Conley Owens745a39b2013-06-05 13:16:18 -0700656 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800657 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700658 else:
Mike Frysinger1b9adab2019-07-04 17:54:54 -0400659 info = info.decode('utf-8')
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800660 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000661 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700662 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800663 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700664 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700665 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900666 except HTTPException as e:
667 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800668
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800669 REVIEW_CACHE[u] = self._review_url
670 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800671
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800672 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700673 username = self._config.GetString('review.%s.username' % self.review)
674 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800675 username = userEmail.split('@')[0]
676 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700677
678 def ToLocal(self, rev):
679 """Convert a remote revision string to something we have locally.
680 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200681 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700683
684 if not rev.startswith('refs/'):
685 rev = R_HEADS + rev
686
687 for spec in self.fetch:
688 if spec.SourceMatches(rev):
689 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600690
691 if not rev.startswith(R_HEADS):
692 return rev
693
Mike Frysinger1f2462e2019-08-03 01:57:09 -0400694 raise GitError('%s: remote %s does not have %s' %
695 (self.projectname, self.name, rev))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696
697 def WritesTo(self, ref):
698 """True if the remote stores to the tracking ref.
699 """
700 for spec in self.fetch:
701 if spec.DestMatches(ref):
702 return True
703 return False
704
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800705 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 """Set the fetch refspec to its default value.
707 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800708 if mirror:
709 dst = 'refs/heads/*'
710 else:
711 dst = 'refs/remotes/%s/*' % self.name
712 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713
714 def Save(self):
715 """Save this remote to the configuration.
716 """
717 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700718 if self.pushUrl is not None:
719 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
720 else:
721 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800723 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530724 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725
726 def _Set(self, key, value):
727 key = 'remote.%s.%s' % (self.name, key)
728 return self._config.SetString(key, value)
729
David Pursehouse8a68ff92012-09-24 12:15:13 +0900730 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731 key = 'remote.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900732 return self._config.GetString(key, all_keys=all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700733
734
735class Branch(object):
736 """Configuration options related to a single branch.
737 """
738 def __init__(self, config, name):
739 self._config = config
740 self.name = name
741 self.merge = self._Get('merge')
742
743 r = self._Get('remote')
744 if r:
745 self.remote = self._config.GetRemote(r)
746 else:
747 self.remote = None
748
749 @property
750 def LocalMerge(self):
751 """Convert the merge spec to a local name.
752 """
753 if self.remote and self.merge:
754 return self.remote.ToLocal(self.merge)
755 return None
756
757 def Save(self):
758 """Save this branch back into the configuration.
759 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700760 if self._config.HasSection('branch', self.name):
761 if self.remote:
762 self._Set('remote', self.remote.name)
763 else:
764 self._Set('remote', None)
765 self._Set('merge', self.merge)
766
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 else:
Mike Frysinger3164d402019-11-11 05:40:22 -0500768 with open(self._config.file, 'a') as fd:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700769 fd.write('[branch "%s"]\n' % self.name)
770 if self.remote:
771 fd.write('\tremote = %s\n' % self.remote.name)
772 if self.merge:
773 fd.write('\tmerge = %s\n' % self.merge)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700774
775 def _Set(self, key, value):
776 key = 'branch.%s.%s' % (self.name, key)
777 return self._config.SetString(key, value)
778
David Pursehouse8a68ff92012-09-24 12:15:13 +0900779 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700780 key = 'branch.%s.%s' % (self.name, key)
David Pursehousee5913ae2020-02-12 13:56:59 +0900781 return self._config.GetString(key, all_keys=all_keys)