blob: 0379181a7145ef8d1982a7fc3e7bd8630b7ac382 [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.
291
292 This internal method populates the GitConfig cache.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293
David Aguilar438c5472009-06-28 15:09:16 -0700294 """
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()
406 try:
Josh Guilfoyle71985722009-08-16 09:44:40 -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
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700414
Doug Anderson0048b692010-12-21 13:39:23 -0800415 if key in _master_keys:
416 return True
Shawn O. Pearcefb231612009-04-10 18:53:46 -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
Doug Anderson06d029c2010-10-27 17:06:01 -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 Anderson06d029c2010-10-27 17:06:01 -0700431
Doug Anderson0048b692010-12-21 13:39:23 -0800432 # 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()
Josh Guilfoyle71985722009-08-16 09:44:40 -0700444
Doug Anderson0048b692010-12-21 13:39:23 -0800445 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
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700454
Doug Anderson0048b692010-12-21 13:39:23 -0800455 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
467 _master_processes.append(p)
468 _master_keys.add(key)
469 time.sleep(1)
470 return True
471 finally:
472 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700473
474def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800475 global _master_keys_lock
476
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700477 terminate_ssh_clients()
478
Doug Anderson06d029c2010-10-27 17:06:01 -0700479 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700480 try:
481 os.kill(p.pid, SIGTERM)
482 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700483 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700484 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700485 del _master_processes[:]
486 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700487
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700488 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700489 if d:
490 try:
491 os.rmdir(os.path.dirname(d))
492 except OSError:
493 pass
494
Doug Anderson0048b692010-12-21 13:39:23 -0800495 # We're done with the lock, so we can delete it.
496 _master_keys_lock = None
497
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700498URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700499URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700500
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700501def GetSchemeFromUrl(url):
502 m = URI_ALL.match(url)
503 if m:
504 return m.group(1)
505 return None
506
Dan Willemsen0745bb22015-08-17 13:41:45 -0700507@contextlib.contextmanager
508def GetUrlCookieFile(url, quiet):
509 if url.startswith('persistent-'):
510 try:
511 p = subprocess.Popen(
512 ['git-remote-persistent-https', '-print_config', url],
513 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
514 stderr=subprocess.PIPE)
515 try:
516 cookieprefix = 'http.cookiefile='
517 proxyprefix = 'http.proxy='
518 cookiefile = None
519 proxy = None
520 for line in p.stdout:
521 line = line.strip()
522 if line.startswith(cookieprefix):
523 cookiefile = line[len(cookieprefix):]
524 if line.startswith(proxyprefix):
525 proxy = line[len(proxyprefix):]
526 # Leave subprocess open, as cookie file may be transient.
527 if cookiefile or proxy:
528 yield cookiefile, proxy
529 return
530 finally:
531 p.stdin.close()
532 if p.wait():
533 err_msg = p.stderr.read()
534 if ' -print_config' in err_msg:
535 pass # Persistent proxy doesn't support -print_config.
536 elif not quiet:
537 print(err_msg, file=sys.stderr)
538 except OSError as e:
539 if e.errno == errno.ENOENT:
540 pass # No persistent proxy.
541 raise
542 yield GitConfig.ForUser().GetString('http.cookiefile'), None
543
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700544def _preconnect(url):
545 m = URI_ALL.match(url)
546 if m:
547 scheme = m.group(1)
548 host = m.group(2)
549 if ':' in host:
550 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700551 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700552 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700553 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
554 return _open_ssh(host, port)
555 return False
556
557 m = URI_SCP.match(url)
558 if m:
559 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700560 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700561
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700562 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700563
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700564class Remote(object):
565 """Configuration options related to a remote.
566 """
567 def __init__(self, config, name):
568 self._config = config
569 self.name = name
570 self.url = self._Get('url')
571 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800572 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530573 self.fetch = list(map(RefSpec.FromString,
574 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800575 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800576
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100577 def _InsteadOf(self):
578 globCfg = GitConfig.ForUser()
579 urlList = globCfg.GetSubSections('url')
580 longest = ""
581 longestUrl = ""
582
583 for url in urlList:
584 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900585 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100586
587 for insteadOf in insteadOfList:
588 if self.url.startswith(insteadOf) \
589 and len(insteadOf) > len(longest):
590 longest = insteadOf
591 longestUrl = url
592
593 if len(longest) == 0:
594 return self.url
595
596 return self.url.replace(longest, longestUrl, 1)
597
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700598 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100599 connectionUrl = self._InsteadOf()
600 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700601
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800602 def ReviewUrl(self, userEmail):
603 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800604 if self.review is None:
605 return None
606
607 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700608 if u.startswith('persistent-'):
609 u = u[len('persistent-'):]
Steve Pucci143d8a72014-01-30 09:45:53 -0800610 if u.split(':')[0] not in ('http', 'https', 'sso'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800611 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700612 if u.endswith('/Gerrit'):
613 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800614 if u.endswith('/ssh_info'):
615 u = u[:len(u) - len('/ssh_info')]
616 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900617 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800618 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800619
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700620 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800621 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700622 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800623 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
624 self._review_url = self._SshReviewUrl(userEmail, host, port)
625 REVIEW_CACHE[u] = self._review_url
Steve Pucci143d8a72014-01-30 09:45:53 -0800626 elif u.startswith('sso:'):
627 self._review_url = u # Assume it's right
628 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700629 else:
630 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800631 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700632 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700633 if info == 'NOT_AVAILABLE' or '<' in info:
634 # If `info` contains '<', we assume the server gave us some sort
635 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700636 #
Conley Owens745a39b2013-06-05 13:16:18 -0700637 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800638 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700639 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800640 host, port = info.split()
641 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700642 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800643 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700644 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700645 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900646 except HTTPException as e:
647 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800648
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800649 REVIEW_CACHE[u] = self._review_url
650 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800651
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800652 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700653 username = self._config.GetString('review.%s.username' % self.review)
654 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800655 username = userEmail.split('@')[0]
656 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700657
658 def ToLocal(self, rev):
659 """Convert a remote revision string to something we have locally.
660 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200661 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700663
664 if not rev.startswith('refs/'):
665 rev = R_HEADS + rev
666
667 for spec in self.fetch:
668 if spec.SourceMatches(rev):
669 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600670
671 if not rev.startswith(R_HEADS):
672 return rev
673
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700674 raise GitError('remote %s does not have %s' % (self.name, rev))
675
676 def WritesTo(self, ref):
677 """True if the remote stores to the tracking ref.
678 """
679 for spec in self.fetch:
680 if spec.DestMatches(ref):
681 return True
682 return False
683
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800684 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700685 """Set the fetch refspec to its default value.
686 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800687 if mirror:
688 dst = 'refs/heads/*'
689 else:
690 dst = 'refs/remotes/%s/*' % self.name
691 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700692
693 def Save(self):
694 """Save this remote to the configuration.
695 """
696 self._Set('url', self.url)
697 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800698 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530699 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700700
701 def _Set(self, key, value):
702 key = 'remote.%s.%s' % (self.name, key)
703 return self._config.SetString(key, value)
704
David Pursehouse8a68ff92012-09-24 12:15:13 +0900705 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900707 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708
709
710class Branch(object):
711 """Configuration options related to a single branch.
712 """
713 def __init__(self, config, name):
714 self._config = config
715 self.name = name
716 self.merge = self._Get('merge')
717
718 r = self._Get('remote')
719 if r:
720 self.remote = self._config.GetRemote(r)
721 else:
722 self.remote = None
723
724 @property
725 def LocalMerge(self):
726 """Convert the merge spec to a local name.
727 """
728 if self.remote and self.merge:
729 return self.remote.ToLocal(self.merge)
730 return None
731
732 def Save(self):
733 """Save this branch back into the configuration.
734 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700735 if self._config.HasSection('branch', self.name):
736 if self.remote:
737 self._Set('remote', self.remote.name)
738 else:
739 self._Set('remote', None)
740 self._Set('merge', self.merge)
741
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700742 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530743 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700744 try:
745 fd.write('[branch "%s"]\n' % self.name)
746 if self.remote:
747 fd.write('\tremote = %s\n' % self.remote.name)
748 if self.merge:
749 fd.write('\tmerge = %s\n' % self.merge)
750 finally:
751 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752
753 def _Set(self, key, value):
754 key = 'branch.%s.%s' % (self.name, key)
755 return self._config.SetString(key, value)
756
David Pursehouse8a68ff92012-09-24 12:15:13 +0900757 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900759 return self._config.GetString(key, all_keys = all_keys)