blob: e4f4a0aba282b3aaa9a83b3e68396670cd7aa3b8 [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
494def _preconnect(url):
495 m = URI_ALL.match(url)
496 if m:
497 scheme = m.group(1)
498 host = m.group(2)
499 if ':' in host:
500 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700501 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700502 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700503 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
504 return _open_ssh(host, port)
505 return False
506
507 m = URI_SCP.match(url)
508 if m:
509 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700510 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700511
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700512 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700513
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514class Remote(object):
515 """Configuration options related to a remote.
516 """
517 def __init__(self, config, name):
518 self._config = config
519 self.name = name
520 self.url = self._Get('url')
521 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800522 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700523 self.fetch = map(lambda x: RefSpec.FromString(x),
524 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800525 self._review_protocol = None
526
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100527 def _InsteadOf(self):
528 globCfg = GitConfig.ForUser()
529 urlList = globCfg.GetSubSections('url')
530 longest = ""
531 longestUrl = ""
532
533 for url in urlList:
534 key = "url." + url + ".insteadOf"
535 insteadOfList = globCfg.GetString(key, all=True)
536
537 for insteadOf in insteadOfList:
538 if self.url.startswith(insteadOf) \
539 and len(insteadOf) > len(longest):
540 longest = insteadOf
541 longestUrl = url
542
543 if len(longest) == 0:
544 return self.url
545
546 return self.url.replace(longest, longestUrl, 1)
547
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700548 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100549 connectionUrl = self._InsteadOf()
550 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700551
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800552 @property
553 def ReviewProtocol(self):
554 if self._review_protocol is None:
555 if self.review is None:
556 return None
557
558 u = self.review
559 if not u.startswith('http:') and not u.startswith('https:'):
560 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700561 if u.endswith('/Gerrit'):
562 u = u[:len(u) - len('/Gerrit')]
563 if not u.endswith('/ssh_info'):
564 if not u.endswith('/'):
565 u += '/'
566 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800567
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700568 if u in REVIEW_CACHE:
569 info = REVIEW_CACHE[u]
570 self._review_protocol = info[0]
571 self._review_host = info[1]
572 self._review_port = info[2]
573 else:
574 try:
575 info = urlopen(u).read()
576 if info == 'NOT_AVAILABLE':
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700577 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700578 if '<' in info:
579 # Assume the server gave us some sort of HTML
580 # response back, like maybe a login page.
581 #
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700582 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800583
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700584 self._review_protocol = 'ssh'
585 self._review_host = info.split(" ")[0]
586 self._review_port = info.split(" ")[1]
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700587 except urllib2.URLError, e:
588 raise UploadError('%s: %s' % (self.review, e.reason[1]))
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700589 except HTTPError, e:
590 if e.code == 404:
591 self._review_protocol = 'http-post'
592 self._review_host = None
593 self._review_port = None
594 else:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700595 raise UploadError('Upload over ssh unavailable')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800596
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700597 REVIEW_CACHE[u] = (
598 self._review_protocol,
599 self._review_host,
600 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800601 return self._review_protocol
602
603 def SshReviewUrl(self, userEmail):
604 if self.ReviewProtocol != 'ssh':
605 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700606 username = self._config.GetString('review.%s.username' % self.review)
607 if username is None:
608 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800609 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700610 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800611 self._review_host,
612 self._review_port,
613 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700614
615 def ToLocal(self, rev):
616 """Convert a remote revision string to something we have locally.
617 """
618 if IsId(rev):
619 return rev
620 if rev.startswith(R_TAGS):
621 return rev
622
623 if not rev.startswith('refs/'):
624 rev = R_HEADS + rev
625
626 for spec in self.fetch:
627 if spec.SourceMatches(rev):
628 return spec.MapSource(rev)
629 raise GitError('remote %s does not have %s' % (self.name, rev))
630
631 def WritesTo(self, ref):
632 """True if the remote stores to the tracking ref.
633 """
634 for spec in self.fetch:
635 if spec.DestMatches(ref):
636 return True
637 return False
638
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800639 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700640 """Set the fetch refspec to its default value.
641 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800642 if mirror:
643 dst = 'refs/heads/*'
644 else:
645 dst = 'refs/remotes/%s/*' % self.name
646 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647
648 def Save(self):
649 """Save this remote to the configuration.
650 """
651 self._Set('url', self.url)
652 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800653 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654 self._Set('fetch', map(lambda x: str(x), self.fetch))
655
656 def _Set(self, key, value):
657 key = 'remote.%s.%s' % (self.name, key)
658 return self._config.SetString(key, value)
659
660 def _Get(self, key, all=False):
661 key = 'remote.%s.%s' % (self.name, key)
662 return self._config.GetString(key, all = all)
663
664
665class Branch(object):
666 """Configuration options related to a single branch.
667 """
668 def __init__(self, config, name):
669 self._config = config
670 self.name = name
671 self.merge = self._Get('merge')
672
673 r = self._Get('remote')
674 if r:
675 self.remote = self._config.GetRemote(r)
676 else:
677 self.remote = None
678
679 @property
680 def LocalMerge(self):
681 """Convert the merge spec to a local name.
682 """
683 if self.remote and self.merge:
684 return self.remote.ToLocal(self.merge)
685 return None
686
687 def Save(self):
688 """Save this branch back into the configuration.
689 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700690 if self._config.HasSection('branch', self.name):
691 if self.remote:
692 self._Set('remote', self.remote.name)
693 else:
694 self._Set('remote', None)
695 self._Set('merge', self.merge)
696
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700697 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700698 fd = open(self._config.file, 'ab')
699 try:
700 fd.write('[branch "%s"]\n' % self.name)
701 if self.remote:
702 fd.write('\tremote = %s\n' % self.remote.name)
703 if self.merge:
704 fd.write('\tmerge = %s\n' % self.merge)
705 finally:
706 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700707
708 def _Set(self, key, value):
709 key = 'branch.%s.%s' % (self.name, key)
710 return self._config.SetString(key, value)
711
712 def _Get(self, key, all=False):
713 key = 'branch.%s.%s' % (self.name, key)
714 return self._config.GetString(key, all = all)