blob: bcd6e8d62276fcf14a750152cbaef227f0f93ed0 [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
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070026import urllib2
27
Shawn O. Pearcefb231612009-04-10 18:53:46 -070028from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080029from urllib2 import urlopen, HTTPError
30from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070031from trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070032
33from git_command import GitCommand
34from git_command import ssh_sock
35from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036
37R_HEADS = 'refs/heads/'
38R_TAGS = 'refs/tags/'
39ID_RE = re.compile('^[0-9a-f]{40}$')
40
Shawn O. Pearce146fe902009-03-25 14:06:43 -070041REVIEW_CACHE = dict()
42
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070043def IsId(rev):
44 return ID_RE.match(rev)
45
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070046def _key(name):
47 parts = name.split('.')
48 if len(parts) < 2:
49 return name.lower()
50 parts[ 0] = parts[ 0].lower()
51 parts[-1] = parts[-1].lower()
52 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070053
54class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070055 _ForUser = None
56
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070057 @classmethod
58 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070059 if cls._ForUser is None:
60 cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
61 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070062
63 @classmethod
64 def ForRepository(cls, gitdir, defaults=None):
65 return cls(file = os.path.join(gitdir, 'config'),
66 defaults = defaults)
67
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070068 def __init__(self, file, defaults=None, pickleFile=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070069 self.file = file
70 self.defaults = defaults
71 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070072 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070073 self._remotes = {}
74 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070075
76 if pickleFile is None:
77 self._pickle = os.path.join(
78 os.path.dirname(self.file),
79 '.repopickle_' + os.path.basename(self.file))
80 else:
81 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082
83 def Has(self, name, include_defaults = True):
84 """Return true if this configuration file has the key.
85 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070086 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070087 return True
88 if include_defaults and self.defaults:
89 return self.defaults.Has(name, include_defaults = True)
90 return False
91
92 def GetBoolean(self, name):
93 """Returns a boolean from the configuration file.
94 None : The value was not defined, or is not a boolean.
95 True : The value was set to true or yes.
96 False: The value was set to false or no.
97 """
98 v = self.GetString(name)
99 if v is None:
100 return None
101 v = v.lower()
102 if v in ('true', 'yes'):
103 return True
104 if v in ('false', 'no'):
105 return False
106 return None
107
108 def GetString(self, name, all=False):
109 """Get the first value for a key, or None if it is not defined.
110
111 This configuration file is used first, if the key is not
112 defined or all = True then the defaults are also searched.
113 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700115 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700116 except KeyError:
117 if self.defaults:
118 return self.defaults.GetString(name, all = all)
119 v = []
120
121 if not all:
122 if v:
123 return v[0]
124 return None
125
126 r = []
127 r.extend(v)
128 if self.defaults:
129 r.extend(self.defaults.GetString(name, all = True))
130 return r
131
132 def SetString(self, name, value):
133 """Set the value(s) for a key.
134 Only this configuration file is modified.
135
136 The supplied value should be either a string,
137 or a list of strings (to store multiple values).
138 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700139 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700140
141 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700142 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700143 except KeyError:
144 old = []
145
146 if value is None:
147 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700148 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700149 self._do('--unset-all', name)
150
151 elif isinstance(value, list):
152 if len(value) == 0:
153 self.SetString(name, None)
154
155 elif len(value) == 1:
156 self.SetString(name, value[0])
157
158 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700159 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160 self._do('--replace-all', name, value[0])
161 for i in xrange(1, len(value)):
162 self._do('--add', name, value[i])
163
164 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700165 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700166 self._do('--replace-all', name, value)
167
168 def GetRemote(self, name):
169 """Get the remote.$name.* configuration values as an object.
170 """
171 try:
172 r = self._remotes[name]
173 except KeyError:
174 r = Remote(self, name)
175 self._remotes[r.name] = r
176 return r
177
178 def GetBranch(self, name):
179 """Get the branch.$name.* configuration values as an object.
180 """
181 try:
182 b = self._branches[name]
183 except KeyError:
184 b = Branch(self, name)
185 self._branches[b.name] = b
186 return b
187
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700188 def GetSubSections(self, section):
189 """List all subsection names matching $section.*.*
190 """
191 return self._sections.get(section, set())
192
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700193 def HasSection(self, section, subsection = ''):
194 """Does at least one key in section.subsection exist?
195 """
196 try:
197 return subsection in self._sections[section]
198 except KeyError:
199 return False
200
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700201 def UrlInsteadOf(self, url):
202 """Resolve any url.*.insteadof references.
203 """
204 for new_url in self.GetSubSections('url'):
205 old_url = self.GetString('url.%s.insteadof' % new_url)
206 if old_url is not None and url.startswith(old_url):
207 return new_url + url[len(old_url):]
208 return url
209
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700210 @property
211 def _sections(self):
212 d = self._section_dict
213 if d is None:
214 d = {}
215 for name in self._cache.keys():
216 p = name.split('.')
217 if 2 == len(p):
218 section = p[0]
219 subsect = ''
220 else:
221 section = p[0]
222 subsect = '.'.join(p[1:-1])
223 if section not in d:
224 d[section] = set()
225 d[section].add(subsect)
226 self._section_dict = d
227 return d
228
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700229 @property
230 def _cache(self):
231 if self._cache_dict is None:
232 self._cache_dict = self._Read()
233 return self._cache_dict
234
235 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700236 d = self._ReadPickle()
237 if d is None:
238 d = self._ReadGit()
239 self._SavePickle(d)
240 return d
241
242 def _ReadPickle(self):
243 try:
244 if os.path.getmtime(self._pickle) \
245 <= os.path.getmtime(self.file):
246 os.remove(self._pickle)
247 return None
248 except OSError:
249 return None
250 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700251 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700252 fd = open(self._pickle, 'rb')
253 try:
254 return cPickle.load(fd)
255 finally:
256 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700257 except EOFError:
258 os.remove(self._pickle)
259 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700260 except IOError:
261 os.remove(self._pickle)
262 return None
263 except cPickle.PickleError:
264 os.remove(self._pickle)
265 return None
266
267 def _SavePickle(self, cache):
268 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700269 fd = open(self._pickle, 'wb')
270 try:
271 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
272 finally:
273 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700274 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700275 if os.path.exists(self._pickle):
276 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700277 except cPickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700278 if os.path.exists(self._pickle):
279 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700280
281 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700282 """
283 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700284
David Aguilar438c5472009-06-28 15:09:16 -0700285 This internal method populates the GitConfig cache.
286
287 """
David Aguilar438c5472009-06-28 15:09:16 -0700288 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700289 d = self._do('--null', '--list')
290 if d is None:
291 return c
292 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700293 if '\n' in line:
294 key, val = line.split('\n', 1)
295 else:
296 key = line
297 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298
299 if key in c:
300 c[key].append(val)
301 else:
302 c[key] = [val]
303
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700304 return c
305
306 def _do(self, *args):
307 command = ['config', '--file', self.file]
308 command.extend(args)
309
310 p = GitCommand(None,
311 command,
312 capture_stdout = True,
313 capture_stderr = True)
314 if p.Wait() == 0:
315 return p.stdout
316 else:
317 GitError('git config %s: %s' % (str(args), p.stderr))
318
319
320class RefSpec(object):
321 """A Git refspec line, split into its components:
322
323 forced: True if the line starts with '+'
324 src: Left side of the line
325 dst: Right side of the line
326 """
327
328 @classmethod
329 def FromString(cls, rs):
330 lhs, rhs = rs.split(':', 2)
331 if lhs.startswith('+'):
332 lhs = lhs[1:]
333 forced = True
334 else:
335 forced = False
336 return cls(forced, lhs, rhs)
337
338 def __init__(self, forced, lhs, rhs):
339 self.forced = forced
340 self.src = lhs
341 self.dst = rhs
342
343 def SourceMatches(self, rev):
344 if self.src:
345 if rev == self.src:
346 return True
347 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
348 return True
349 return False
350
351 def DestMatches(self, ref):
352 if self.dst:
353 if ref == self.dst:
354 return True
355 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
356 return True
357 return False
358
359 def MapSource(self, rev):
360 if self.src.endswith('/*'):
361 return self.dst[:-1] + rev[len(self.src) - 1:]
362 return self.dst
363
364 def __str__(self):
365 s = ''
366 if self.forced:
367 s += '+'
368 if self.src:
369 s += self.src
370 if self.dst:
371 s += ':'
372 s += self.dst
373 return s
374
375
Doug Anderson06d029c2010-10-27 17:06:01 -0700376_master_processes = []
377_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700378_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800379_master_keys_lock = None
380
381def init_ssh():
382 """Should be called once at the start of repo to init ssh master handling.
383
384 At the moment, all we do is to create our lock.
385 """
386 global _master_keys_lock
387 assert _master_keys_lock is None, "Should only call init_ssh once"
388 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700389
Josh Guilfoyle71985722009-08-16 09:44:40 -0700390def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700391 global _ssh_master
392
Doug Anderson0048b692010-12-21 13:39:23 -0800393 # Acquire the lock. This is needed to prevent opening multiple masters for
394 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
395 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
396 # one that was passed to repo init.
397 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700398 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700399
Doug Anderson0048b692010-12-21 13:39:23 -0800400 # Check to see whether we already think that the master is running; if we
401 # think it's already running, return right away.
402 if port is not None:
403 key = '%s:%s' % (host, port)
404 else:
405 key = host
406
407 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700408 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700409
Doug Anderson0048b692010-12-21 13:39:23 -0800410 if not _ssh_master \
411 or 'GIT_SSH' in os.environ \
412 or sys.platform in ('win32', 'cygwin'):
413 # failed earlier, or cygwin ssh can't do this
414 #
415 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700416
Doug Anderson0048b692010-12-21 13:39:23 -0800417 # We will make two calls to ssh; this is the common part of both calls.
418 command_base = ['ssh',
419 '-o','ControlPath %s' % ssh_sock(),
420 host]
421 if port is not None:
422 command_base[1:1] = ['-p',str(port)]
423
424 # Since the key wasn't in _master_keys, we think that master isn't running.
425 # ...but before actually starting a master, we'll double-check. This can
426 # be important because we can't tell that that 'git@myhost.com' is the same
427 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
428 check_command = command_base + ['-O','check']
429 try:
430 Trace(': %s', ' '.join(check_command))
431 check_process = subprocess.Popen(check_command,
432 stdout=subprocess.PIPE,
433 stderr=subprocess.PIPE)
434 check_process.communicate() # read output, but ignore it...
435 isnt_running = check_process.wait()
436
437 if not isnt_running:
438 # Our double-check found that the master _was_ infact running. Add to
439 # the list of keys.
440 _master_keys.add(key)
441 return True
442 except Exception:
443 # Ignore excpetions. We we will fall back to the normal command and print
444 # to the log there.
445 pass
446
447 command = command_base[:1] + \
448 ['-M', '-N'] + \
449 command_base[1:]
450 try:
451 Trace(': %s', ' '.join(command))
452 p = subprocess.Popen(command)
453 except Exception, e:
454 _ssh_master = False
455 print >>sys.stderr, \
456 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
457 % (host,port, str(e))
458 return False
459
460 _master_processes.append(p)
461 _master_keys.add(key)
462 time.sleep(1)
463 return True
464 finally:
465 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700466
467def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800468 global _master_keys_lock
469
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700470 terminate_ssh_clients()
471
Doug Anderson06d029c2010-10-27 17:06:01 -0700472 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700473 try:
474 os.kill(p.pid, SIGTERM)
475 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700476 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700477 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700478 del _master_processes[:]
479 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700480
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700481 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700482 if d:
483 try:
484 os.rmdir(os.path.dirname(d))
485 except OSError:
486 pass
487
Doug Anderson0048b692010-12-21 13:39:23 -0800488 # We're done with the lock, so we can delete it.
489 _master_keys_lock = None
490
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700491URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce2f968c92009-04-30 14:30:28 -0700492URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700493
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700494def GetSchemeFromUrl(url):
495 m = URI_ALL.match(url)
496 if m:
497 return m.group(1)
498 return None
499
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700500def _preconnect(url):
501 m = URI_ALL.match(url)
502 if m:
503 scheme = m.group(1)
504 host = m.group(2)
505 if ':' in host:
506 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700507 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700508 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700509 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
510 return _open_ssh(host, port)
511 return False
512
513 m = URI_SCP.match(url)
514 if m:
515 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700516 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700517
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700518 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700519
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520class Remote(object):
521 """Configuration options related to a remote.
522 """
523 def __init__(self, config, name):
524 self._config = config
525 self.name = name
526 self.url = self._Get('url')
527 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800528 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529 self.fetch = map(lambda x: RefSpec.FromString(x),
530 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800531 self._review_protocol = None
532
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100533 def _InsteadOf(self):
534 globCfg = GitConfig.ForUser()
535 urlList = globCfg.GetSubSections('url')
536 longest = ""
537 longestUrl = ""
538
539 for url in urlList:
540 key = "url." + url + ".insteadOf"
541 insteadOfList = globCfg.GetString(key, all=True)
542
543 for insteadOf in insteadOfList:
544 if self.url.startswith(insteadOf) \
545 and len(insteadOf) > len(longest):
546 longest = insteadOf
547 longestUrl = url
548
549 if len(longest) == 0:
550 return self.url
551
552 return self.url.replace(longest, longestUrl, 1)
553
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700554 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100555 connectionUrl = self._InsteadOf()
556 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700557
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800558 @property
559 def ReviewProtocol(self):
560 if self._review_protocol is None:
561 if self.review is None:
562 return None
563
564 u = self.review
565 if not u.startswith('http:') and not u.startswith('https:'):
566 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700567 if u.endswith('/Gerrit'):
568 u = u[:len(u) - len('/Gerrit')]
569 if not u.endswith('/ssh_info'):
570 if not u.endswith('/'):
571 u += '/'
572 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800573
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700574 if u in REVIEW_CACHE:
575 info = REVIEW_CACHE[u]
576 self._review_protocol = info[0]
577 self._review_host = info[1]
578 self._review_port = info[2]
579 else:
580 try:
581 info = urlopen(u).read()
582 if info == 'NOT_AVAILABLE':
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700583 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700584 if '<' in info:
585 # Assume the server gave us some sort of HTML
586 # response back, like maybe a login page.
587 #
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700588 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800589
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700590 self._review_protocol = 'ssh'
591 self._review_host = info.split(" ")[0]
592 self._review_port = info.split(" ")[1]
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700593 except urllib2.URLError, e:
594 raise UploadError('%s: %s' % (self.review, e.reason[1]))
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700595 except HTTPError, e:
596 if e.code == 404:
597 self._review_protocol = 'http-post'
598 self._review_host = None
599 self._review_port = None
600 else:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700601 raise UploadError('Upload over ssh unavailable')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800602
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700603 REVIEW_CACHE[u] = (
604 self._review_protocol,
605 self._review_host,
606 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800607 return self._review_protocol
608
609 def SshReviewUrl(self, userEmail):
610 if self.ReviewProtocol != 'ssh':
611 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700612 username = self._config.GetString('review.%s.username' % self.review)
613 if username is None:
614 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800615 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700616 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800617 self._review_host,
618 self._review_port,
619 self.projectname)
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)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700660 self._Set('fetch', map(lambda x: str(x), self.fetch))
661
662 def _Set(self, key, value):
663 key = 'remote.%s.%s' % (self.name, key)
664 return self._config.SetString(key, value)
665
666 def _Get(self, key, all=False):
667 key = 'remote.%s.%s' % (self.name, key)
668 return self._config.GetString(key, all = all)
669
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
718 def _Get(self, key, all=False):
719 key = 'branch.%s.%s' % (self.name, key)
720 return self._config.GetString(key, all = all)