blob: 3ba9dbd1cd3a9e1feb9b0198700a93b8c758912c [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001#
2# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
Sarah Owenscecd1d82012-11-01 22:59:27 -070016from __future__ import print_function
Chirayu Desai217ea7d2013-03-01 19:14:38 +053017
Dan Willemsen0745bb22015-08-17 13:41:45 -070018import contextlib
19import errno
Anthony King85b24ac2014-05-06 15:57:48 +010020import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import os
22import re
Łukasz Gardońbed59ce2017-08-08 10:18:11 +020023import ssl
Shawn O. Pearcefb231612009-04-10 18:53:46 -070024import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Doug Anderson0048b692010-12-21 13:39:23 -080026try:
27 import threading as _threading
28except ImportError:
29 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070030import time
David Pursehouse59bbb582013-05-17 10:49:33 +090031
32from pyversion import is_python3
33if is_python3():
Sarah Owens1f7627f2012-10-31 09:21:55 -070034 import urllib.request
35 import urllib.error
36else:
David Pursehouse59bbb582013-05-17 10:49:33 +090037 import urllib2
Sarah Owens1f7627f2012-10-31 09:21:55 -070038 import imp
39 urllib = imp.new_module('urllib')
40 urllib.request = urllib2
41 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070042
Shawn O. Pearcefb231612009-04-10 18:53:46 -070043from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080044from error import GitError, UploadError
Renaud Paquay010fed72016-11-11 14:25:29 -080045import platform_utils
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070046from trace import Trace
David Pursehouseecf8f2b2013-05-24 12:12:23 +090047if is_python3():
48 from http.client import HTTPException
49else:
50 from httplib import HTTPException
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070051
52from git_command import GitCommand
53from git_command import ssh_sock
54from git_command import terminate_ssh_clients
Zac Livingston9ead97b2017-06-13 08:29:04 -060055from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056
David Pursehouse1d947b32012-10-25 12:23:11 +090057ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058
Shawn O. Pearce146fe902009-03-25 14:06:43 -070059REVIEW_CACHE = dict()
60
Zac Livingston9ead97b2017-06-13 08:29:04 -060061def IsChange(rev):
62 return rev.startswith(R_CHANGES)
63
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064def IsId(rev):
65 return ID_RE.match(rev)
66
Zac Livingston9ead97b2017-06-13 08:29:04 -060067def IsTag(rev):
68 return rev.startswith(R_TAGS)
69
70def IsImmutable(rev):
71 return IsChange(rev) or IsId(rev) or IsTag(rev)
72
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070073def _key(name):
74 parts = name.split('.')
75 if len(parts) < 2:
76 return name.lower()
77 parts[ 0] = parts[ 0].lower()
78 parts[-1] = parts[-1].lower()
79 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070080
81class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070082 _ForUser = None
83
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070084 @classmethod
85 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070086 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090087 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070088 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070089
90 @classmethod
91 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090092 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093 defaults = defaults)
94
Anthony King85b24ac2014-05-06 15:57:48 +010095 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090096 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070097 self.defaults = defaults
98 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070099 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700100 self._remotes = {}
101 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700102
Anthony King85b24ac2014-05-06 15:57:48 +0100103 self._json = jsonFile
104 if self._json is None:
105 self._json = os.path.join(
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700106 os.path.dirname(self.file),
Anthony King85b24ac2014-05-06 15:57:48 +0100107 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108
109 def Has(self, name, include_defaults = True):
110 """Return true if this configuration file has the key.
111 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700112 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700113 return True
114 if include_defaults and self.defaults:
115 return self.defaults.Has(name, include_defaults = True)
116 return False
117
118 def GetBoolean(self, name):
119 """Returns a boolean from the configuration file.
120 None : The value was not defined, or is not a boolean.
121 True : The value was set to true or yes.
122 False: The value was set to false or no.
123 """
124 v = self.GetString(name)
125 if v is None:
126 return None
127 v = v.lower()
128 if v in ('true', 'yes'):
129 return True
130 if v in ('false', 'no'):
131 return False
132 return None
133
David Pursehouse8a68ff92012-09-24 12:15:13 +0900134 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135 """Get the first value for a key, or None if it is not defined.
136
137 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900138 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700141 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142 except KeyError:
143 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900144 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700145 v = []
146
David Pursehouse8a68ff92012-09-24 12:15:13 +0900147 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700148 if v:
149 return v[0]
150 return None
151
152 r = []
153 r.extend(v)
154 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900155 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700156 return r
157
158 def SetString(self, name, value):
159 """Set the value(s) for a key.
160 Only this configuration file is modified.
161
162 The supplied value should be either a string,
163 or a list of strings (to store multiple values).
164 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700165 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166
167 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700168 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169 except KeyError:
170 old = []
171
172 if value is None:
173 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700174 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175 self._do('--unset-all', name)
176
177 elif isinstance(value, list):
178 if len(value) == 0:
179 self.SetString(name, None)
180
181 elif len(value) == 1:
182 self.SetString(name, value[0])
183
184 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700185 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700187 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700188 self._do('--add', name, value[i])
189
190 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700191 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700192 self._do('--replace-all', name, value)
193
194 def GetRemote(self, name):
195 """Get the remote.$name.* configuration values as an object.
196 """
197 try:
198 r = self._remotes[name]
199 except KeyError:
200 r = Remote(self, name)
201 self._remotes[r.name] = r
202 return r
203
204 def GetBranch(self, name):
205 """Get the branch.$name.* configuration values as an object.
206 """
207 try:
208 b = self._branches[name]
209 except KeyError:
210 b = Branch(self, name)
211 self._branches[b.name] = b
212 return b
213
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700214 def GetSubSections(self, section):
215 """List all subsection names matching $section.*.*
216 """
217 return self._sections.get(section, set())
218
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700219 def HasSection(self, section, subsection = ''):
220 """Does at least one key in section.subsection exist?
221 """
222 try:
223 return subsection in self._sections[section]
224 except KeyError:
225 return False
226
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700227 def UrlInsteadOf(self, url):
228 """Resolve any url.*.insteadof references.
229 """
230 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700231 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
232 if old_url is not None and url.startswith(old_url):
233 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700234 return url
235
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700236 @property
237 def _sections(self):
238 d = self._section_dict
239 if d is None:
240 d = {}
241 for name in self._cache.keys():
242 p = name.split('.')
243 if 2 == len(p):
244 section = p[0]
245 subsect = ''
246 else:
247 section = p[0]
248 subsect = '.'.join(p[1:-1])
249 if section not in d:
250 d[section] = set()
251 d[section].add(subsect)
252 self._section_dict = d
253 return d
254
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 @property
256 def _cache(self):
257 if self._cache_dict is None:
258 self._cache_dict = self._Read()
259 return self._cache_dict
260
261 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100262 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700263 if d is None:
264 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100265 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700266 return d
267
Anthony King85b24ac2014-05-06 15:57:48 +0100268 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700269 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100270 if os.path.getmtime(self._json) \
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700271 <= os.path.getmtime(self.file):
Renaud Paquay010fed72016-11-11 14:25:29 -0800272 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700273 return None
274 except OSError:
275 return None
276 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100277 Trace(': parsing %s', self.file)
278 fd = open(self._json)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700279 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100280 return json.load(fd)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700281 finally:
282 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100283 except (IOError, ValueError):
Renaud Paquay010fed72016-11-11 14:25:29 -0800284 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700285 return None
286
Anthony King85b24ac2014-05-06 15:57:48 +0100287 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700288 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100289 fd = open(self._json, 'w')
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700290 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100291 json.dump(cache, fd, indent=2)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700292 finally:
293 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100294 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100295 if os.path.exists(self._json):
Renaud Paquay010fed72016-11-11 14:25:29 -0800296 platform_utils.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700297
298 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700299 """
300 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700301
David Aguilar438c5472009-06-28 15:09:16 -0700302 This internal method populates the GitConfig cache.
303
304 """
David Aguilar438c5472009-06-28 15:09:16 -0700305 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700306 d = self._do('--null', '--list')
307 if d is None:
308 return c
Chirayu Desai0eb35cb2013-11-19 18:46:29 +0530309 for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
310 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700311 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900312 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700313 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900314 key = line
315 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700316
317 if key in c:
318 c[key].append(val)
319 else:
320 c[key] = [val]
321
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700322 return c
323
324 def _do(self, *args):
325 command = ['config', '--file', self.file]
326 command.extend(args)
327
328 p = GitCommand(None,
329 command,
330 capture_stdout = True,
331 capture_stderr = True)
332 if p.Wait() == 0:
333 return p.stdout
334 else:
335 GitError('git config %s: %s' % (str(args), p.stderr))
336
337
338class RefSpec(object):
339 """A Git refspec line, split into its components:
340
341 forced: True if the line starts with '+'
342 src: Left side of the line
343 dst: Right side of the line
344 """
345
346 @classmethod
347 def FromString(cls, rs):
348 lhs, rhs = rs.split(':', 2)
349 if lhs.startswith('+'):
350 lhs = lhs[1:]
351 forced = True
352 else:
353 forced = False
354 return cls(forced, lhs, rhs)
355
356 def __init__(self, forced, lhs, rhs):
357 self.forced = forced
358 self.src = lhs
359 self.dst = rhs
360
361 def SourceMatches(self, rev):
362 if self.src:
363 if rev == self.src:
364 return True
365 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
366 return True
367 return False
368
369 def DestMatches(self, ref):
370 if self.dst:
371 if ref == self.dst:
372 return True
373 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
374 return True
375 return False
376
377 def MapSource(self, rev):
378 if self.src.endswith('/*'):
379 return self.dst[:-1] + rev[len(self.src) - 1:]
380 return self.dst
381
382 def __str__(self):
383 s = ''
384 if self.forced:
385 s += '+'
386 if self.src:
387 s += self.src
388 if self.dst:
389 s += ':'
390 s += self.dst
391 return s
392
393
Doug Anderson06d029c2010-10-27 17:06:01 -0700394_master_processes = []
395_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700396_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800397_master_keys_lock = None
398
399def init_ssh():
400 """Should be called once at the start of repo to init ssh master handling.
401
402 At the moment, all we do is to create our lock.
403 """
404 global _master_keys_lock
405 assert _master_keys_lock is None, "Should only call init_ssh once"
406 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700407
Josh Guilfoyle71985722009-08-16 09:44:40 -0700408def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700409 global _ssh_master
410
Doug Anderson0048b692010-12-21 13:39:23 -0800411 # Acquire the lock. This is needed to prevent opening multiple masters for
412 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
413 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
414 # one that was passed to repo init.
415 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700416 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700417
Doug Anderson0048b692010-12-21 13:39:23 -0800418 # Check to see whether we already think that the master is running; if we
419 # think it's already running, return right away.
420 if port is not None:
421 key = '%s:%s' % (host, port)
422 else:
423 key = host
424
425 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700426 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700427
Doug Anderson0048b692010-12-21 13:39:23 -0800428 if not _ssh_master \
429 or 'GIT_SSH' in os.environ \
430 or sys.platform in ('win32', 'cygwin'):
431 # failed earlier, or cygwin ssh can't do this
432 #
433 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700434
Doug Anderson0048b692010-12-21 13:39:23 -0800435 # We will make two calls to ssh; this is the common part of both calls.
436 command_base = ['ssh',
437 '-o','ControlPath %s' % ssh_sock(),
438 host]
439 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900440 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800441
442 # Since the key wasn't in _master_keys, we think that master isn't running.
443 # ...but before actually starting a master, we'll double-check. This can
444 # be important because we can't tell that that 'git@myhost.com' is the same
445 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
446 check_command = command_base + ['-O','check']
447 try:
448 Trace(': %s', ' '.join(check_command))
449 check_process = subprocess.Popen(check_command,
450 stdout=subprocess.PIPE,
451 stderr=subprocess.PIPE)
452 check_process.communicate() # read output, but ignore it...
453 isnt_running = check_process.wait()
454
455 if not isnt_running:
456 # Our double-check found that the master _was_ infact running. Add to
457 # the list of keys.
458 _master_keys.add(key)
459 return True
460 except Exception:
461 # Ignore excpetions. We we will fall back to the normal command and print
462 # to the log there.
463 pass
464
465 command = command_base[:1] + \
466 ['-M', '-N'] + \
467 command_base[1:]
468 try:
469 Trace(': %s', ' '.join(command))
470 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700471 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800472 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700473 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
474 % (host,port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800475 return False
476
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200477 time.sleep(1)
478 ssh_died = (p.poll() is not None)
479 if ssh_died:
480 return False
481
Doug Anderson0048b692010-12-21 13:39:23 -0800482 _master_processes.append(p)
483 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800484 return True
485 finally:
486 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700487
488def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800489 global _master_keys_lock
490
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700491 terminate_ssh_clients()
492
Doug Anderson06d029c2010-10-27 17:06:01 -0700493 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700494 try:
495 os.kill(p.pid, SIGTERM)
496 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700497 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700498 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700499 del _master_processes[:]
500 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700501
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700502 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700503 if d:
504 try:
505 os.rmdir(os.path.dirname(d))
506 except OSError:
507 pass
508
Doug Anderson0048b692010-12-21 13:39:23 -0800509 # We're done with the lock, so we can delete it.
510 _master_keys_lock = None
511
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700512URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700513URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700514
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700515def GetSchemeFromUrl(url):
516 m = URI_ALL.match(url)
517 if m:
518 return m.group(1)
519 return None
520
Dan Willemsen0745bb22015-08-17 13:41:45 -0700521@contextlib.contextmanager
522def GetUrlCookieFile(url, quiet):
523 if url.startswith('persistent-'):
524 try:
525 p = subprocess.Popen(
526 ['git-remote-persistent-https', '-print_config', url],
527 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
528 stderr=subprocess.PIPE)
529 try:
530 cookieprefix = 'http.cookiefile='
531 proxyprefix = 'http.proxy='
532 cookiefile = None
533 proxy = None
534 for line in p.stdout:
535 line = line.strip()
536 if line.startswith(cookieprefix):
537 cookiefile = line[len(cookieprefix):]
538 if line.startswith(proxyprefix):
539 proxy = line[len(proxyprefix):]
540 # Leave subprocess open, as cookie file may be transient.
541 if cookiefile or proxy:
542 yield cookiefile, proxy
543 return
544 finally:
545 p.stdin.close()
546 if p.wait():
547 err_msg = p.stderr.read()
548 if ' -print_config' in err_msg:
549 pass # Persistent proxy doesn't support -print_config.
550 elif not quiet:
551 print(err_msg, file=sys.stderr)
552 except OSError as e:
553 if e.errno == errno.ENOENT:
554 pass # No persistent proxy.
555 raise
556 yield GitConfig.ForUser().GetString('http.cookiefile'), None
557
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700558def _preconnect(url):
559 m = URI_ALL.match(url)
560 if m:
561 scheme = m.group(1)
562 host = m.group(2)
563 if ':' in host:
564 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700565 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700566 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700567 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
568 return _open_ssh(host, port)
569 return False
570
571 m = URI_SCP.match(url)
572 if m:
573 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700574 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700575
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700576 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700577
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700578class Remote(object):
579 """Configuration options related to a remote.
580 """
581 def __init__(self, config, name):
582 self._config = config
583 self.name = name
584 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700585 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700586 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800587 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530588 self.fetch = list(map(RefSpec.FromString,
589 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800590 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800591
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100592 def _InsteadOf(self):
593 globCfg = GitConfig.ForUser()
594 urlList = globCfg.GetSubSections('url')
595 longest = ""
596 longestUrl = ""
597
598 for url in urlList:
599 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900600 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100601
602 for insteadOf in insteadOfList:
603 if self.url.startswith(insteadOf) \
604 and len(insteadOf) > len(longest):
605 longest = insteadOf
606 longestUrl = url
607
608 if len(longest) == 0:
609 return self.url
610
611 return self.url.replace(longest, longestUrl, 1)
612
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700613 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100614 connectionUrl = self._InsteadOf()
615 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700616
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200617 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800618 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800619 if self.review is None:
620 return None
621
622 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700623 if u.startswith('persistent-'):
624 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100625 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800626 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700627 if u.endswith('/Gerrit'):
628 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800629 if u.endswith('/ssh_info'):
630 u = u[:len(u) - len('/ssh_info')]
631 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900632 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800633 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800634
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700635 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800636 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700637 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800638 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
639 self._review_url = self._SshReviewUrl(userEmail, host, port)
640 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100641 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800642 self._review_url = u # Assume it's right
643 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200644 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
645 self._review_url = http_url
646 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700647 else:
648 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800649 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200650 if not validate_certs:
651 context = ssl._create_unverified_context()
652 info = urllib.request.urlopen(info_url, context=context).read()
653 else:
654 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700655 if info == 'NOT_AVAILABLE' or '<' in info:
656 # If `info` contains '<', we assume the server gave us some sort
657 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700658 #
Conley Owens745a39b2013-06-05 13:16:18 -0700659 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800660 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700661 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800662 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000663 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700664 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800665 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700666 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700667 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900668 except HTTPException as e:
669 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800670
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800671 REVIEW_CACHE[u] = self._review_url
672 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800673
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800674 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700675 username = self._config.GetString('review.%s.username' % self.review)
676 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800677 username = userEmail.split('@')[0]
678 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679
680 def ToLocal(self, rev):
681 """Convert a remote revision string to something we have locally.
682 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200683 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685
686 if not rev.startswith('refs/'):
687 rev = R_HEADS + rev
688
689 for spec in self.fetch:
690 if spec.SourceMatches(rev):
691 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600692
693 if not rev.startswith(R_HEADS):
694 return rev
695
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 raise GitError('remote %s does not have %s' % (self.name, rev))
697
698 def WritesTo(self, ref):
699 """True if the remote stores to the tracking ref.
700 """
701 for spec in self.fetch:
702 if spec.DestMatches(ref):
703 return True
704 return False
705
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800706 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700707 """Set the fetch refspec to its default value.
708 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800709 if mirror:
710 dst = 'refs/heads/*'
711 else:
712 dst = 'refs/remotes/%s/*' % self.name
713 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700714
715 def Save(self):
716 """Save this remote to the configuration.
717 """
718 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700719 if self.pushUrl is not None:
720 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
721 else:
722 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700723 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800724 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530725 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700726
727 def _Set(self, key, value):
728 key = 'remote.%s.%s' % (self.name, key)
729 return self._config.SetString(key, value)
730
David Pursehouse8a68ff92012-09-24 12:15:13 +0900731 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700732 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900733 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700734
735
736class Branch(object):
737 """Configuration options related to a single branch.
738 """
739 def __init__(self, config, name):
740 self._config = config
741 self.name = name
742 self.merge = self._Get('merge')
743
744 r = self._Get('remote')
745 if r:
746 self.remote = self._config.GetRemote(r)
747 else:
748 self.remote = None
749
750 @property
751 def LocalMerge(self):
752 """Convert the merge spec to a local name.
753 """
754 if self.remote and self.merge:
755 return self.remote.ToLocal(self.merge)
756 return None
757
758 def Save(self):
759 """Save this branch back into the configuration.
760 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700761 if self._config.HasSection('branch', self.name):
762 if self.remote:
763 self._Set('remote', self.remote.name)
764 else:
765 self._Set('remote', None)
766 self._Set('merge', self.merge)
767
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700768 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530769 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700770 try:
771 fd.write('[branch "%s"]\n' % self.name)
772 if self.remote:
773 fd.write('\tremote = %s\n' % self.remote.name)
774 if self.merge:
775 fd.write('\tmerge = %s\n' % self.merge)
776 finally:
777 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778
779 def _Set(self, key, value):
780 key = 'branch.%s.%s' % (self.name, key)
781 return self._config.SetString(key, value)
782
David Pursehouse8a68ff92012-09-24 12:15:13 +0900783 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900785 return self._config.GetString(key, all_keys = all_keys)