blob: 8c247394edc1b4069818e24d3e64cf5baf25e549 [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
Zac Livingston9ead97b2017-06-13 08:29:04 -060053from git_refs import R_CHANGES, R_HEADS, R_TAGS
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070054
David Pursehouse1d947b32012-10-25 12:23:11 +090055ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056
Shawn O. Pearce146fe902009-03-25 14:06:43 -070057REVIEW_CACHE = dict()
58
Zac Livingston9ead97b2017-06-13 08:29:04 -060059def IsChange(rev):
60 return rev.startswith(R_CHANGES)
61
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070062def IsId(rev):
63 return ID_RE.match(rev)
64
Zac Livingston9ead97b2017-06-13 08:29:04 -060065def IsTag(rev):
66 return rev.startswith(R_TAGS)
67
68def IsImmutable(rev):
69 return IsChange(rev) or IsId(rev) or IsTag(rev)
70
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070071def _key(name):
72 parts = name.split('.')
73 if len(parts) < 2:
74 return name.lower()
75 parts[ 0] = parts[ 0].lower()
76 parts[-1] = parts[-1].lower()
77 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070078
79class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070080 _ForUser = None
81
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082 @classmethod
83 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070084 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090085 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070086 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070087
88 @classmethod
89 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090090 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070091 defaults = defaults)
92
Anthony King85b24ac2014-05-06 15:57:48 +010093 def __init__(self, configfile, defaults=None, jsonFile=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090094 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095 self.defaults = defaults
96 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070097 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098 self._remotes = {}
99 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700100
Anthony King85b24ac2014-05-06 15:57:48 +0100101 self._json = jsonFile
102 if self._json is None:
103 self._json = os.path.join(
Shawn O. Pearce1b34c912009-05-21 18:52:49 -0700104 os.path.dirname(self.file),
Anthony King85b24ac2014-05-06 15:57:48 +0100105 '.repo_' + os.path.basename(self.file) + '.json')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700106
107 def Has(self, name, include_defaults = True):
108 """Return true if this configuration file has the key.
109 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700110 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700111 return True
112 if include_defaults and self.defaults:
113 return self.defaults.Has(name, include_defaults = True)
114 return False
115
116 def GetBoolean(self, name):
117 """Returns a boolean from the configuration file.
118 None : The value was not defined, or is not a boolean.
119 True : The value was set to true or yes.
120 False: The value was set to false or no.
121 """
122 v = self.GetString(name)
123 if v is None:
124 return None
125 v = v.lower()
126 if v in ('true', 'yes'):
127 return True
128 if v in ('false', 'no'):
129 return False
130 return None
131
David Pursehouse8a68ff92012-09-24 12:15:13 +0900132 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 """Get the first value for a key, or None if it is not defined.
134
135 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900136 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700138 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700139 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140 except KeyError:
141 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900142 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700143 v = []
144
David Pursehouse8a68ff92012-09-24 12:15:13 +0900145 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146 if v:
147 return v[0]
148 return None
149
150 r = []
151 r.extend(v)
152 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900153 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 return r
155
156 def SetString(self, name, value):
157 """Set the value(s) for a key.
158 Only this configuration file is modified.
159
160 The supplied value should be either a string,
161 or a list of strings (to store multiple values).
162 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700163 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164
165 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700166 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167 except KeyError:
168 old = []
169
170 if value is None:
171 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700172 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173 self._do('--unset-all', name)
174
175 elif isinstance(value, list):
176 if len(value) == 0:
177 self.SetString(name, None)
178
179 elif len(value) == 1:
180 self.SetString(name, value[0])
181
182 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700183 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700184 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700185 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700186 self._do('--add', name, value[i])
187
188 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700189 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700190 self._do('--replace-all', name, value)
191
192 def GetRemote(self, name):
193 """Get the remote.$name.* configuration values as an object.
194 """
195 try:
196 r = self._remotes[name]
197 except KeyError:
198 r = Remote(self, name)
199 self._remotes[r.name] = r
200 return r
201
202 def GetBranch(self, name):
203 """Get the branch.$name.* configuration values as an object.
204 """
205 try:
206 b = self._branches[name]
207 except KeyError:
208 b = Branch(self, name)
209 self._branches[b.name] = b
210 return b
211
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700212 def GetSubSections(self, section):
213 """List all subsection names matching $section.*.*
214 """
215 return self._sections.get(section, set())
216
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700217 def HasSection(self, section, subsection = ''):
218 """Does at least one key in section.subsection exist?
219 """
220 try:
221 return subsection in self._sections[section]
222 except KeyError:
223 return False
224
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700225 def UrlInsteadOf(self, url):
226 """Resolve any url.*.insteadof references.
227 """
228 for new_url in self.GetSubSections('url'):
Dan Willemsen4e4d40f2013-10-28 22:28:42 -0700229 for old_url in self.GetString('url.%s.insteadof' % new_url, True):
230 if old_url is not None and url.startswith(old_url):
231 return new_url + url[len(old_url):]
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700232 return url
233
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700234 @property
235 def _sections(self):
236 d = self._section_dict
237 if d is None:
238 d = {}
239 for name in self._cache.keys():
240 p = name.split('.')
241 if 2 == len(p):
242 section = p[0]
243 subsect = ''
244 else:
245 section = p[0]
246 subsect = '.'.join(p[1:-1])
247 if section not in d:
248 d[section] = set()
249 d[section].add(subsect)
250 self._section_dict = d
251 return d
252
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700253 @property
254 def _cache(self):
255 if self._cache_dict is None:
256 self._cache_dict = self._Read()
257 return self._cache_dict
258
259 def _Read(self):
Anthony King85b24ac2014-05-06 15:57:48 +0100260 d = self._ReadJson()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700261 if d is None:
262 d = self._ReadGit()
Anthony King85b24ac2014-05-06 15:57:48 +0100263 self._SaveJson(d)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700264 return d
265
Anthony King85b24ac2014-05-06 15:57:48 +0100266 def _ReadJson(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700267 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100268 if os.path.getmtime(self._json) \
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700269 <= os.path.getmtime(self.file):
Anthony King85b24ac2014-05-06 15:57:48 +0100270 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700271 return None
272 except OSError:
273 return None
274 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100275 Trace(': parsing %s', self.file)
276 fd = open(self._json)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700277 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100278 return json.load(fd)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700279 finally:
280 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100281 except (IOError, ValueError):
282 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700283 return None
284
Anthony King85b24ac2014-05-06 15:57:48 +0100285 def _SaveJson(self, cache):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700286 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100287 fd = open(self._json, 'w')
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700288 try:
Anthony King85b24ac2014-05-06 15:57:48 +0100289 json.dump(cache, fd, indent=2)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700290 finally:
291 fd.close()
Anthony King85b24ac2014-05-06 15:57:48 +0100292 except (IOError, TypeError):
Anthony Kingb1d1fd72015-06-03 17:02:26 +0100293 if os.path.exists(self._json):
Anthony King85b24ac2014-05-06 15:57:48 +0100294 os.remove(self._json)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700295
296 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700297 """
298 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700299
David Aguilar438c5472009-06-28 15:09:16 -0700300 This internal method populates the GitConfig cache.
301
302 """
David Aguilar438c5472009-06-28 15:09:16 -0700303 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700304 d = self._do('--null', '--list')
305 if d is None:
306 return c
Chirayu Desai0eb35cb2013-11-19 18:46:29 +0530307 for line in d.decode('utf-8').rstrip('\0').split('\0'): # pylint: disable=W1401
308 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700309 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900310 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700311 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900312 key = line
313 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700314
315 if key in c:
316 c[key].append(val)
317 else:
318 c[key] = [val]
319
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700320 return c
321
322 def _do(self, *args):
323 command = ['config', '--file', self.file]
324 command.extend(args)
325
326 p = GitCommand(None,
327 command,
328 capture_stdout = True,
329 capture_stderr = True)
330 if p.Wait() == 0:
331 return p.stdout
332 else:
333 GitError('git config %s: %s' % (str(args), p.stderr))
334
335
336class RefSpec(object):
337 """A Git refspec line, split into its components:
338
339 forced: True if the line starts with '+'
340 src: Left side of the line
341 dst: Right side of the line
342 """
343
344 @classmethod
345 def FromString(cls, rs):
346 lhs, rhs = rs.split(':', 2)
347 if lhs.startswith('+'):
348 lhs = lhs[1:]
349 forced = True
350 else:
351 forced = False
352 return cls(forced, lhs, rhs)
353
354 def __init__(self, forced, lhs, rhs):
355 self.forced = forced
356 self.src = lhs
357 self.dst = rhs
358
359 def SourceMatches(self, rev):
360 if self.src:
361 if rev == self.src:
362 return True
363 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
364 return True
365 return False
366
367 def DestMatches(self, ref):
368 if self.dst:
369 if ref == self.dst:
370 return True
371 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
372 return True
373 return False
374
375 def MapSource(self, rev):
376 if self.src.endswith('/*'):
377 return self.dst[:-1] + rev[len(self.src) - 1:]
378 return self.dst
379
380 def __str__(self):
381 s = ''
382 if self.forced:
383 s += '+'
384 if self.src:
385 s += self.src
386 if self.dst:
387 s += ':'
388 s += self.dst
389 return s
390
391
Doug Anderson06d029c2010-10-27 17:06:01 -0700392_master_processes = []
393_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700394_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800395_master_keys_lock = None
396
397def init_ssh():
398 """Should be called once at the start of repo to init ssh master handling.
399
400 At the moment, all we do is to create our lock.
401 """
402 global _master_keys_lock
403 assert _master_keys_lock is None, "Should only call init_ssh once"
404 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700405
Josh Guilfoyle71985722009-08-16 09:44:40 -0700406def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700407 global _ssh_master
408
Doug Anderson0048b692010-12-21 13:39:23 -0800409 # Acquire the lock. This is needed to prevent opening multiple masters for
410 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
411 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
412 # one that was passed to repo init.
413 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700414 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700415
Doug Anderson0048b692010-12-21 13:39:23 -0800416 # Check to see whether we already think that the master is running; if we
417 # think it's already running, return right away.
418 if port is not None:
419 key = '%s:%s' % (host, port)
420 else:
421 key = host
422
423 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700424 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700425
Doug Anderson0048b692010-12-21 13:39:23 -0800426 if not _ssh_master \
427 or 'GIT_SSH' in os.environ \
428 or sys.platform in ('win32', 'cygwin'):
429 # failed earlier, or cygwin ssh can't do this
430 #
431 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700432
Doug Anderson0048b692010-12-21 13:39:23 -0800433 # We will make two calls to ssh; this is the common part of both calls.
434 command_base = ['ssh',
435 '-o','ControlPath %s' % ssh_sock(),
436 host]
437 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900438 command_base[1:1] = ['-p', str(port)]
Doug Anderson0048b692010-12-21 13:39:23 -0800439
440 # Since the key wasn't in _master_keys, we think that master isn't running.
441 # ...but before actually starting a master, we'll double-check. This can
442 # be important because we can't tell that that 'git@myhost.com' is the same
443 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
444 check_command = command_base + ['-O','check']
445 try:
446 Trace(': %s', ' '.join(check_command))
447 check_process = subprocess.Popen(check_command,
448 stdout=subprocess.PIPE,
449 stderr=subprocess.PIPE)
450 check_process.communicate() # read output, but ignore it...
451 isnt_running = check_process.wait()
452
453 if not isnt_running:
454 # Our double-check found that the master _was_ infact running. Add to
455 # the list of keys.
456 _master_keys.add(key)
457 return True
458 except Exception:
459 # Ignore excpetions. We we will fall back to the normal command and print
460 # to the log there.
461 pass
462
463 command = command_base[:1] + \
464 ['-M', '-N'] + \
465 command_base[1:]
466 try:
467 Trace(': %s', ' '.join(command))
468 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700469 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800470 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700471 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
472 % (host,port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800473 return False
474
Timo Lotterbach05dc46b2016-07-15 16:48:42 +0200475 time.sleep(1)
476 ssh_died = (p.poll() is not None)
477 if ssh_died:
478 return False
479
Doug Anderson0048b692010-12-21 13:39:23 -0800480 _master_processes.append(p)
481 _master_keys.add(key)
Doug Anderson0048b692010-12-21 13:39:23 -0800482 return True
483 finally:
484 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700485
486def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800487 global _master_keys_lock
488
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700489 terminate_ssh_clients()
490
Doug Anderson06d029c2010-10-27 17:06:01 -0700491 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700492 try:
493 os.kill(p.pid, SIGTERM)
494 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700495 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700496 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700497 del _master_processes[:]
498 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700499
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700500 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700501 if d:
502 try:
503 os.rmdir(os.path.dirname(d))
504 except OSError:
505 pass
506
Doug Anderson0048b692010-12-21 13:39:23 -0800507 # We're done with the lock, so we can delete it.
508 _master_keys_lock = None
509
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700510URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700511URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700512
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700513def GetSchemeFromUrl(url):
514 m = URI_ALL.match(url)
515 if m:
516 return m.group(1)
517 return None
518
Dan Willemsen0745bb22015-08-17 13:41:45 -0700519@contextlib.contextmanager
520def GetUrlCookieFile(url, quiet):
521 if url.startswith('persistent-'):
522 try:
523 p = subprocess.Popen(
524 ['git-remote-persistent-https', '-print_config', url],
525 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
526 stderr=subprocess.PIPE)
527 try:
528 cookieprefix = 'http.cookiefile='
529 proxyprefix = 'http.proxy='
530 cookiefile = None
531 proxy = None
532 for line in p.stdout:
533 line = line.strip()
534 if line.startswith(cookieprefix):
535 cookiefile = line[len(cookieprefix):]
536 if line.startswith(proxyprefix):
537 proxy = line[len(proxyprefix):]
538 # Leave subprocess open, as cookie file may be transient.
539 if cookiefile or proxy:
540 yield cookiefile, proxy
541 return
542 finally:
543 p.stdin.close()
544 if p.wait():
545 err_msg = p.stderr.read()
546 if ' -print_config' in err_msg:
547 pass # Persistent proxy doesn't support -print_config.
548 elif not quiet:
549 print(err_msg, file=sys.stderr)
550 except OSError as e:
551 if e.errno == errno.ENOENT:
552 pass # No persistent proxy.
553 raise
554 yield GitConfig.ForUser().GetString('http.cookiefile'), None
555
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700556def _preconnect(url):
557 m = URI_ALL.match(url)
558 if m:
559 scheme = m.group(1)
560 host = m.group(2)
561 if ':' in host:
562 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700563 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700564 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700565 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
566 return _open_ssh(host, port)
567 return False
568
569 m = URI_SCP.match(url)
570 if m:
571 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700572 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700573
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700574 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700575
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700576class Remote(object):
577 """Configuration options related to a remote.
578 """
579 def __init__(self, config, name):
580 self._config = config
581 self.name = name
582 self.url = self._Get('url')
Steve Raed6480452016-08-10 15:00:00 -0700583 self.pushUrl = self._Get('pushurl')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700584 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800585 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530586 self.fetch = list(map(RefSpec.FromString,
587 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800588 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800589
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100590 def _InsteadOf(self):
591 globCfg = GitConfig.ForUser()
592 urlList = globCfg.GetSubSections('url')
593 longest = ""
594 longestUrl = ""
595
596 for url in urlList:
597 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900598 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100599
600 for insteadOf in insteadOfList:
601 if self.url.startswith(insteadOf) \
602 and len(insteadOf) > len(longest):
603 longest = insteadOf
604 longestUrl = url
605
606 if len(longest) == 0:
607 return self.url
608
609 return self.url.replace(longest, longestUrl, 1)
610
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700611 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100612 connectionUrl = self._InsteadOf()
613 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700614
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800615 def ReviewUrl(self, userEmail):
616 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800617 if self.review is None:
618 return None
619
620 u = self.review
Conley Owens7e12e0a2014-10-23 15:40:00 -0700621 if u.startswith('persistent-'):
622 u = u[len('persistent-'):]
Steve Pucci143d8a72014-01-30 09:45:53 -0800623 if u.split(':')[0] not in ('http', 'https', 'sso'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800624 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700625 if u.endswith('/Gerrit'):
626 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800627 if u.endswith('/ssh_info'):
628 u = u[:len(u) - len('/ssh_info')]
629 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900630 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800631 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800632
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700633 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800634 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700635 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800636 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
637 self._review_url = self._SshReviewUrl(userEmail, host, port)
638 REVIEW_CACHE[u] = self._review_url
Steve Pucci143d8a72014-01-30 09:45:53 -0800639 elif u.startswith('sso:'):
640 self._review_url = u # Assume it's right
641 REVIEW_CACHE[u] = self._review_url
Timo Lotterbacheec726c2016-10-07 10:52:08 +0200642 elif 'REPO_IGNORE_SSH_INFO' in os.environ:
643 self._review_url = http_url
644 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700645 else:
646 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800647 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700648 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700649 if info == 'NOT_AVAILABLE' or '<' in info:
650 # If `info` contains '<', we assume the server gave us some sort
651 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700652 #
Conley Owens745a39b2013-06-05 13:16:18 -0700653 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800654 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700655 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800656 host, port = info.split()
Dan Willemsen16889ba2016-09-22 16:39:06 +0000657 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700658 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800659 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700660 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700661 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900662 except HTTPException as e:
663 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800664
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800665 REVIEW_CACHE[u] = self._review_url
666 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800667
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800668 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700669 username = self._config.GetString('review.%s.username' % self.review)
670 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800671 username = userEmail.split('@')[0]
672 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700673
674 def ToLocal(self, rev):
675 """Convert a remote revision string to something we have locally.
676 """
Yann Droneaud936183a2013-09-12 10:51:18 +0200677 if self.name == '.' or IsId(rev):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700678 return rev
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700679
680 if not rev.startswith('refs/'):
681 rev = R_HEADS + rev
682
683 for spec in self.fetch:
684 if spec.SourceMatches(rev):
685 return spec.MapSource(rev)
Nasser Grainawi909d58b2014-09-19 12:13:04 -0600686
687 if not rev.startswith(R_HEADS):
688 return rev
689
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700690 raise GitError('remote %s does not have %s' % (self.name, rev))
691
692 def WritesTo(self, ref):
693 """True if the remote stores to the tracking ref.
694 """
695 for spec in self.fetch:
696 if spec.DestMatches(ref):
697 return True
698 return False
699
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800700 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701 """Set the fetch refspec to its default value.
702 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800703 if mirror:
704 dst = 'refs/heads/*'
705 else:
706 dst = 'refs/remotes/%s/*' % self.name
707 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708
709 def Save(self):
710 """Save this remote to the configuration.
711 """
712 self._Set('url', self.url)
Steve Raed6480452016-08-10 15:00:00 -0700713 if self.pushUrl is not None:
714 self._Set('pushurl', self.pushUrl + '/' + self.projectname)
715 else:
716 self._Set('pushurl', self.pushUrl)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700717 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800718 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530719 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720
721 def _Set(self, key, value):
722 key = 'remote.%s.%s' % (self.name, key)
723 return self._config.SetString(key, value)
724
David Pursehouse8a68ff92012-09-24 12:15:13 +0900725 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700726 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900727 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700728
729
730class Branch(object):
731 """Configuration options related to a single branch.
732 """
733 def __init__(self, config, name):
734 self._config = config
735 self.name = name
736 self.merge = self._Get('merge')
737
738 r = self._Get('remote')
739 if r:
740 self.remote = self._config.GetRemote(r)
741 else:
742 self.remote = None
743
744 @property
745 def LocalMerge(self):
746 """Convert the merge spec to a local name.
747 """
748 if self.remote and self.merge:
749 return self.remote.ToLocal(self.merge)
750 return None
751
752 def Save(self):
753 """Save this branch back into the configuration.
754 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700755 if self._config.HasSection('branch', self.name):
756 if self.remote:
757 self._Set('remote', self.remote.name)
758 else:
759 self._Set('remote', None)
760 self._Set('merge', self.merge)
761
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700762 else:
Chirayu Desai303a82f2014-08-19 22:57:17 +0530763 fd = open(self._config.file, 'a')
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700764 try:
765 fd.write('[branch "%s"]\n' % self.name)
766 if self.remote:
767 fd.write('\tremote = %s\n' % self.remote.name)
768 if self.merge:
769 fd.write('\tmerge = %s\n' % self.merge)
770 finally:
771 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772
773 def _Set(self, key, value):
774 key = 'branch.%s.%s' % (self.name, key)
775 return self._config.SetString(key, value)
776
David Pursehouse8a68ff92012-09-24 12:15:13 +0900777 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700778 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900779 return self._config.GetString(key, all_keys = all_keys)