blob: 854b2387ada101461ce05d3e8b4753aadb011924 [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):
Daichi Ueurace7e0262018-02-26 08:49:36 +0900537 cookiefile = os.path.expanduser(line[len(cookieprefix):])
Dan Willemsen0745bb22015-08-17 13:41:45 -0700538 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
Daichi Ueurace7e0262018-02-26 08:49:36 +0900556 cookiefile = GitConfig.ForUser().GetString('http.cookiefile')
557 if cookiefile:
558 cookiefile = os.path.expanduser(cookiefile)
559 yield cookiefile, None
Dan Willemsen0745bb22015-08-17 13:41:45 -0700560
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700561def _preconnect(url):
562 m = URI_ALL.match(url)
563 if m:
564 scheme = m.group(1)
565 host = m.group(2)
566 if ':' in host:
567 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700568 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700569 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700570 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
571 return _open_ssh(host, port)
572 return False
573
574 m = URI_SCP.match(url)
575 if m:
576 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700577 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700578
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700579 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700580
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581class Remote(object):
582 """Configuration options related to a remote.
583 """
584 def __init__(self, config, name):
585 self._config = config
586 self.name = name
587 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700588 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700589 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800590 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530591 self.fetch = list(map(RefSpec.FromString,
592 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800593 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800594
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100595 def _InsteadOf(self):
596 globCfg = GitConfig.ForUser()
597 urlList = globCfg.GetSubSections('url')
598 longest = ""
599 longestUrl = ""
600
601 for url in urlList:
602 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900603 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100604
605 for insteadOf in insteadOfList:
606 if self.url.startswith(insteadOf) \
607 and len(insteadOf) > len(longest):
608 longest = insteadOf
609 longestUrl = url
610
611 if len(longest) == 0:
612 return self.url
613
614 return self.url.replace(longest, longestUrl, 1)
615
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700616 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100617 connectionUrl = self._InsteadOf()
618 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700619
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200620 def ReviewUrl(self, userEmail, validate_certs):
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800621 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800622 if self.review is None:
623 return None
624
625 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700626 if u.startswith('persistent-'):
627 u = u[len('persistent-'):]
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100628 if u.split(':')[0] not in ('http', 'https', 'sso', 'ssh'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800629 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700630 if u.endswith('/Gerrit'):
631 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800632 if u.endswith('/ssh_info'):
633 u = u[:len(u) - len('/ssh_info')]
634 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900635 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800636 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800637
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700638 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800639 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700640 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800641 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
642 self._review_url = self._SshReviewUrl(userEmail, host, port)
643 REVIEW_CACHE[u] = self._review_url
Christian Koestlin2ec2a5d2016-12-05 20:32:45 +0100644 elif u.startswith('sso:') or u.startswith('ssh:'):
Steve Pucci143d8a72014-01-30 09:45:53 -0800645 self._review_url = u # Assume it's right
646 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200647 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
648 self._review_url = http_url
649 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700650 else:
651 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800652 info_url = u + 'ssh_info'
Łukasz Gardońbed59ce2017-08-08 10:18:11 +0200653 if not validate_certs:
654 context = ssl._create_unverified_context()
655 info = urllib.request.urlopen(info_url, context=context).read()
656 else:
657 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700658 if info == 'NOT_AVAILABLE' or '<' in info:
659 # If `info` contains '<', we assume the server gave us some sort
660 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700661 #
Conley Owens745a39b2013-06-05 13:16:18 -0700662 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800663 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700664 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800665 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000666 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700667 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800668 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700669 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700670 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900671 except HTTPException as e:
672 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800673
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800674 REVIEW_CACHE[u] = self._review_url
675 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800676
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800677 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700678 username = self._config.GetString('review.%s.username' % self.review)
679 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800680 username = userEmail.split('@')[0]
681 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700682
683 def ToLocal(self, rev):
684 """Convert a remote revision string to something we have locally.
685 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200686 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700687 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688
689 if not rev.startswith('refs/'):
690 rev = R_HEADS + rev
691
692 for spec in self.fetch:
693 if spec.SourceMatches(rev):
694 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600695
696 if not rev.startswith(R_HEADS):
697 return rev
698
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700699 raise GitError('remote %s does not have %s' % (self.name, rev))
700
701 def WritesTo(self, ref):
702 """True if the remote stores to the tracking ref.
703 """
704 for spec in self.fetch:
705 if spec.DestMatches(ref):
706 return True
707 return False
708
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800709 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700710 """Set the fetch refspec to its default value.
711 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800712 if mirror:
713 dst = 'refs/heads/*'
714 else:
715 dst = 'refs/remotes/%s/*' % self.name
716 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717
718 def Save(self):
719 """Save this remote to the configuration.
720 """
721 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700722 if self.pushUrl is not None:
723 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
724 else:
725 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700726 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800727 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530728 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700729
730 def _Set(self, key, value):
731 key = 'remote.%s.%s' % (self.name, key)
732 return self._config.SetString(key, value)
733
David Pursehouse8a68ff92012-09-24 12:15:13 +0900734 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700735 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900736 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700737
738
739class Branch(object):
740 """Configuration options related to a single branch.
741 """
742 def __init__(self, config, name):
743 self._config = config
744 self.name = name
745 self.merge = self._Get('merge')
746
747 r = self._Get('remote')
748 if r:
749 self.remote = self._config.GetRemote(r)
750 else:
751 self.remote = None
752
753 @property
754 def LocalMerge(self):
755 """Convert the merge spec to a local name.
756 """
757 if self.remote and self.merge:
758 return self.remote.ToLocal(self.merge)
759 return None
760
761 def Save(self):
762 """Save this branch back into the configuration.
763 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700764 if self._config.HasSection('branch', self.name):
765 if self.remote:
766 self._Set('remote', self.remote.name)
767 else:
768 self._Set('remote', None)
769 self._Set('merge', self.merge)
770
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700771 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530772 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700773 try:
774 fd.write('[branch "%s"]\n' % self.name)
775 if self.remote:
776 fd.write('\tremote = %s\n' % self.remote.name)
777 if self.merge:
778 fd.write('\tmerge = %s\n' % self.merge)
779 finally:
780 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700781
782 def _Set(self, key, value):
783 key = 'branch.%s.%s' % (self.name, key)
784 return self._config.SetString(key, value)
785
David Pursehouse8a68ff92012-09-24 12:15:13 +0900786 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700787 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900788 return self._config.GetString(key, all_keys = all_keys)