blob: f60893ee733c8774e08a1b4adc947f91bb489b48 [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
Shawn O. Pearcec12c3602009-04-17 21:03:32 -070016import cPickle
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070017import os
18import re
Shawn O. Pearcefb231612009-04-10 18:53:46 -070019import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import sys
Doug Anderson0048b692010-12-21 13:39:23 -080021try:
22 import threading as _threading
23except ImportError:
24 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070025import time
Sarah Owens1f7627f2012-10-31 09:21:55 -070026try:
27 import urllib2
28except ImportError:
29 # For python3
30 import urllib.request
31 import urllib.error
32else:
33 # For python2
34 import imp
35 urllib = imp.new_module('urllib')
36 urllib.request = urllib2
37 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070038
Shawn O. Pearcefb231612009-04-10 18:53:46 -070039from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080040from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070041from trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070042
43from git_command import GitCommand
44from git_command import ssh_sock
45from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
47R_HEADS = 'refs/heads/'
48R_TAGS = 'refs/tags/'
David Pursehouse1d947b32012-10-25 12:23:11 +090049ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070050
Shawn O. Pearce146fe902009-03-25 14:06:43 -070051REVIEW_CACHE = dict()
52
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070053def IsId(rev):
54 return ID_RE.match(rev)
55
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070056def _key(name):
57 parts = name.split('.')
58 if len(parts) < 2:
59 return name.lower()
60 parts[ 0] = parts[ 0].lower()
61 parts[-1] = parts[-1].lower()
62 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063
64class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070065 _ForUser = None
66
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067 @classmethod
68 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070069 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090070 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070071 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072
73 @classmethod
74 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090075 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070076 defaults = defaults)
77
David Pursehouse8a68ff92012-09-24 12:15:13 +090078 def __init__(self, configfile, defaults=None, pickleFile=None):
79 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070080 self.defaults = defaults
81 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070082 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070083 self._remotes = {}
84 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070085
86 if pickleFile is None:
87 self._pickle = os.path.join(
88 os.path.dirname(self.file),
89 '.repopickle_' + os.path.basename(self.file))
90 else:
91 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070092
93 def Has(self, name, include_defaults = True):
94 """Return true if this configuration file has the key.
95 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070096 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070097 return True
98 if include_defaults and self.defaults:
99 return self.defaults.Has(name, include_defaults = True)
100 return False
101
102 def GetBoolean(self, name):
103 """Returns a boolean from the configuration file.
104 None : The value was not defined, or is not a boolean.
105 True : The value was set to true or yes.
106 False: The value was set to false or no.
107 """
108 v = self.GetString(name)
109 if v is None:
110 return None
111 v = v.lower()
112 if v in ('true', 'yes'):
113 return True
114 if v in ('false', 'no'):
115 return False
116 return None
117
David Pursehouse8a68ff92012-09-24 12:15:13 +0900118 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700119 """Get the first value for a key, or None if it is not defined.
120
121 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900122 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700123 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700124 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700125 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700126 except KeyError:
127 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900128 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129 v = []
130
David Pursehouse8a68ff92012-09-24 12:15:13 +0900131 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700132 if v:
133 return v[0]
134 return None
135
136 r = []
137 r.extend(v)
138 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900139 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140 return r
141
142 def SetString(self, name, value):
143 """Set the value(s) for a key.
144 Only this configuration file is modified.
145
146 The supplied value should be either a string,
147 or a list of strings (to store multiple values).
148 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700149 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700150
151 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700152 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700153 except KeyError:
154 old = []
155
156 if value is None:
157 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700158 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 self._do('--unset-all', name)
160
161 elif isinstance(value, list):
162 if len(value) == 0:
163 self.SetString(name, None)
164
165 elif len(value) == 1:
166 self.SetString(name, value[0])
167
168 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700169 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700171 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 self._do('--add', name, value[i])
173
174 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700175 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176 self._do('--replace-all', name, value)
177
178 def GetRemote(self, name):
179 """Get the remote.$name.* configuration values as an object.
180 """
181 try:
182 r = self._remotes[name]
183 except KeyError:
184 r = Remote(self, name)
185 self._remotes[r.name] = r
186 return r
187
188 def GetBranch(self, name):
189 """Get the branch.$name.* configuration values as an object.
190 """
191 try:
192 b = self._branches[name]
193 except KeyError:
194 b = Branch(self, name)
195 self._branches[b.name] = b
196 return b
197
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700198 def GetSubSections(self, section):
199 """List all subsection names matching $section.*.*
200 """
201 return self._sections.get(section, set())
202
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700203 def HasSection(self, section, subsection = ''):
204 """Does at least one key in section.subsection exist?
205 """
206 try:
207 return subsection in self._sections[section]
208 except KeyError:
209 return False
210
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700211 def UrlInsteadOf(self, url):
212 """Resolve any url.*.insteadof references.
213 """
214 for new_url in self.GetSubSections('url'):
215 old_url = self.GetString('url.%s.insteadof' % new_url)
216 if old_url is not None and url.startswith(old_url):
217 return new_url + url[len(old_url):]
218 return url
219
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700220 @property
221 def _sections(self):
222 d = self._section_dict
223 if d is None:
224 d = {}
225 for name in self._cache.keys():
226 p = name.split('.')
227 if 2 == len(p):
228 section = p[0]
229 subsect = ''
230 else:
231 section = p[0]
232 subsect = '.'.join(p[1:-1])
233 if section not in d:
234 d[section] = set()
235 d[section].add(subsect)
236 self._section_dict = d
237 return d
238
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700239 @property
240 def _cache(self):
241 if self._cache_dict is None:
242 self._cache_dict = self._Read()
243 return self._cache_dict
244
245 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700246 d = self._ReadPickle()
247 if d is None:
248 d = self._ReadGit()
249 self._SavePickle(d)
250 return d
251
252 def _ReadPickle(self):
253 try:
254 if os.path.getmtime(self._pickle) \
255 <= os.path.getmtime(self.file):
256 os.remove(self._pickle)
257 return None
258 except OSError:
259 return None
260 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700261 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700262 fd = open(self._pickle, 'rb')
263 try:
264 return cPickle.load(fd)
265 finally:
266 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700267 except EOFError:
268 os.remove(self._pickle)
269 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700270 except IOError:
271 os.remove(self._pickle)
272 return None
273 except cPickle.PickleError:
274 os.remove(self._pickle)
275 return None
276
277 def _SavePickle(self, cache):
278 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700279 fd = open(self._pickle, 'wb')
280 try:
281 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
282 finally:
283 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700284 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700285 if os.path.exists(self._pickle):
286 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700287 except cPickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700288 if os.path.exists(self._pickle):
289 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700290
291 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700292 """
293 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700294
David Aguilar438c5472009-06-28 15:09:16 -0700295 This internal method populates the GitConfig cache.
296
297 """
David Aguilar438c5472009-06-28 15:09:16 -0700298 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700299 d = self._do('--null', '--list')
300 if d is None:
301 return c
David Pursehouse1d947b32012-10-25 12:23:11 +0900302 for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
303 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700304 if '\n' in line:
305 key, val = line.split('\n', 1)
306 else:
307 key = line
308 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700309
310 if key in c:
311 c[key].append(val)
312 else:
313 c[key] = [val]
314
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700315 return c
316
317 def _do(self, *args):
318 command = ['config', '--file', self.file]
319 command.extend(args)
320
321 p = GitCommand(None,
322 command,
323 capture_stdout = True,
324 capture_stderr = True)
325 if p.Wait() == 0:
326 return p.stdout
327 else:
328 GitError('git config %s: %s' % (str(args), p.stderr))
329
330
331class RefSpec(object):
332 """A Git refspec line, split into its components:
333
334 forced: True if the line starts with '+'
335 src: Left side of the line
336 dst: Right side of the line
337 """
338
339 @classmethod
340 def FromString(cls, rs):
341 lhs, rhs = rs.split(':', 2)
342 if lhs.startswith('+'):
343 lhs = lhs[1:]
344 forced = True
345 else:
346 forced = False
347 return cls(forced, lhs, rhs)
348
349 def __init__(self, forced, lhs, rhs):
350 self.forced = forced
351 self.src = lhs
352 self.dst = rhs
353
354 def SourceMatches(self, rev):
355 if self.src:
356 if rev == self.src:
357 return True
358 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
359 return True
360 return False
361
362 def DestMatches(self, ref):
363 if self.dst:
364 if ref == self.dst:
365 return True
366 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
367 return True
368 return False
369
370 def MapSource(self, rev):
371 if self.src.endswith('/*'):
372 return self.dst[:-1] + rev[len(self.src) - 1:]
373 return self.dst
374
375 def __str__(self):
376 s = ''
377 if self.forced:
378 s += '+'
379 if self.src:
380 s += self.src
381 if self.dst:
382 s += ':'
383 s += self.dst
384 return s
385
386
Doug Anderson06d029c2010-10-27 17:06:01 -0700387_master_processes = []
388_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700389_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800390_master_keys_lock = None
391
392def init_ssh():
393 """Should be called once at the start of repo to init ssh master handling.
394
395 At the moment, all we do is to create our lock.
396 """
397 global _master_keys_lock
398 assert _master_keys_lock is None, "Should only call init_ssh once"
399 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700400
Josh Guilfoyle71985722009-08-16 09:44:40 -0700401def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700402 global _ssh_master
403
Doug Anderson0048b692010-12-21 13:39:23 -0800404 # Acquire the lock. This is needed to prevent opening multiple masters for
405 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
406 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
407 # one that was passed to repo init.
408 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700409 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700410
Doug Anderson0048b692010-12-21 13:39:23 -0800411 # Check to see whether we already think that the master is running; if we
412 # think it's already running, return right away.
413 if port is not None:
414 key = '%s:%s' % (host, port)
415 else:
416 key = host
417
418 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700419 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700420
Doug Anderson0048b692010-12-21 13:39:23 -0800421 if not _ssh_master \
422 or 'GIT_SSH' in os.environ \
423 or sys.platform in ('win32', 'cygwin'):
424 # failed earlier, or cygwin ssh can't do this
425 #
426 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700427
Doug Anderson0048b692010-12-21 13:39:23 -0800428 # We will make two calls to ssh; this is the common part of both calls.
429 command_base = ['ssh',
430 '-o','ControlPath %s' % ssh_sock(),
431 host]
432 if port is not None:
433 command_base[1:1] = ['-p',str(port)]
434
435 # Since the key wasn't in _master_keys, we think that master isn't running.
436 # ...but before actually starting a master, we'll double-check. This can
437 # be important because we can't tell that that 'git@myhost.com' is the same
438 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
439 check_command = command_base + ['-O','check']
440 try:
441 Trace(': %s', ' '.join(check_command))
442 check_process = subprocess.Popen(check_command,
443 stdout=subprocess.PIPE,
444 stderr=subprocess.PIPE)
445 check_process.communicate() # read output, but ignore it...
446 isnt_running = check_process.wait()
447
448 if not isnt_running:
449 # Our double-check found that the master _was_ infact running. Add to
450 # the list of keys.
451 _master_keys.add(key)
452 return True
453 except Exception:
454 # Ignore excpetions. We we will fall back to the normal command and print
455 # to the log there.
456 pass
457
458 command = command_base[:1] + \
459 ['-M', '-N'] + \
460 command_base[1:]
461 try:
462 Trace(': %s', ' '.join(command))
463 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700464 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800465 _ssh_master = False
466 print >>sys.stderr, \
467 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
468 % (host,port, str(e))
469 return False
470
471 _master_processes.append(p)
472 _master_keys.add(key)
473 time.sleep(1)
474 return True
475 finally:
476 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700477
478def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800479 global _master_keys_lock
480
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700481 terminate_ssh_clients()
482
Doug Anderson06d029c2010-10-27 17:06:01 -0700483 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700484 try:
485 os.kill(p.pid, SIGTERM)
486 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700487 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700488 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700489 del _master_processes[:]
490 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700491
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700492 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700493 if d:
494 try:
495 os.rmdir(os.path.dirname(d))
496 except OSError:
497 pass
498
Doug Anderson0048b692010-12-21 13:39:23 -0800499 # We're done with the lock, so we can delete it.
500 _master_keys_lock = None
501
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700502URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700503URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700504
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700505def GetSchemeFromUrl(url):
506 m = URI_ALL.match(url)
507 if m:
508 return m.group(1)
509 return None
510
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700511def _preconnect(url):
512 m = URI_ALL.match(url)
513 if m:
514 scheme = m.group(1)
515 host = m.group(2)
516 if ':' in host:
517 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700518 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700519 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700520 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
521 return _open_ssh(host, port)
522 return False
523
524 m = URI_SCP.match(url)
525 if m:
526 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700527 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700528
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700529 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700530
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531class Remote(object):
532 """Configuration options related to a remote.
533 """
534 def __init__(self, config, name):
535 self._config = config
536 self.name = name
537 self.url = self._Get('url')
538 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800539 self.projectname = self._Get('projectname')
David Pursehouse7e6dd2d2012-10-25 12:40:51 +0900540 self.fetch = map(RefSpec.FromString,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900541 self._Get('fetch', all_keys=True))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800542 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800543
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100544 def _InsteadOf(self):
545 globCfg = GitConfig.ForUser()
546 urlList = globCfg.GetSubSections('url')
547 longest = ""
548 longestUrl = ""
549
550 for url in urlList:
551 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900552 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100553
554 for insteadOf in insteadOfList:
555 if self.url.startswith(insteadOf) \
556 and len(insteadOf) > len(longest):
557 longest = insteadOf
558 longestUrl = url
559
560 if len(longest) == 0:
561 return self.url
562
563 return self.url.replace(longest, longestUrl, 1)
564
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700565 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100566 connectionUrl = self._InsteadOf()
567 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700568
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800569 def ReviewUrl(self, userEmail):
570 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800571 if self.review is None:
572 return None
573
574 u = self.review
575 if not u.startswith('http:') and not u.startswith('https:'):
576 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700577 if u.endswith('/Gerrit'):
578 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800579 if u.endswith('/ssh_info'):
580 u = u[:len(u) - len('/ssh_info')]
581 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900582 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800583 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800584
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700585 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800586 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700587 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800588 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
589 self._review_url = self._SshReviewUrl(userEmail, host, port)
590 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700591 else:
592 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800593 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700594 info = urllib.request.urlopen(info_url).read()
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700595 if '<' in info:
596 # Assume the server gave us some sort of HTML
597 # response back, like maybe a login page.
598 #
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800599 raise UploadError('%s: Cannot parse response' % info_url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800600
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800601 if info == 'NOT_AVAILABLE':
602 # Assume HTTP if SSH is not enabled.
603 self._review_url = http_url + 'p/'
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700604 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800605 host, port = info.split()
606 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700607 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800608 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700609 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700610 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800611
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800612 REVIEW_CACHE[u] = self._review_url
613 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800614
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800615 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700616 username = self._config.GetString('review.%s.username' % self.review)
617 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800618 username = userEmail.split('@')[0]
619 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700620
621 def ToLocal(self, rev):
622 """Convert a remote revision string to something we have locally.
623 """
624 if IsId(rev):
625 return rev
626 if rev.startswith(R_TAGS):
627 return rev
628
629 if not rev.startswith('refs/'):
630 rev = R_HEADS + rev
631
632 for spec in self.fetch:
633 if spec.SourceMatches(rev):
634 return spec.MapSource(rev)
635 raise GitError('remote %s does not have %s' % (self.name, rev))
636
637 def WritesTo(self, ref):
638 """True if the remote stores to the tracking ref.
639 """
640 for spec in self.fetch:
641 if spec.DestMatches(ref):
642 return True
643 return False
644
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800645 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646 """Set the fetch refspec to its default value.
647 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800648 if mirror:
649 dst = 'refs/heads/*'
650 else:
651 dst = 'refs/remotes/%s/*' % self.name
652 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654 def Save(self):
655 """Save this remote to the configuration.
656 """
657 self._Set('url', self.url)
658 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800659 self._Set('projectname', self.projectname)
David Pursehouse7e6dd2d2012-10-25 12:40:51 +0900660 self._Set('fetch', map(str, self.fetch))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700661
662 def _Set(self, key, value):
663 key = 'remote.%s.%s' % (self.name, key)
664 return self._config.SetString(key, value)
665
David Pursehouse8a68ff92012-09-24 12:15:13 +0900666 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900668 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669
670
671class Branch(object):
672 """Configuration options related to a single branch.
673 """
674 def __init__(self, config, name):
675 self._config = config
676 self.name = name
677 self.merge = self._Get('merge')
678
679 r = self._Get('remote')
680 if r:
681 self.remote = self._config.GetRemote(r)
682 else:
683 self.remote = None
684
685 @property
686 def LocalMerge(self):
687 """Convert the merge spec to a local name.
688 """
689 if self.remote and self.merge:
690 return self.remote.ToLocal(self.merge)
691 return None
692
693 def Save(self):
694 """Save this branch back into the configuration.
695 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700696 if self._config.HasSection('branch', self.name):
697 if self.remote:
698 self._Set('remote', self.remote.name)
699 else:
700 self._Set('remote', None)
701 self._Set('merge', self.merge)
702
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700703 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700704 fd = open(self._config.file, 'ab')
705 try:
706 fd.write('[branch "%s"]\n' % self.name)
707 if self.remote:
708 fd.write('\tremote = %s\n' % self.remote.name)
709 if self.merge:
710 fd.write('\tmerge = %s\n' % self.merge)
711 finally:
712 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713
714 def _Set(self, key, value):
715 key = 'branch.%s.%s' % (self.name, key)
716 return self._config.SetString(key, value)
717
David Pursehouse8a68ff92012-09-24 12:15:13 +0900718 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900720 return self._config.GetString(key, all_keys = all_keys)