blob: 32879ec7b7a779f84f80cfacfdc48908f5293c7c [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
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
Chirayu Desai217ea7d2013-03-01 19:14:38 +053019import pickle
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
Shawn O. Pearcefb231612009-04-10 18:53:46 -070021import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import sys
Doug Anderson0048b692010-12-21 13:39:23 -080023try:
24 import threading as _threading
25except ImportError:
26 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070027import time
David Pursehouse59bbb582013-05-17 10:49:33 +090028
29from pyversion import is_python3
30if is_python3():
Sarah Owens1f7627f2012-10-31 09:21:55 -070031 import urllib.request
32 import urllib.error
33else:
David Pursehouse59bbb582013-05-17 10:49:33 +090034 import urllib2
Sarah Owens1f7627f2012-10-31 09:21:55 -070035 import imp
36 urllib = imp.new_module('urllib')
37 urllib.request = urllib2
38 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070039
Shawn O. Pearcefb231612009-04-10 18:53:46 -070040from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080041from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070042from trace import Trace
David Pursehouseecf8f2b2013-05-24 12:12:23 +090043if is_python3():
44 from http.client import HTTPException
45else:
46 from httplib import HTTPException
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070047
48from git_command import GitCommand
49from git_command import ssh_sock
50from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051
52R_HEADS = 'refs/heads/'
53R_TAGS = 'refs/tags/'
David Pursehouse1d947b32012-10-25 12:23:11 +090054ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055
Shawn O. Pearce146fe902009-03-25 14:06:43 -070056REVIEW_CACHE = dict()
57
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070058def IsId(rev):
59 return ID_RE.match(rev)
60
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070061def _key(name):
62 parts = name.split('.')
63 if len(parts) < 2:
64 return name.lower()
65 parts[ 0] = parts[ 0].lower()
66 parts[-1] = parts[-1].lower()
67 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068
69class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070070 _ForUser = None
71
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072 @classmethod
73 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070074 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090075 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070076 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077
78 @classmethod
79 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090080 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081 defaults = defaults)
82
David Pursehouse8a68ff92012-09-24 12:15:13 +090083 def __init__(self, configfile, defaults=None, pickleFile=None):
84 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070085 self.defaults = defaults
86 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070087 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070088 self._remotes = {}
89 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070090
91 if pickleFile is None:
92 self._pickle = os.path.join(
93 os.path.dirname(self.file),
94 '.repopickle_' + os.path.basename(self.file))
95 else:
96 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070097
98 def Has(self, name, include_defaults = True):
99 """Return true if this configuration file has the key.
100 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700101 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700102 return True
103 if include_defaults and self.defaults:
104 return self.defaults.Has(name, include_defaults = True)
105 return False
106
107 def GetBoolean(self, name):
108 """Returns a boolean from the configuration file.
109 None : The value was not defined, or is not a boolean.
110 True : The value was set to true or yes.
111 False: The value was set to false or no.
112 """
113 v = self.GetString(name)
114 if v is None:
115 return None
116 v = v.lower()
117 if v in ('true', 'yes'):
118 return True
119 if v in ('false', 'no'):
120 return False
121 return None
122
David Pursehouse8a68ff92012-09-24 12:15:13 +0900123 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700124 """Get the first value for a key, or None if it is not defined.
125
126 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900127 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700130 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131 except KeyError:
132 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900133 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700134 v = []
135
David Pursehouse8a68ff92012-09-24 12:15:13 +0900136 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137 if v:
138 return v[0]
139 return None
140
141 r = []
142 r.extend(v)
143 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900144 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700145 return r
146
147 def SetString(self, name, value):
148 """Set the value(s) for a key.
149 Only this configuration file is modified.
150
151 The supplied value should be either a string,
152 or a list of strings (to store multiple values).
153 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700154 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700155
156 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700157 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 except KeyError:
159 old = []
160
161 if value is None:
162 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700163 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164 self._do('--unset-all', name)
165
166 elif isinstance(value, list):
167 if len(value) == 0:
168 self.SetString(name, None)
169
170 elif len(value) == 1:
171 self.SetString(name, value[0])
172
173 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700174 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700175 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700176 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 self._do('--add', name, value[i])
178
179 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700180 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700181 self._do('--replace-all', name, value)
182
183 def GetRemote(self, name):
184 """Get the remote.$name.* configuration values as an object.
185 """
186 try:
187 r = self._remotes[name]
188 except KeyError:
189 r = Remote(self, name)
190 self._remotes[r.name] = r
191 return r
192
193 def GetBranch(self, name):
194 """Get the branch.$name.* configuration values as an object.
195 """
196 try:
197 b = self._branches[name]
198 except KeyError:
199 b = Branch(self, name)
200 self._branches[b.name] = b
201 return b
202
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700203 def GetSubSections(self, section):
204 """List all subsection names matching $section.*.*
205 """
206 return self._sections.get(section, set())
207
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700208 def HasSection(self, section, subsection = ''):
209 """Does at least one key in section.subsection exist?
210 """
211 try:
212 return subsection in self._sections[section]
213 except KeyError:
214 return False
215
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700216 def UrlInsteadOf(self, url):
217 """Resolve any url.*.insteadof references.
218 """
219 for new_url in self.GetSubSections('url'):
220 old_url = self.GetString('url.%s.insteadof' % new_url)
221 if old_url is not None and url.startswith(old_url):
222 return new_url + url[len(old_url):]
223 return url
224
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700225 @property
226 def _sections(self):
227 d = self._section_dict
228 if d is None:
229 d = {}
230 for name in self._cache.keys():
231 p = name.split('.')
232 if 2 == len(p):
233 section = p[0]
234 subsect = ''
235 else:
236 section = p[0]
237 subsect = '.'.join(p[1:-1])
238 if section not in d:
239 d[section] = set()
240 d[section].add(subsect)
241 self._section_dict = d
242 return d
243
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700244 @property
245 def _cache(self):
246 if self._cache_dict is None:
247 self._cache_dict = self._Read()
248 return self._cache_dict
249
250 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700251 d = self._ReadPickle()
252 if d is None:
253 d = self._ReadGit()
254 self._SavePickle(d)
255 return d
256
257 def _ReadPickle(self):
258 try:
259 if os.path.getmtime(self._pickle) \
260 <= os.path.getmtime(self.file):
261 os.remove(self._pickle)
262 return None
263 except OSError:
264 return None
265 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700266 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700267 fd = open(self._pickle, 'rb')
268 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530269 return pickle.load(fd)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700270 finally:
271 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700272 except EOFError:
273 os.remove(self._pickle)
274 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700275 except IOError:
276 os.remove(self._pickle)
277 return None
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530278 except pickle.PickleError:
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700279 os.remove(self._pickle)
280 return None
281
282 def _SavePickle(self, cache):
283 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700284 fd = open(self._pickle, 'wb')
285 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530286 pickle.dump(cache, fd, pickle.HIGHEST_PROTOCOL)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700287 finally:
288 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700289 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700290 if os.path.exists(self._pickle):
291 os.remove(self._pickle)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530292 except pickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700293 if os.path.exists(self._pickle):
294 os.remove(self._pickle)
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.
299
300 This internal method populates the GitConfig cache.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700301
David Aguilar438c5472009-06-28 15:09:16 -0700302 """
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()
414 try:
Josh Guilfoyle71985722009-08-16 09:44:40 -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
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700422
Doug Anderson0048b692010-12-21 13:39:23 -0800423 if key in _master_keys:
424 return True
Shawn O. Pearcefb231612009-04-10 18:53:46 -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
Doug Anderson06d029c2010-10-27 17:06:01 -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 Anderson06d029c2010-10-27 17:06:01 -0700439
Doug Anderson0048b692010-12-21 13:39:23 -0800440 # 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()
Josh Guilfoyle71985722009-08-16 09:44:40 -0700452
Doug Anderson0048b692010-12-21 13:39:23 -0800453 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
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700462
Doug Anderson0048b692010-12-21 13:39:23 -0800463 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
475 _master_processes.append(p)
476 _master_keys.add(key)
477 time.sleep(1)
478 return True
479 finally:
480 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700481
482def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800483 global _master_keys_lock
484
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700485 terminate_ssh_clients()
486
Doug Anderson06d029c2010-10-27 17:06:01 -0700487 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700488 try:
489 os.kill(p.pid, SIGTERM)
490 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700491 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700492 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700493 del _master_processes[:]
494 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700495
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700496 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700497 if d:
498 try:
499 os.rmdir(os.path.dirname(d))
500 except OSError:
501 pass
502
Doug Anderson0048b692010-12-21 13:39:23 -0800503 # We're done with the lock, so we can delete it.
504 _master_keys_lock = None
505
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700506URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700507URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700508
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700509def GetSchemeFromUrl(url):
510 m = URI_ALL.match(url)
511 if m:
512 return m.group(1)
513 return None
514
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700515def _preconnect(url):
516 m = URI_ALL.match(url)
517 if m:
518 scheme = m.group(1)
519 host = m.group(2)
520 if ':' in host:
521 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700522 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700523 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700524 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
525 return _open_ssh(host, port)
526 return False
527
528 m = URI_SCP.match(url)
529 if m:
530 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700531 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700532
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700533 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700534
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700535class Remote(object):
536 """Configuration options related to a remote.
537 """
538 def __init__(self, config, name):
539 self._config = config
540 self.name = name
541 self.url = self._Get('url')
542 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800543 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530544 self.fetch = list(map(RefSpec.FromString,
545 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800546 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800547
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100548 def _InsteadOf(self):
549 globCfg = GitConfig.ForUser()
550 urlList = globCfg.GetSubSections('url')
551 longest = ""
552 longestUrl = ""
553
554 for url in urlList:
555 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900556 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100557
558 for insteadOf in insteadOfList:
559 if self.url.startswith(insteadOf) \
560 and len(insteadOf) > len(longest):
561 longest = insteadOf
562 longestUrl = url
563
564 if len(longest) == 0:
565 return self.url
566
567 return self.url.replace(longest, longestUrl, 1)
568
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700569 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100570 connectionUrl = self._InsteadOf()
571 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700572
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800573 def ReviewUrl(self, userEmail):
574 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800575 if self.review is None:
576 return None
577
578 u = self.review
Steve Pucci143d8a72014-01-30 09:45:53 -0800579 if u.split(':')[0] not in ('http', 'https', 'sso'):
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800580 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700581 if u.endswith('/Gerrit'):
582 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800583 if u.endswith('/ssh_info'):
584 u = u[:len(u) - len('/ssh_info')]
585 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900586 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800587 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800588
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700589 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800590 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700591 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800592 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
593 self._review_url = self._SshReviewUrl(userEmail, host, port)
594 REVIEW_CACHE[u] = self._review_url
Steve Pucci143d8a72014-01-30 09:45:53 -0800595 elif u.startswith('sso:'):
596 self._review_url = u # Assume it's right
597 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700598 else:
599 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800600 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700601 info = urllib.request.urlopen(info_url).read()
Conley Owens745a39b2013-06-05 13:16:18 -0700602 if info == 'NOT_AVAILABLE' or '<' in info:
603 # If `info` contains '<', we assume the server gave us some sort
604 # of HTML response back, like maybe a login page.
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700605 #
Conley Owens745a39b2013-06-05 13:16:18 -0700606 # Assume HTTP if SSH is not enabled or ssh_info doesn't look right.
Conley Owens2cd38a02014-02-04 15:32:29 -0800607 self._review_url = http_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700608 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800609 host, port = info.split()
610 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700611 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800612 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700613 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700614 raise UploadError('%s: %s' % (self.review, str(e)))
David Pursehouseecf8f2b2013-05-24 12:12:23 +0900615 except HTTPException as e:
616 raise UploadError('%s: %s' % (self.review, e.__class__.__name__))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800617
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800618 REVIEW_CACHE[u] = self._review_url
619 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800620
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800621 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700622 username = self._config.GetString('review.%s.username' % self.review)
623 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800624 username = userEmail.split('@')[0]
625 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700626
627 def ToLocal(self, rev):
628 """Convert a remote revision string to something we have locally.
629 """
630 if IsId(rev):
631 return rev
632 if rev.startswith(R_TAGS):
633 return rev
634
635 if not rev.startswith('refs/'):
636 rev = R_HEADS + rev
637
638 for spec in self.fetch:
639 if spec.SourceMatches(rev):
640 return spec.MapSource(rev)
641 raise GitError('remote %s does not have %s' % (self.name, rev))
642
643 def WritesTo(self, ref):
644 """True if the remote stores to the tracking ref.
645 """
646 for spec in self.fetch:
647 if spec.DestMatches(ref):
648 return True
649 return False
650
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800651 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700652 """Set the fetch refspec to its default value.
653 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800654 if mirror:
655 dst = 'refs/heads/*'
656 else:
657 dst = 'refs/remotes/%s/*' % self.name
658 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659
660 def Save(self):
661 """Save this remote to the configuration.
662 """
663 self._Set('url', self.url)
664 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800665 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530666 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667
668 def _Set(self, key, value):
669 key = 'remote.%s.%s' % (self.name, key)
670 return self._config.SetString(key, value)
671
David Pursehouse8a68ff92012-09-24 12:15:13 +0900672 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700673 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900674 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700675
676
677class Branch(object):
678 """Configuration options related to a single branch.
679 """
680 def __init__(self, config, name):
681 self._config = config
682 self.name = name
683 self.merge = self._Get('merge')
684
685 r = self._Get('remote')
686 if r:
687 self.remote = self._config.GetRemote(r)
688 else:
689 self.remote = None
690
691 @property
692 def LocalMerge(self):
693 """Convert the merge spec to a local name.
694 """
695 if self.remote and self.merge:
696 return self.remote.ToLocal(self.merge)
697 return None
698
699 def Save(self):
700 """Save this branch back into the configuration.
701 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700702 if self._config.HasSection('branch', self.name):
703 if self.remote:
704 self._Set('remote', self.remote.name)
705 else:
706 self._Set('remote', None)
707 self._Set('merge', self.merge)
708
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700709 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700710 fd = open(self._config.file, 'ab')
711 try:
712 fd.write('[branch "%s"]\n' % self.name)
713 if self.remote:
714 fd.write('\tremote = %s\n' % self.remote.name)
715 if self.merge:
716 fd.write('\tmerge = %s\n' % self.merge)
717 finally:
718 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719
720 def _Set(self, key, value):
721 key = 'branch.%s.%s' % (self.name, key)
722 return self._config.SetString(key, value)
723
David Pursehouse8a68ff92012-09-24 12:15:13 +0900724 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700725 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900726 return self._config.GetString(key, all_keys = all_keys)