blob: ca1282a36ff60617c9c22db2cf929f7890b4f78f [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
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070047from 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()
78 parts[ 0] = parts[ 0].lower()
79 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 Pursehouse8a68ff92012-09-24 12:15:13 +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 Pursehouse8a68ff92012-09-24 12:15:13 +090093 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070094 defaults = defaults)
95
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(
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700107 os.path.dirname(self.file),
Anthony King85b24ac2014-05-06 15:57:48 +0100108 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700109
110 def Has(self, name, include_defaults = True):
111 """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:
116 return self.defaults.Has(name, include_defaults = True)
117 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 Pursehouse8a68ff92012-09-24 12:15:13 +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 Pursehouse8a68ff92012-09-24 12:15:13 +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
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700220 def HasSection(self, section, subsection = ''):
221 """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) \
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700272 <= 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)
279 fd = open(self._json)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700280 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100281 return json.load(fd)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700282 finally:
283 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100284 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800285 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700286 return None
287
Anthony King85b24ac2014-05-06 15:57:48 +0100288 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700289 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100290 fd = open(self._json, 'w')
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700291 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100292 json.dump(cache, fd, indent=2)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700293 finally:
294 fd.close()
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,
332 capture_stdout = True,
333 capture_stderr = True)
334 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
401def init_ssh():
402 """Should be called once at the start of repo to init ssh master handling.
403
404 At the moment, all we do is to create our lock.
405 """
406 global _master_keys_lock
407 assert _master_keys_lock is None, "Should only call init_ssh once"
408 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700409
Josh Guilfoyle71985722009-08-16 09:44:40 -0700410def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700411 global _ssh_master
412
Doug Anderson0048b692010-12-21 13:39:23 -0800413 # Acquire the lock. This is needed to prevent opening multiple masters for
414 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
415 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
416 # one that was passed to repo init.
417 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700418 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700419
Doug Anderson0048b692010-12-21 13:39:23 -0800420 # Check to see whether we already think that the master is running; if we
421 # think it's already running, return right away.
422 if port is not None:
423 key = '%s:%s' % (host, port)
424 else:
425 key = host
426
427 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700428 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700429
Doug Anderson0048b692010-12-21 13:39:23 -0800430 if not _ssh_master \
431 or 'GIT_SSH' in os.environ \
432 or sys.platform in ('win32', 'cygwin'):
433 # failed earlier, or cygwin ssh can't do this
434 #
435 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700436
Doug Anderson0048b692010-12-21 13:39:23 -0800437 # We will make two calls to ssh; this is the common part of both calls.
438 command_base = ['ssh',
439 '-o','ControlPath %s' % ssh_sock(),
440 host]
441 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900442 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800443
444 # Since the key wasn't in _master_keys, we think that master isn't running.
445 # ...but before actually starting a master, we'll double-check. This can
446 # be important because we can't tell that that 'git@myhost.com' is the same
447 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
448 check_command = command_base + ['-O','check']
449 try:
450 Trace(': %s', ' '.join(check_command))
451 check_process = subprocess.Popen(check_command,
452 stdout=subprocess.PIPE,
453 stderr=subprocess.PIPE)
454 check_process.communicate() # read output, but ignore it...
455 isnt_running = check_process.wait()
456
457 if not isnt_running:
458 # Our double-check found that the master _was_ infact running. Add to
459 # the list of keys.
460 _master_keys.add(key)
461 return True
462 except Exception:
463 # Ignore excpetions. We we will fall back to the normal command and print
464 # to the log there.
465 pass
466
467 command = command_base[:1] + \
468 ['-M', '-N'] + \
469 command_base[1:]
470 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'
476 % (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
490def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800491 global _master_keys_lock
492
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700493 terminate_ssh_clients()
494
Doug Anderson06d029c2010-10-27 17:06:01 -0700495 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700496 try:
497 os.kill(p.pid, SIGTERM)
498 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700499 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700500 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700501 del _master_processes[:]
502 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700503
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700504 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700505 if d:
506 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700507 platform_utils.rmdir(os.path.dirname(d))
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700508 except OSError:
509 pass
510
Doug Anderson0048b692010-12-21 13:39:23 -0800511 # We're done with the lock, so we can delete it.
512 _master_keys_lock = None
513
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700514URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700515URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700516
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700517def GetSchemeFromUrl(url):
518 m = URI_ALL.match(url)
519 if m:
520 return m.group(1)
521 return None
522
Dan Willemsen0745bb22015-08-17 13:41:45 -0700523@contextlib.contextmanager
524def GetUrlCookieFile(url, quiet):
525 if url.startswith('persistent-'):
526 try:
527 p = subprocess.Popen(
528 ['git-remote-persistent-https', '-print_config', url],
529 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
530 stderr=subprocess.PIPE)
531 try:
532 cookieprefix = 'http.cookiefile='
533 proxyprefix = 'http.proxy='
534 cookiefile = None
535 proxy = None
536 for line in p.stdout:
537 line = line.strip()
538 if line.startswith(cookieprefix):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900539 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700540 if line.startswith(proxyprefix):
541 proxy = line[len(proxyprefix):]
542 # Leave subprocess open, as cookie file may be transient.
543 if cookiefile or proxy:
544 yield cookiefile, proxy
545 return
546 finally:
547 p.stdin.close()
548 if p.wait():
549 err_msg = p.stderr.read()
550 if ' -print_config' in err_msg:
551 pass # Persistent proxy doesn't support -print_config.
552 elif not quiet:
553 print(err_msg, file=sys.stderr)
554 except OSError as e:
555 if e.errno == errno.ENOENT:
556 pass # No persistent proxy.
557 raise
Daichi Ueurace7e0262018-02-26 08:49:36 +0900558 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
559 if cookiefile:
560 cookiefile = os.path.expanduser(cookiefile)
561 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700562
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700563def _preconnect(url):
564 m = URI_ALL.match(url)
565 if m:
566 scheme = m.group(1)
567 host = m.group(2)
568 if ':' in host:
569 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700570 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700571 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700572 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
573 return _open_ssh(host, port)
574 return False
575
576 m = URI_SCP.match(url)
577 if m:
578 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700579 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700580
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700581 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700582
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700583class Remote(object):
584 """Configuration options related to a remote.
585 """
586 def __init__(self, config, name):
587 self._config = config
588 self.name = name
589 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700590 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700591 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800592 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530593 self.fetch = list(map(RefSpec.FromString,
594 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800595 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800596
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100597 def _InsteadOf(self):
598 globCfg = GitConfig.ForUser()
599 urlList = globCfg.GetSubSections('url')
600 longest = ""
601 longestUrl = ""
602
603 for url in urlList:
604 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900605 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100606
607 for insteadOf in insteadOfList:
608 if self.url.startswith(insteadOf) \
609 and len(insteadOf) > len(longest):
610 longest = insteadOf
611 longestUrl = url
612
613 if len(longest) == 0:
614 return self.url
615
616 return self.url.replace(longest, longestUrl, 1)
617
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700618 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100619 connectionUrl = self._InsteadOf()
620 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700621
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200622 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800623 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800624 if self.review is None:
625 return None
626
627 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700628 if u.startswith('persistent-'):
629 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100630 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800631 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700632 if u.endswith('/Gerrit'):
633 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800634 if u.endswith('/ssh_info'):
635 u = u[:len(u) - len('/ssh_info')]
636 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900637 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800638 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800639
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700640 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800641 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700642 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800643 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
644 self._review_url = self._SshReviewUrl(userEmail, host, port)
645 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100646 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800647 self._review_url = u # Assume it's right
648 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200649 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
650 self._review_url = http_url
651 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700652 else:
653 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800654 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200655 if not validate_certs:
656 context = ssl._create_unverified_context()
657 info = urllib.request.urlopen(info_url, context=context).read()
658 else:
659 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700660 if info == 'NOT_AVAILABLE' or '<' in info:
661 # If `info` contains '<', we assume the server gave us some sort
662 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700663 #
Conley Owens745a39b2013-06-05 13:16:18 -0700664 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800665 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700666 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800667 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000668 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700669 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800670 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700671 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700672 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900673 except HTTPException as e:
674 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800675
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800676 REVIEW_CACHE[u] = self._review_url
677 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800678
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800679 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700680 username = self._config.GetString('review.%s.username' % self.review)
681 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800682 username = userEmail.split('@')[0]
683 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684
685 def ToLocal(self, rev):
686 """Convert a remote revision string to something we have locally.
687 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200688 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690
691 if not rev.startswith('refs/'):
692 rev = R_HEADS + rev
693
694 for spec in self.fetch:
695 if spec.SourceMatches(rev):
696 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600697
698 if not rev.startswith(R_HEADS):
699 return rev
700
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701 raise GitError('remote %s does not have %s' % (self.name, rev))
702
703 def WritesTo(self, ref):
704 """True if the remote stores to the tracking ref.
705 """
706 for spec in self.fetch:
707 if spec.DestMatches(ref):
708 return True
709 return False
710
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800711 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 """Set the fetch refspec to its default value.
713 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800714 if mirror:
715 dst = 'refs/heads/*'
716 else:
717 dst = 'refs/remotes/%s/*' % self.name
718 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719
720 def Save(self):
721 """Save this remote to the configuration.
722 """
723 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700724 if self.pushUrl is not None:
725 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
726 else:
727 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800729 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530730 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700731
732 def _Set(self, key, value):
733 key = 'remote.%s.%s' % (self.name, key)
734 return self._config.SetString(key, value)
735
David Pursehouse8a68ff92012-09-24 12:15:13 +0900736 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900738 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700739
740
741class Branch(object):
742 """Configuration options related to a single branch.
743 """
744 def __init__(self, config, name):
745 self._config = config
746 self.name = name
747 self.merge = self._Get('merge')
748
749 r = self._Get('remote')
750 if r:
751 self.remote = self._config.GetRemote(r)
752 else:
753 self.remote = None
754
755 @property
756 def LocalMerge(self):
757 """Convert the merge spec to a local name.
758 """
759 if self.remote and self.merge:
760 return self.remote.ToLocal(self.merge)
761 return None
762
763 def Save(self):
764 """Save this branch back into the configuration.
765 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700766 if self._config.HasSection('branch', self.name):
767 if self.remote:
768 self._Set('remote', self.remote.name)
769 else:
770 self._Set('remote', None)
771 self._Set('merge', self.merge)
772
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700773 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530774 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700775 try:
776 fd.write('[branch "%s"]\n' % self.name)
777 if self.remote:
778 fd.write('\tremote = %s\n' % self.remote.name)
779 if self.merge:
780 fd.write('\tmerge = %s\n' % self.merge)
781 finally:
782 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700783
784 def _Set(self, key, value):
785 key = 'branch.%s.%s' % (self.name, key)
786 return self._config.SetString(key, value)
787
David Pursehouse8a68ff92012-09-24 12:15:13 +0900788 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700789 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900790 return self._config.GetString(key, all_keys = all_keys)