blob: c4c31e18691fd3340be5ea5732310c2f164b4c22 [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
Anthony King85b24ac2014-05-06 15:57:48 +010018import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070019import os
20import re
Shawn O. Pearcefb231612009-04-10 18:53:46 -070021import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import sys
Doug Anderson0048b692010-12-21 13:39:23 -080023try:
24 import threading as _threading
25except ImportError:
26 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070027import time
David Pursehouse59bbb582013-05-17 10:49:33 +090028
29from pyversion import is_python3
30if is_python3():
Sarah Owens1f7627f2012-10-31 09:21:55 -070031 import urllib.request
32 import urllib.error
33else:
David Pursehouse59bbb582013-05-17 10:49:33 +090034 import urllib2
Sarah Owens1f7627f2012-10-31 09:21:55 -070035 import imp
36 urllib = imp.new_module('urllib')
37 urllib.request = urllib2
38 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070039
Shawn O. Pearcefb231612009-04-10 18:53:46 -070040from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080041from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070042from trace import Trace
David Pursehouseecf8f2b2013-05-24 12:12:23 +090043if is_python3():
44 from http.client import HTTPException
45else:
46 from httplib import HTTPException
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070047
48from git_command import GitCommand
49from git_command import ssh_sock
50from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051
52R_HEADS = 'refs/heads/'
53R_TAGS = 'refs/tags/'
David Pursehouse1d947b32012-10-25 12:23:11 +090054ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055
Shawn O. Pearce146fe902009-03-25 14:06:43 -070056REVIEW_CACHE = dict()
57
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058def IsId(rev):
59 return ID_RE.match(rev)
60
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070061def _key(name):
62 parts = name.split('.')
63 if len(parts) < 2:
64 return name.lower()
65 parts[ 0] = parts[ 0].lower()
66 parts[-1] = parts[-1].lower()
67 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068
69class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070070 _ForUser = None
71
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072 @classmethod
73 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070074 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090075 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070076 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077
78 @classmethod
79 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090080 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081 defaults = defaults)
82
Anthony King85b24ac2014-05-06 15:57:48 +010083 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090084 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070085 self.defaults = defaults
86 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070087 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070088 self._remotes = {}
89 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070090
Anthony King85b24ac2014-05-06 15:57:48 +010091 self._json = jsonFile
92 if self._json is None:
93 self._json = os.path.join(
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070094 os.path.dirname(self.file),
Anthony King85b24ac2014-05-06 15:57:48 +010095 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070096
97 def Has(self, name, include_defaults = True):
98 """Return true if this configuration file has the key.
99 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700100 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700101 return True
102 if include_defaults and self.defaults:
103 return self.defaults.Has(name, include_defaults = True)
104 return False
105
106 def GetBoolean(self, name):
107 """Returns a boolean from the configuration file.
108 None : The value was not defined, or is not a boolean.
109 True : The value was set to true or yes.
110 False: The value was set to false or no.
111 """
112 v = self.GetString(name)
113 if v is None:
114 return None
115 v = v.lower()
116 if v in ('true', 'yes'):
117 return True
118 if v in ('false', 'no'):
119 return False
120 return None
121
David Pursehouse8a68ff92012-09-24 12:15:13 +0900122 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 """Get the first value for a key, or None if it is not defined.
124
125 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900126 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700127 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700129 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130 except KeyError:
131 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900132 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 v = []
134
David Pursehouse8a68ff92012-09-24 12:15:13 +0900135 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700136 if v:
137 return v[0]
138 return None
139
140 r = []
141 r.extend(v)
142 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900143 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700144 return r
145
146 def SetString(self, name, value):
147 """Set the value(s) for a key.
148 Only this configuration file is modified.
149
150 The supplied value should be either a string,
151 or a list of strings (to store multiple values).
152 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700153 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154
155 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700156 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157 except KeyError:
158 old = []
159
160 if value is None:
161 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700162 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700163 self._do('--unset-all', name)
164
165 elif isinstance(value, list):
166 if len(value) == 0:
167 self.SetString(name, None)
168
169 elif len(value) == 1:
170 self.SetString(name, value[0])
171
172 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700173 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700175 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 self._do('--add', name, value[i])
177
178 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700179 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700180 self._do('--replace-all', name, value)
181
182 def GetRemote(self, name):
183 """Get the remote.$name.* configuration values as an object.
184 """
185 try:
186 r = self._remotes[name]
187 except KeyError:
188 r = Remote(self, name)
189 self._remotes[r.name] = r
190 return r
191
192 def GetBranch(self, name):
193 """Get the branch.$name.* configuration values as an object.
194 """
195 try:
196 b = self._branches[name]
197 except KeyError:
198 b = Branch(self, name)
199 self._branches[b.name] = b
200 return b
201
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700202 def GetSubSections(self, section):
203 """List all subsection names matching $section.*.*
204 """
205 return self._sections.get(section, set())
206
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700207 def HasSection(self, section, subsection = ''):
208 """Does at least one key in section.subsection exist?
209 """
210 try:
211 return subsection in self._sections[section]
212 except KeyError:
213 return False
214
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700215 def UrlInsteadOf(self, url):
216 """Resolve any url.*.insteadof references.
217 """
218 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700219 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
220 if old_url is not None and url.startswith(old_url):
221 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700222 return url
223
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700224 @property
225 def _sections(self):
226 d = self._section_dict
227 if d is None:
228 d = {}
229 for name in self._cache.keys():
230 p = name.split('.')
231 if 2 == len(p):
232 section = p[0]
233 subsect = ''
234 else:
235 section = p[0]
236 subsect = '.'.join(p[1:-1])
237 if section not in d:
238 d[section] = set()
239 d[section].add(subsect)
240 self._section_dict = d
241 return d
242
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243 @property
244 def _cache(self):
245 if self._cache_dict is None:
246 self._cache_dict = self._Read()
247 return self._cache_dict
248
249 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100250 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700251 if d is None:
252 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100253 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700254 return d
255
Anthony King85b24ac2014-05-06 15:57:48 +0100256 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700257 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100258 if os.path.getmtime(self._json) \
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700259 <= os.path.getmtime(self.file):
Anthony King85b24ac2014-05-06 15:57:48 +0100260 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700261 return None
262 except OSError:
263 return None
264 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100265 Trace(': parsing %s', self.file)
266 fd = open(self._json)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700267 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100268 return json.load(fd)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700269 finally:
270 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100271 except (IOError, ValueError):
272 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700273 return None
274
Anthony King85b24ac2014-05-06 15:57:48 +0100275 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700276 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100277 fd = open(self._json, 'w')
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700278 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100279 json.dump(cache, fd, indent=2)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700280 finally:
281 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100282 except (IOError, TypeError):
283 if os.path.exists(self.json):
284 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700285
286 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700287 """
288 Read configuration data from git.
289
290 This internal method populates the GitConfig cache.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700291
David Aguilar438c5472009-06-28 15:09:16 -0700292 """
David Aguilar438c5472009-06-28 15:09:16 -0700293 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700294 d = self._do('--null', '--list')
295 if d is None:
296 return c
Chirayu Desai0eb35cb2013-11-19 18:46:29 +0530297 for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
298 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700299 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900300 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700301 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900302 key = line
303 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700304
305 if key in c:
306 c[key].append(val)
307 else:
308 c[key] = [val]
309
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700310 return c
311
312 def _do(self, *args):
313 command = ['config', '--file', self.file]
314 command.extend(args)
315
316 p = GitCommand(None,
317 command,
318 capture_stdout = True,
319 capture_stderr = True)
320 if p.Wait() == 0:
321 return p.stdout
322 else:
323 GitError('git config %s: %s' % (str(args), p.stderr))
324
325
326class RefSpec(object):
327 """A Git refspec line, split into its components:
328
329 forced: True if the line starts with '+'
330 src: Left side of the line
331 dst: Right side of the line
332 """
333
334 @classmethod
335 def FromString(cls, rs):
336 lhs, rhs = rs.split(':', 2)
337 if lhs.startswith('+'):
338 lhs = lhs[1:]
339 forced = True
340 else:
341 forced = False
342 return cls(forced, lhs, rhs)
343
344 def __init__(self, forced, lhs, rhs):
345 self.forced = forced
346 self.src = lhs
347 self.dst = rhs
348
349 def SourceMatches(self, rev):
350 if self.src:
351 if rev == self.src:
352 return True
353 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
354 return True
355 return False
356
357 def DestMatches(self, ref):
358 if self.dst:
359 if ref == self.dst:
360 return True
361 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
362 return True
363 return False
364
365 def MapSource(self, rev):
366 if self.src.endswith('/*'):
367 return self.dst[:-1] + rev[len(self.src) - 1:]
368 return self.dst
369
370 def __str__(self):
371 s = ''
372 if self.forced:
373 s += '+'
374 if self.src:
375 s += self.src
376 if self.dst:
377 s += ':'
378 s += self.dst
379 return s
380
381
Doug Anderson06d029c2010-10-27 17:06:01 -0700382_master_processes = []
383_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700384_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800385_master_keys_lock = None
386
387def init_ssh():
388 """Should be called once at the start of repo to init ssh master handling.
389
390 At the moment, all we do is to create our lock.
391 """
392 global _master_keys_lock
393 assert _master_keys_lock is None, "Should only call init_ssh once"
394 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700395
Josh Guilfoyle71985722009-08-16 09:44:40 -0700396def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700397 global _ssh_master
398
Doug Anderson0048b692010-12-21 13:39:23 -0800399 # Acquire the lock. This is needed to prevent opening multiple masters for
400 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
401 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
402 # one that was passed to repo init.
403 _master_keys_lock.acquire()
404 try:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700405
Doug Anderson0048b692010-12-21 13:39:23 -0800406 # Check to see whether we already think that the master is running; if we
407 # think it's already running, return right away.
408 if port is not None:
409 key = '%s:%s' % (host, port)
410 else:
411 key = host
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700412
Doug Anderson0048b692010-12-21 13:39:23 -0800413 if key in _master_keys:
414 return True
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700415
Doug Anderson0048b692010-12-21 13:39:23 -0800416 if not _ssh_master \
417 or 'GIT_SSH' in os.environ \
418 or sys.platform in ('win32', 'cygwin'):
419 # failed earlier, or cygwin ssh can't do this
420 #
421 return False
Doug Anderson06d029c2010-10-27 17:06:01 -0700422
Doug Anderson0048b692010-12-21 13:39:23 -0800423 # We will make two calls to ssh; this is the common part of both calls.
424 command_base = ['ssh',
425 '-o','ControlPath %s' % ssh_sock(),
426 host]
427 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900428 command_base[1:1] = ['-p', str(port)]
Doug Anderson06d029c2010-10-27 17:06:01 -0700429
Doug Anderson0048b692010-12-21 13:39:23 -0800430 # Since the key wasn't in _master_keys, we think that master isn't running.
431 # ...but before actually starting a master, we'll double-check. This can
432 # be important because we can't tell that that 'git@myhost.com' is the same
433 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
434 check_command = command_base + ['-O','check']
435 try:
436 Trace(': %s', ' '.join(check_command))
437 check_process = subprocess.Popen(check_command,
438 stdout=subprocess.PIPE,
439 stderr=subprocess.PIPE)
440 check_process.communicate() # read output, but ignore it...
441 isnt_running = check_process.wait()
Josh Guilfoyle71985722009-08-16 09:44:40 -0700442
Doug Anderson0048b692010-12-21 13:39:23 -0800443 if not isnt_running:
444 # Our double-check found that the master _was_ infact running. Add to
445 # the list of keys.
446 _master_keys.add(key)
447 return True
448 except Exception:
449 # Ignore excpetions. We we will fall back to the normal command and print
450 # to the log there.
451 pass
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700452
Doug Anderson0048b692010-12-21 13:39:23 -0800453 command = command_base[:1] + \
454 ['-M', '-N'] + \
455 command_base[1:]
456 try:
457 Trace(': %s', ' '.join(command))
458 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700459 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800460 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700461 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
462 % (host,port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800463 return False
464
465 _master_processes.append(p)
466 _master_keys.add(key)
467 time.sleep(1)
468 return True
469 finally:
470 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700471
472def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800473 global _master_keys_lock
474
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700475 terminate_ssh_clients()
476
Doug Anderson06d029c2010-10-27 17:06:01 -0700477 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700478 try:
479 os.kill(p.pid, SIGTERM)
480 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700481 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700482 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700483 del _master_processes[:]
484 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700485
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700486 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700487 if d:
488 try:
489 os.rmdir(os.path.dirname(d))
490 except OSError:
491 pass
492
Doug Anderson0048b692010-12-21 13:39:23 -0800493 # We're done with the lock, so we can delete it.
494 _master_keys_lock = None
495
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700496URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700497URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700498
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700499def GetSchemeFromUrl(url):
500 m = URI_ALL.match(url)
501 if m:
502 return m.group(1)
503 return None
504
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700505def _preconnect(url):
506 m = URI_ALL.match(url)
507 if m:
508 scheme = m.group(1)
509 host = m.group(2)
510 if ':' in host:
511 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700512 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700513 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700514 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
515 return _open_ssh(host, port)
516 return False
517
518 m = URI_SCP.match(url)
519 if m:
520 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700521 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700522
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700523 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700524
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700525class Remote(object):
526 """Configuration options related to a remote.
527 """
528 def __init__(self, config, name):
529 self._config = config
530 self.name = name
531 self.url = self._Get('url')
532 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800533 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530534 self.fetch = list(map(RefSpec.FromString,
535 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800536 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800537
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100538 def _InsteadOf(self):
539 globCfg = GitConfig.ForUser()
540 urlList = globCfg.GetSubSections('url')
541 longest = ""
542 longestUrl = ""
543
544 for url in urlList:
545 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900546 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100547
548 for insteadOf in insteadOfList:
549 if self.url.startswith(insteadOf) \
550 and len(insteadOf) > len(longest):
551 longest = insteadOf
552 longestUrl = url
553
554 if len(longest) == 0:
555 return self.url
556
557 return self.url.replace(longest, longestUrl, 1)
558
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700559 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100560 connectionUrl = self._InsteadOf()
561 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700562
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800563 def ReviewUrl(self, userEmail):
564 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800565 if self.review is None:
566 return None
567
568 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700569 if u.startswith('persistent-'):
570 u = u[len('persistent-'):]
Steve Pucci143d8a72014-01-30 09:45:53 -0800571 if u.split(':')[0] not in ('http', 'https', 'sso'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800572 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700573 if u.endswith('/Gerrit'):
574 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800575 if u.endswith('/ssh_info'):
576 u = u[:len(u) - len('/ssh_info')]
577 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900578 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800579 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800580
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700581 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800582 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700583 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800584 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
585 self._review_url = self._SshReviewUrl(userEmail, host, port)
586 REVIEW_CACHE[u] = self._review_url
Steve Pucci143d8a72014-01-30 09:45:53 -0800587 elif u.startswith('sso:'):
588 self._review_url = u # Assume it's right
589 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700590 else:
591 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800592 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700593 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700594 if info == 'NOT_AVAILABLE' or '<' in info:
595 # If `info` contains '<', we assume the server gave us some sort
596 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700597 #
Conley Owens745a39b2013-06-05 13:16:18 -0700598 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800599 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700600 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800601 host, port = info.split()
602 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700603 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800604 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700605 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700606 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900607 except HTTPException as e:
608 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800609
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800610 REVIEW_CACHE[u] = self._review_url
611 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800612
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800613 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700614 username = self._config.GetString('review.%s.username' % self.review)
615 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800616 username = userEmail.split('@')[0]
617 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700618
619 def ToLocal(self, rev):
620 """Convert a remote revision string to something we have locally.
621 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200622 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700623 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700624
625 if not rev.startswith('refs/'):
626 rev = R_HEADS + rev
627
628 for spec in self.fetch:
629 if spec.SourceMatches(rev):
630 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600631
632 if not rev.startswith(R_HEADS):
633 return rev
634
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 raise GitError('remote %s does not have %s' % (self.name, rev))
636
637 def WritesTo(self, ref):
638 """True if the remote stores to the tracking ref.
639 """
640 for spec in self.fetch:
641 if spec.DestMatches(ref):
642 return True
643 return False
644
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800645 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646 """Set the fetch refspec to its default value.
647 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800648 if mirror:
649 dst = 'refs/heads/*'
650 else:
651 dst = 'refs/remotes/%s/*' % self.name
652 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654 def Save(self):
655 """Save this remote to the configuration.
656 """
657 self._Set('url', self.url)
658 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800659 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530660 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700661
662 def _Set(self, key, value):
663 key = 'remote.%s.%s' % (self.name, key)
664 return self._config.SetString(key, value)
665
David Pursehouse8a68ff92012-09-24 12:15:13 +0900666 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900668 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669
670
671class Branch(object):
672 """Configuration options related to a single branch.
673 """
674 def __init__(self, config, name):
675 self._config = config
676 self.name = name
677 self.merge = self._Get('merge')
678
679 r = self._Get('remote')
680 if r:
681 self.remote = self._config.GetRemote(r)
682 else:
683 self.remote = None
684
685 @property
686 def LocalMerge(self):
687 """Convert the merge spec to a local name.
688 """
689 if self.remote and self.merge:
690 return self.remote.ToLocal(self.merge)
691 return None
692
693 def Save(self):
694 """Save this branch back into the configuration.
695 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700696 if self._config.HasSection('branch', self.name):
697 if self.remote:
698 self._Set('remote', self.remote.name)
699 else:
700 self._Set('remote', None)
701 self._Set('merge', self.merge)
702
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700703 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530704 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700705 try:
706 fd.write('[branch "%s"]\n' % self.name)
707 if self.remote:
708 fd.write('\tremote = %s\n' % self.remote.name)
709 if self.merge:
710 fd.write('\tmerge = %s\n' % self.merge)
711 finally:
712 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713
714 def _Set(self, key, value):
715 key = 'branch.%s.%s' % (self.name, key)
716 return self._config.SetString(key, value)
717
David Pursehouse8a68ff92012-09-24 12:15:13 +0900718 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900720 return self._config.GetString(key, all_keys = all_keys)