blob: 3fc56b68bb633bd2fa2390bd14952d86a705fb2f [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
Shawn O. Pearcefb231612009-04-10 18:53:46 -070023import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Doug Anderson0048b692010-12-21 13:39:23 -080025try:
26 import threading as _threading
27except ImportError:
28 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070029import time
David Pursehouse59bbb582013-05-17 10:49:33 +090030
31from pyversion import is_python3
32if is_python3():
Sarah Owens1f7627f2012-10-31 09:21:55 -070033 import urllib.request
34 import urllib.error
35else:
David Pursehouse59bbb582013-05-17 10:49:33 +090036 import urllib2
Sarah Owens1f7627f2012-10-31 09:21:55 -070037 import imp
38 urllib = imp.new_module('urllib')
39 urllib.request = urllib2
40 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070041
Shawn O. Pearcefb231612009-04-10 18:53:46 -070042from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080043from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070044from trace import Trace
David Pursehouseecf8f2b2013-05-24 12:12:23 +090045if is_python3():
46 from http.client import HTTPException
47else:
48 from httplib import HTTPException
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070049
50from git_command import GitCommand
51from git_command import ssh_sock
52from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070053
54R_HEADS = 'refs/heads/'
55R_TAGS = 'refs/tags/'
David Pursehouse1d947b32012-10-25 12:23:11 +090056ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057
Shawn O. Pearce146fe902009-03-25 14:06:43 -070058REVIEW_CACHE = dict()
59
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070060def IsId(rev):
61 return ID_RE.match(rev)
62
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070063def _key(name):
64 parts = name.split('.')
65 if len(parts) < 2:
66 return name.lower()
67 parts[ 0] = parts[ 0].lower()
68 parts[-1] = parts[-1].lower()
69 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070070
71class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070072 _ForUser = None
73
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074 @classmethod
75 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070076 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090077 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070078 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070079
80 @classmethod
81 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090082 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070083 defaults = defaults)
84
Anthony King85b24ac2014-05-06 15:57:48 +010085 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090086 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070087 self.defaults = defaults
88 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070089 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070090 self._remotes = {}
91 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070092
Anthony King85b24ac2014-05-06 15:57:48 +010093 self._json = jsonFile
94 if self._json is None:
95 self._json = os.path.join(
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070096 os.path.dirname(self.file),
Anthony King85b24ac2014-05-06 15:57:48 +010097 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098
99 def Has(self, name, include_defaults = True):
100 """Return true if this configuration file has the key.
101 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700102 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700103 return True
104 if include_defaults and self.defaults:
105 return self.defaults.Has(name, include_defaults = True)
106 return False
107
108 def GetBoolean(self, name):
109 """Returns a boolean from the configuration file.
110 None : The value was not defined, or is not a boolean.
111 True : The value was set to true or yes.
112 False: The value was set to false or no.
113 """
114 v = self.GetString(name)
115 if v is None:
116 return None
117 v = v.lower()
118 if v in ('true', 'yes'):
119 return True
120 if v in ('false', 'no'):
121 return False
122 return None
123
David Pursehouse8a68ff92012-09-24 12:15:13 +0900124 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700125 """Get the first value for a key, or None if it is not defined.
126
127 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900128 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700131 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132 except KeyError:
133 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900134 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700135 v = []
136
David Pursehouse8a68ff92012-09-24 12:15:13 +0900137 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700138 if v:
139 return v[0]
140 return None
141
142 r = []
143 r.extend(v)
144 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900145 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146 return r
147
148 def SetString(self, name, value):
149 """Set the value(s) for a key.
150 Only this configuration file is modified.
151
152 The supplied value should be either a string,
153 or a list of strings (to store multiple values).
154 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700155 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700156
157 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700158 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 except KeyError:
160 old = []
161
162 if value is None:
163 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700164 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 self._do('--unset-all', name)
166
167 elif isinstance(value, list):
168 if len(value) == 0:
169 self.SetString(name, None)
170
171 elif len(value) == 1:
172 self.SetString(name, value[0])
173
174 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700175 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700177 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 self._do('--add', name, value[i])
179
180 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700181 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700182 self._do('--replace-all', name, value)
183
184 def GetRemote(self, name):
185 """Get the remote.$name.* configuration values as an object.
186 """
187 try:
188 r = self._remotes[name]
189 except KeyError:
190 r = Remote(self, name)
191 self._remotes[r.name] = r
192 return r
193
194 def GetBranch(self, name):
195 """Get the branch.$name.* configuration values as an object.
196 """
197 try:
198 b = self._branches[name]
199 except KeyError:
200 b = Branch(self, name)
201 self._branches[b.name] = b
202 return b
203
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700204 def GetSubSections(self, section):
205 """List all subsection names matching $section.*.*
206 """
207 return self._sections.get(section, set())
208
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700209 def HasSection(self, section, subsection = ''):
210 """Does at least one key in section.subsection exist?
211 """
212 try:
213 return subsection in self._sections[section]
214 except KeyError:
215 return False
216
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700217 def UrlInsteadOf(self, url):
218 """Resolve any url.*.insteadof references.
219 """
220 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700221 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
222 if old_url is not None and url.startswith(old_url):
223 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700224 return url
225
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700226 @property
227 def _sections(self):
228 d = self._section_dict
229 if d is None:
230 d = {}
231 for name in self._cache.keys():
232 p = name.split('.')
233 if 2 == len(p):
234 section = p[0]
235 subsect = ''
236 else:
237 section = p[0]
238 subsect = '.'.join(p[1:-1])
239 if section not in d:
240 d[section] = set()
241 d[section].add(subsect)
242 self._section_dict = d
243 return d
244
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700245 @property
246 def _cache(self):
247 if self._cache_dict is None:
248 self._cache_dict = self._Read()
249 return self._cache_dict
250
251 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100252 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700253 if d is None:
254 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100255 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700256 return d
257
Anthony King85b24ac2014-05-06 15:57:48 +0100258 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700259 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100260 if os.path.getmtime(self._json) \
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700261 <= os.path.getmtime(self.file):
Anthony King85b24ac2014-05-06 15:57:48 +0100262 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700263 return None
264 except OSError:
265 return None
266 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100267 Trace(': parsing %s', self.file)
268 fd = open(self._json)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700269 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100270 return json.load(fd)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700271 finally:
272 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100273 except (IOError, ValueError):
274 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700275 return None
276
Anthony King85b24ac2014-05-06 15:57:48 +0100277 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700278 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100279 fd = open(self._json, 'w')
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700280 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100281 json.dump(cache, fd, indent=2)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700282 finally:
283 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100284 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100285 if os.path.exists(self._json):
Anthony King85b24ac2014-05-06 15:57:48 +0100286 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700287
288 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700289 """
290 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700291
David Aguilar438c5472009-06-28 15:09:16 -0700292 This internal method populates the GitConfig cache.
293
294 """
David Aguilar438c5472009-06-28 15:09:16 -0700295 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700296 d = self._do('--null', '--list')
297 if d is None:
298 return c
Chirayu Desai0eb35cb2013-11-19 18:46:29 +0530299 for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
300 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700301 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900302 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700303 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900304 key = line
305 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700306
307 if key in c:
308 c[key].append(val)
309 else:
310 c[key] = [val]
311
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700312 return c
313
314 def _do(self, *args):
315 command = ['config', '--file', self.file]
316 command.extend(args)
317
318 p = GitCommand(None,
319 command,
320 capture_stdout = True,
321 capture_stderr = True)
322 if p.Wait() == 0:
323 return p.stdout
324 else:
325 GitError('git config %s: %s' % (str(args), p.stderr))
326
327
328class RefSpec(object):
329 """A Git refspec line, split into its components:
330
331 forced: True if the line starts with '+'
332 src: Left side of the line
333 dst: Right side of the line
334 """
335
336 @classmethod
337 def FromString(cls, rs):
338 lhs, rhs = rs.split(':', 2)
339 if lhs.startswith('+'):
340 lhs = lhs[1:]
341 forced = True
342 else:
343 forced = False
344 return cls(forced, lhs, rhs)
345
346 def __init__(self, forced, lhs, rhs):
347 self.forced = forced
348 self.src = lhs
349 self.dst = rhs
350
351 def SourceMatches(self, rev):
352 if self.src:
353 if rev == self.src:
354 return True
355 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
356 return True
357 return False
358
359 def DestMatches(self, ref):
360 if self.dst:
361 if ref == self.dst:
362 return True
363 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
364 return True
365 return False
366
367 def MapSource(self, rev):
368 if self.src.endswith('/*'):
369 return self.dst[:-1] + rev[len(self.src) - 1:]
370 return self.dst
371
372 def __str__(self):
373 s = ''
374 if self.forced:
375 s += '+'
376 if self.src:
377 s += self.src
378 if self.dst:
379 s += ':'
380 s += self.dst
381 return s
382
383
Doug Anderson06d029c2010-10-27 17:06:01 -0700384_master_processes = []
385_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700386_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800387_master_keys_lock = None
388
389def init_ssh():
390 """Should be called once at the start of repo to init ssh master handling.
391
392 At the moment, all we do is to create our lock.
393 """
394 global _master_keys_lock
395 assert _master_keys_lock is None, "Should only call init_ssh once"
396 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700397
Josh Guilfoyle71985722009-08-16 09:44:40 -0700398def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700399 global _ssh_master
400
Doug Anderson0048b692010-12-21 13:39:23 -0800401 # Acquire the lock. This is needed to prevent opening multiple masters for
402 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
403 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
404 # one that was passed to repo init.
405 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700406 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700407
Doug Anderson0048b692010-12-21 13:39:23 -0800408 # Check to see whether we already think that the master is running; if we
409 # think it's already running, return right away.
410 if port is not None:
411 key = '%s:%s' % (host, port)
412 else:
413 key = host
414
415 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700416 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700417
Doug Anderson0048b692010-12-21 13:39:23 -0800418 if not _ssh_master \
419 or 'GIT_SSH' in os.environ \
420 or sys.platform in ('win32', 'cygwin'):
421 # failed earlier, or cygwin ssh can't do this
422 #
423 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700424
Doug Anderson0048b692010-12-21 13:39:23 -0800425 # We will make two calls to ssh; this is the common part of both calls.
426 command_base = ['ssh',
427 '-o','ControlPath %s' % ssh_sock(),
428 host]
429 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900430 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800431
432 # Since the key wasn't in _master_keys, we think that master isn't running.
433 # ...but before actually starting a master, we'll double-check. This can
434 # be important because we can't tell that that 'git@myhost.com' is the same
435 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
436 check_command = command_base + ['-O','check']
437 try:
438 Trace(': %s', ' '.join(check_command))
439 check_process = subprocess.Popen(check_command,
440 stdout=subprocess.PIPE,
441 stderr=subprocess.PIPE)
442 check_process.communicate() # read output, but ignore it...
443 isnt_running = check_process.wait()
444
445 if not isnt_running:
446 # Our double-check found that the master _was_ infact running. Add to
447 # the list of keys.
448 _master_keys.add(key)
449 return True
450 except Exception:
451 # Ignore excpetions. We we will fall back to the normal command and print
452 # to the log there.
453 pass
454
455 command = command_base[:1] + \
456 ['-M', '-N'] + \
457 command_base[1:]
458 try:
459 Trace(': %s', ' '.join(command))
460 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700461 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800462 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700463 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
464 % (host,port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800465 return False
466
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200467 time.sleep(1)
468 ssh_died = (p.poll() is not None)
469 if ssh_died:
470 return False
471
Doug Anderson0048b692010-12-21 13:39:23 -0800472 _master_processes.append(p)
473 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800474 return True
475 finally:
476 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700477
478def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800479 global _master_keys_lock
480
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700481 terminate_ssh_clients()
482
Doug Anderson06d029c2010-10-27 17:06:01 -0700483 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700484 try:
485 os.kill(p.pid, SIGTERM)
486 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700487 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700488 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700489 del _master_processes[:]
490 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700491
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700492 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700493 if d:
494 try:
495 os.rmdir(os.path.dirname(d))
496 except OSError:
497 pass
498
Doug Anderson0048b692010-12-21 13:39:23 -0800499 # We're done with the lock, so we can delete it.
500 _master_keys_lock = None
501
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700502URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700503URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700504
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700505def GetSchemeFromUrl(url):
506 m = URI_ALL.match(url)
507 if m:
508 return m.group(1)
509 return None
510
Dan Willemsen0745bb22015-08-17 13:41:45 -0700511@contextlib.contextmanager
512def GetUrlCookieFile(url, quiet):
513 if url.startswith('persistent-'):
514 try:
515 p = subprocess.Popen(
516 ['git-remote-persistent-https', '-print_config', url],
517 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
518 stderr=subprocess.PIPE)
519 try:
520 cookieprefix = 'http.cookiefile='
521 proxyprefix = 'http.proxy='
522 cookiefile = None
523 proxy = None
524 for line in p.stdout:
525 line = line.strip()
526 if line.startswith(cookieprefix):
527 cookiefile = line[len(cookieprefix):]
528 if line.startswith(proxyprefix):
529 proxy = line[len(proxyprefix):]
530 # Leave subprocess open, as cookie file may be transient.
531 if cookiefile or proxy:
532 yield cookiefile, proxy
533 return
534 finally:
535 p.stdin.close()
536 if p.wait():
537 err_msg = p.stderr.read()
538 if ' -print_config' in err_msg:
539 pass # Persistent proxy doesn't support -print_config.
540 elif not quiet:
541 print(err_msg, file=sys.stderr)
542 except OSError as e:
543 if e.errno == errno.ENOENT:
544 pass # No persistent proxy.
545 raise
546 yield GitConfig.ForUser().GetString('http.cookiefile'), None
547
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700548def _preconnect(url):
549 m = URI_ALL.match(url)
550 if m:
551 scheme = m.group(1)
552 host = m.group(2)
553 if ':' in host:
554 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700555 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700556 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700557 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
558 return _open_ssh(host, port)
559 return False
560
561 m = URI_SCP.match(url)
562 if m:
563 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700564 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700565
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700566 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700567
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700568class Remote(object):
569 """Configuration options related to a remote.
570 """
571 def __init__(self, config, name):
572 self._config = config
573 self.name = name
574 self.url = self._Get('url')
575 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800576 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530577 self.fetch = list(map(RefSpec.FromString,
578 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800579 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800580
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100581 def _InsteadOf(self):
582 globCfg = GitConfig.ForUser()
583 urlList = globCfg.GetSubSections('url')
584 longest = ""
585 longestUrl = ""
586
587 for url in urlList:
588 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900589 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100590
591 for insteadOf in insteadOfList:
592 if self.url.startswith(insteadOf) \
593 and len(insteadOf) > len(longest):
594 longest = insteadOf
595 longestUrl = url
596
597 if len(longest) == 0:
598 return self.url
599
600 return self.url.replace(longest, longestUrl, 1)
601
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700602 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100603 connectionUrl = self._InsteadOf()
604 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700605
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800606 def ReviewUrl(self, userEmail):
607 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800608 if self.review is None:
609 return None
610
611 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700612 if u.startswith('persistent-'):
613 u = u[len('persistent-'):]
Steve Pucci143d8a72014-01-30 09:45:53 -0800614 if u.split(':')[0] not in ('http', 'https', 'sso'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800615 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700616 if u.endswith('/Gerrit'):
617 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800618 if u.endswith('/ssh_info'):
619 u = u[:len(u) - len('/ssh_info')]
620 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900621 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800622 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800623
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700624 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800625 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700626 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800627 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
628 self._review_url = self._SshReviewUrl(userEmail, host, port)
629 REVIEW_CACHE[u] = self._review_url
Steve Pucci143d8a72014-01-30 09:45:53 -0800630 elif u.startswith('sso:'):
631 self._review_url = u # Assume it's right
632 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700633 else:
634 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800635 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700636 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700637 if info == 'NOT_AVAILABLE' or '<' in info:
638 # If `info` contains '<', we assume the server gave us some sort
639 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700640 #
Conley Owens745a39b2013-06-05 13:16:18 -0700641 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800642 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700643 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800644 host, port = info.split()
645 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700646 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800647 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700648 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700649 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900650 except HTTPException as e:
651 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800652
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800653 REVIEW_CACHE[u] = self._review_url
654 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800655
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800656 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700657 username = self._config.GetString('review.%s.username' % self.review)
658 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800659 username = userEmail.split('@')[0]
660 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700661
662 def ToLocal(self, rev):
663 """Convert a remote revision string to something we have locally.
664 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200665 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700666 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667
668 if not rev.startswith('refs/'):
669 rev = R_HEADS + rev
670
671 for spec in self.fetch:
672 if spec.SourceMatches(rev):
673 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600674
675 if not rev.startswith(R_HEADS):
676 return rev
677
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700678 raise GitError('remote %s does not have %s' % (self.name, rev))
679
680 def WritesTo(self, ref):
681 """True if the remote stores to the tracking ref.
682 """
683 for spec in self.fetch:
684 if spec.DestMatches(ref):
685 return True
686 return False
687
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800688 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700689 """Set the fetch refspec to its default value.
690 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800691 if mirror:
692 dst = 'refs/heads/*'
693 else:
694 dst = 'refs/remotes/%s/*' % self.name
695 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696
697 def Save(self):
698 """Save this remote to the configuration.
699 """
700 self._Set('url', self.url)
701 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800702 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530703 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704
705 def _Set(self, key, value):
706 key = 'remote.%s.%s' % (self.name, key)
707 return self._config.SetString(key, value)
708
David Pursehouse8a68ff92012-09-24 12:15:13 +0900709 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700710 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900711 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712
713
714class Branch(object):
715 """Configuration options related to a single branch.
716 """
717 def __init__(self, config, name):
718 self._config = config
719 self.name = name
720 self.merge = self._Get('merge')
721
722 r = self._Get('remote')
723 if r:
724 self.remote = self._config.GetRemote(r)
725 else:
726 self.remote = None
727
728 @property
729 def LocalMerge(self):
730 """Convert the merge spec to a local name.
731 """
732 if self.remote and self.merge:
733 return self.remote.ToLocal(self.merge)
734 return None
735
736 def Save(self):
737 """Save this branch back into the configuration.
738 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700739 if self._config.HasSection('branch', self.name):
740 if self.remote:
741 self._Set('remote', self.remote.name)
742 else:
743 self._Set('remote', None)
744 self._Set('merge', self.merge)
745
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700746 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530747 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700748 try:
749 fd.write('[branch "%s"]\n' % self.name)
750 if self.remote:
751 fd.write('\tremote = %s\n' % self.remote.name)
752 if self.merge:
753 fd.write('\tmerge = %s\n' % self.merge)
754 finally:
755 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700756
757 def _Set(self, key, value):
758 key = 'branch.%s.%s' % (self.name, key)
759 return self._config.SetString(key, value)
760
David Pursehouse8a68ff92012-09-24 12:15:13 +0900761 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700762 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900763 return self._config.GetString(key, all_keys = all_keys)