blob: e2236785318fc10393d2052de6ee83f336e581de [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')
Steve Raed6480452016-08-10 15:00:00 -0700575 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700576 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800577 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530578 self.fetch = list(map(RefSpec.FromString,
579 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800580 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800581
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100582 def _InsteadOf(self):
583 globCfg = GitConfig.ForUser()
584 urlList = globCfg.GetSubSections('url')
585 longest = ""
586 longestUrl = ""
587
588 for url in urlList:
589 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900590 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100591
592 for insteadOf in insteadOfList:
593 if self.url.startswith(insteadOf) \
594 and len(insteadOf) > len(longest):
595 longest = insteadOf
596 longestUrl = url
597
598 if len(longest) == 0:
599 return self.url
600
601 return self.url.replace(longest, longestUrl, 1)
602
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700603 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100604 connectionUrl = self._InsteadOf()
605 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700606
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800607 def ReviewUrl(self, userEmail):
608 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800609 if self.review is None:
610 return None
611
612 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700613 if u.startswith('persistent-'):
614 u = u[len('persistent-'):]
Steve Pucci143d8a72014-01-30 09:45:53 -0800615 if u.split(':')[0] not in ('http', 'https', 'sso'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800616 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700617 if u.endswith('/Gerrit'):
618 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800619 if u.endswith('/ssh_info'):
620 u = u[:len(u) - len('/ssh_info')]
621 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900622 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800623 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800624
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700625 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800626 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700627 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800628 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
629 self._review_url = self._SshReviewUrl(userEmail, host, port)
630 REVIEW_CACHE[u] = self._review_url
Steve Pucci143d8a72014-01-30 09:45:53 -0800631 elif u.startswith('sso:'):
632 self._review_url = u # Assume it's right
633 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700634 else:
635 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800636 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700637 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700638 if info == 'NOT_AVAILABLE' or '<' in info:
639 # If `info` contains '<', we assume the server gave us some sort
640 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700641 #
Conley Owens745a39b2013-06-05 13:16:18 -0700642 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800643 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700644 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800645 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000646 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700647 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800648 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700649 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700650 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900651 except HTTPException as e:
652 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800653
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800654 REVIEW_CACHE[u] = self._review_url
655 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800656
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800657 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700658 username = self._config.GetString('review.%s.username' % self.review)
659 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800660 username = userEmail.split('@')[0]
661 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662
663 def ToLocal(self, rev):
664 """Convert a remote revision string to something we have locally.
665 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200666 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700668
669 if not rev.startswith('refs/'):
670 rev = R_HEADS + rev
671
672 for spec in self.fetch:
673 if spec.SourceMatches(rev):
674 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600675
676 if not rev.startswith(R_HEADS):
677 return rev
678
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679 raise GitError('remote %s does not have %s' % (self.name, rev))
680
681 def WritesTo(self, ref):
682 """True if the remote stores to the tracking ref.
683 """
684 for spec in self.fetch:
685 if spec.DestMatches(ref):
686 return True
687 return False
688
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800689 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 """Set the fetch refspec to its default value.
691 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800692 if mirror:
693 dst = 'refs/heads/*'
694 else:
695 dst = 'refs/remotes/%s/*' % self.name
696 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697
698 def Save(self):
699 """Save this remote to the configuration.
700 """
701 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700702 if self.pushUrl is not None:
703 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
704 else:
705 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800707 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530708 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700709
710 def _Set(self, key, value):
711 key = 'remote.%s.%s' % (self.name, key)
712 return self._config.SetString(key, value)
713
David Pursehouse8a68ff92012-09-24 12:15:13 +0900714 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700715 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900716 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717
718
719class Branch(object):
720 """Configuration options related to a single branch.
721 """
722 def __init__(self, config, name):
723 self._config = config
724 self.name = name
725 self.merge = self._Get('merge')
726
727 r = self._Get('remote')
728 if r:
729 self.remote = self._config.GetRemote(r)
730 else:
731 self.remote = None
732
733 @property
734 def LocalMerge(self):
735 """Convert the merge spec to a local name.
736 """
737 if self.remote and self.merge:
738 return self.remote.ToLocal(self.merge)
739 return None
740
741 def Save(self):
742 """Save this branch back into the configuration.
743 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700744 if self._config.HasSection('branch', self.name):
745 if self.remote:
746 self._Set('remote', self.remote.name)
747 else:
748 self._Set('remote', None)
749 self._Set('merge', self.merge)
750
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700751 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530752 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700753 try:
754 fd.write('[branch "%s"]\n' % self.name)
755 if self.remote:
756 fd.write('\tremote = %s\n' % self.remote.name)
757 if self.merge:
758 fd.write('\tmerge = %s\n' % self.merge)
759 finally:
760 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700761
762 def _Set(self, key, value):
763 key = 'branch.%s.%s' % (self.name, key)
764 return self._config.SetString(key, value)
765
David Pursehouse8a68ff92012-09-24 12:15:13 +0900766 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900768 return self._config.GetString(key, all_keys = all_keys)