blob: 289a5b64aa459577b411a503fe8d89ab089a284c [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
201 @property
202 def _sections(self):
203 d = self._section_dict
204 if d is None:
205 d = {}
206 for name in self._cache.keys():
207 p = name.split('.')
208 if 2 == len(p):
209 section = p[0]
210 subsect = ''
211 else:
212 section = p[0]
213 subsect = '.'.join(p[1:-1])
214 if section not in d:
215 d[section] = set()
216 d[section].add(subsect)
217 self._section_dict = d
218 return d
219
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700220 @property
221 def _cache(self):
222 if self._cache_dict is None:
223 self._cache_dict = self._Read()
224 return self._cache_dict
225
226 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700227 d = self._ReadPickle()
228 if d is None:
229 d = self._ReadGit()
230 self._SavePickle(d)
231 return d
232
233 def _ReadPickle(self):
234 try:
235 if os.path.getmtime(self._pickle) \
236 <= os.path.getmtime(self.file):
237 os.remove(self._pickle)
238 return None
239 except OSError:
240 return None
241 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700242 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700243 fd = open(self._pickle, 'rb')
244 try:
245 return cPickle.load(fd)
246 finally:
247 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700248 except EOFError:
249 os.remove(self._pickle)
250 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700251 except IOError:
252 os.remove(self._pickle)
253 return None
254 except cPickle.PickleError:
255 os.remove(self._pickle)
256 return None
257
258 def _SavePickle(self, cache):
259 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700260 fd = open(self._pickle, 'wb')
261 try:
262 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
263 finally:
264 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700265 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700266 if os.path.exists(self._pickle):
267 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700268 except cPickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700269 if os.path.exists(self._pickle):
270 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700271
272 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700273 """
274 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700275
David Aguilar438c5472009-06-28 15:09:16 -0700276 This internal method populates the GitConfig cache.
277
278 """
David Aguilar438c5472009-06-28 15:09:16 -0700279 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700280 d = self._do('--null', '--list')
281 if d is None:
282 return c
283 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700284 if '\n' in line:
285 key, val = line.split('\n', 1)
286 else:
287 key = line
288 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700289
290 if key in c:
291 c[key].append(val)
292 else:
293 c[key] = [val]
294
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700295 return c
296
297 def _do(self, *args):
298 command = ['config', '--file', self.file]
299 command.extend(args)
300
301 p = GitCommand(None,
302 command,
303 capture_stdout = True,
304 capture_stderr = True)
305 if p.Wait() == 0:
306 return p.stdout
307 else:
308 GitError('git config %s: %s' % (str(args), p.stderr))
309
310
311class RefSpec(object):
312 """A Git refspec line, split into its components:
313
314 forced: True if the line starts with '+'
315 src: Left side of the line
316 dst: Right side of the line
317 """
318
319 @classmethod
320 def FromString(cls, rs):
321 lhs, rhs = rs.split(':', 2)
322 if lhs.startswith('+'):
323 lhs = lhs[1:]
324 forced = True
325 else:
326 forced = False
327 return cls(forced, lhs, rhs)
328
329 def __init__(self, forced, lhs, rhs):
330 self.forced = forced
331 self.src = lhs
332 self.dst = rhs
333
334 def SourceMatches(self, rev):
335 if self.src:
336 if rev == self.src:
337 return True
338 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
339 return True
340 return False
341
342 def DestMatches(self, ref):
343 if self.dst:
344 if ref == self.dst:
345 return True
346 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
347 return True
348 return False
349
350 def MapSource(self, rev):
351 if self.src.endswith('/*'):
352 return self.dst[:-1] + rev[len(self.src) - 1:]
353 return self.dst
354
355 def __str__(self):
356 s = ''
357 if self.forced:
358 s += '+'
359 if self.src:
360 s += self.src
361 if self.dst:
362 s += ':'
363 s += self.dst
364 return s
365
366
Doug Anderson06d029c2010-10-27 17:06:01 -0700367_master_processes = []
368_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700369_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800370_master_keys_lock = None
371
372def init_ssh():
373 """Should be called once at the start of repo to init ssh master handling.
374
375 At the moment, all we do is to create our lock.
376 """
377 global _master_keys_lock
378 assert _master_keys_lock is None, "Should only call init_ssh once"
379 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700380
Josh Guilfoyle71985722009-08-16 09:44:40 -0700381def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700382 global _ssh_master
383
Doug Anderson0048b692010-12-21 13:39:23 -0800384 # Acquire the lock. This is needed to prevent opening multiple masters for
385 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
386 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
387 # one that was passed to repo init.
388 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700389 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700390
Doug Anderson0048b692010-12-21 13:39:23 -0800391 # Check to see whether we already think that the master is running; if we
392 # think it's already running, return right away.
393 if port is not None:
394 key = '%s:%s' % (host, port)
395 else:
396 key = host
397
398 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700399 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700400
Doug Anderson0048b692010-12-21 13:39:23 -0800401 if not _ssh_master \
402 or 'GIT_SSH' in os.environ \
403 or sys.platform in ('win32', 'cygwin'):
404 # failed earlier, or cygwin ssh can't do this
405 #
406 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700407
Doug Anderson0048b692010-12-21 13:39:23 -0800408 # We will make two calls to ssh; this is the common part of both calls.
409 command_base = ['ssh',
410 '-o','ControlPath %s' % ssh_sock(),
411 host]
412 if port is not None:
413 command_base[1:1] = ['-p',str(port)]
414
415 # Since the key wasn't in _master_keys, we think that master isn't running.
416 # ...but before actually starting a master, we'll double-check. This can
417 # be important because we can't tell that that 'git@myhost.com' is the same
418 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
419 check_command = command_base + ['-O','check']
420 try:
421 Trace(': %s', ' '.join(check_command))
422 check_process = subprocess.Popen(check_command,
423 stdout=subprocess.PIPE,
424 stderr=subprocess.PIPE)
425 check_process.communicate() # read output, but ignore it...
426 isnt_running = check_process.wait()
427
428 if not isnt_running:
429 # Our double-check found that the master _was_ infact running. Add to
430 # the list of keys.
431 _master_keys.add(key)
432 return True
433 except Exception:
434 # Ignore excpetions. We we will fall back to the normal command and print
435 # to the log there.
436 pass
437
438 command = command_base[:1] + \
439 ['-M', '-N'] + \
440 command_base[1:]
441 try:
442 Trace(': %s', ' '.join(command))
443 p = subprocess.Popen(command)
444 except Exception, e:
445 _ssh_master = False
446 print >>sys.stderr, \
447 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
448 % (host,port, str(e))
449 return False
450
451 _master_processes.append(p)
452 _master_keys.add(key)
453 time.sleep(1)
454 return True
455 finally:
456 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700457
458def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800459 global _master_keys_lock
460
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700461 terminate_ssh_clients()
462
Doug Anderson06d029c2010-10-27 17:06:01 -0700463 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700464 try:
465 os.kill(p.pid, SIGTERM)
466 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700467 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700468 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700469 del _master_processes[:]
470 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700471
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700472 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700473 if d:
474 try:
475 os.rmdir(os.path.dirname(d))
476 except OSError:
477 pass
478
Doug Anderson0048b692010-12-21 13:39:23 -0800479 # We're done with the lock, so we can delete it.
480 _master_keys_lock = None
481
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700482URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce2f968c92009-04-30 14:30:28 -0700483URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700484
485def _preconnect(url):
486 m = URI_ALL.match(url)
487 if m:
488 scheme = m.group(1)
489 host = m.group(2)
490 if ':' in host:
491 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700492 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700493 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700494 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
495 return _open_ssh(host, port)
496 return False
497
498 m = URI_SCP.match(url)
499 if m:
500 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700501 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700502
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700503 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700504
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700505class Remote(object):
506 """Configuration options related to a remote.
507 """
508 def __init__(self, config, name):
509 self._config = config
510 self.name = name
511 self.url = self._Get('url')
512 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800513 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700514 self.fetch = map(lambda x: RefSpec.FromString(x),
515 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800516 self._review_protocol = None
517
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100518 def _InsteadOf(self):
519 globCfg = GitConfig.ForUser()
520 urlList = globCfg.GetSubSections('url')
521 longest = ""
522 longestUrl = ""
523
524 for url in urlList:
525 key = "url." + url + ".insteadOf"
526 insteadOfList = globCfg.GetString(key, all=True)
527
528 for insteadOf in insteadOfList:
529 if self.url.startswith(insteadOf) \
530 and len(insteadOf) > len(longest):
531 longest = insteadOf
532 longestUrl = url
533
534 if len(longest) == 0:
535 return self.url
536
537 return self.url.replace(longest, longestUrl, 1)
538
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700539 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100540 connectionUrl = self._InsteadOf()
541 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700542
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800543 @property
544 def ReviewProtocol(self):
545 if self._review_protocol is None:
546 if self.review is None:
547 return None
548
549 u = self.review
550 if not u.startswith('http:') and not u.startswith('https:'):
551 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700552 if u.endswith('/Gerrit'):
553 u = u[:len(u) - len('/Gerrit')]
554 if not u.endswith('/ssh_info'):
555 if not u.endswith('/'):
556 u += '/'
557 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800558
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700559 if u in REVIEW_CACHE:
560 info = REVIEW_CACHE[u]
561 self._review_protocol = info[0]
562 self._review_host = info[1]
563 self._review_port = info[2]
564 else:
565 try:
Anatol Pomozova101f1c2011-07-08 13:52:11 -0700566 if 'http_proxy' in os.environ:
567 proxy_url = os.environ['http_proxy']
568 proxy_support = urllib2.ProxyHandler({"http" : proxy_url, "https" : proxy_url})
569 opener = urllib2.build_opener(proxy_support)
570 urllib2.install_opener(opener)
571
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700572 info = urlopen(u).read()
573 if info == 'NOT_AVAILABLE':
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700574 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700575 if '<' in info:
576 # Assume the server gave us some sort of HTML
577 # response back, like maybe a login page.
578 #
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700579 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800580
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700581 self._review_protocol = 'ssh'
582 self._review_host = info.split(" ")[0]
583 self._review_port = info.split(" ")[1]
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700584 except urllib2.URLError, e:
585 raise UploadError('%s: %s' % (self.review, e.reason[1]))
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700586 except HTTPError, e:
587 if e.code == 404:
588 self._review_protocol = 'http-post'
589 self._review_host = None
590 self._review_port = None
591 else:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700592 raise UploadError('Upload over ssh unavailable')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800593
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700594 REVIEW_CACHE[u] = (
595 self._review_protocol,
596 self._review_host,
597 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800598 return self._review_protocol
599
600 def SshReviewUrl(self, userEmail):
601 if self.ReviewProtocol != 'ssh':
602 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700603 username = self._config.GetString('review.%s.username' % self.review)
604 if username is None:
605 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800606 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700607 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800608 self._review_host,
609 self._review_port,
610 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700611
612 def ToLocal(self, rev):
613 """Convert a remote revision string to something we have locally.
614 """
615 if IsId(rev):
616 return rev
617 if rev.startswith(R_TAGS):
618 return rev
619
620 if not rev.startswith('refs/'):
621 rev = R_HEADS + rev
622
623 for spec in self.fetch:
624 if spec.SourceMatches(rev):
625 return spec.MapSource(rev)
626 raise GitError('remote %s does not have %s' % (self.name, rev))
627
628 def WritesTo(self, ref):
629 """True if the remote stores to the tracking ref.
630 """
631 for spec in self.fetch:
632 if spec.DestMatches(ref):
633 return True
634 return False
635
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800636 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700637 """Set the fetch refspec to its default value.
638 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800639 if mirror:
640 dst = 'refs/heads/*'
641 else:
642 dst = 'refs/remotes/%s/*' % self.name
643 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700644
645 def Save(self):
646 """Save this remote to the configuration.
647 """
648 self._Set('url', self.url)
649 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800650 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700651 self._Set('fetch', map(lambda x: str(x), self.fetch))
652
653 def _Set(self, key, value):
654 key = 'remote.%s.%s' % (self.name, key)
655 return self._config.SetString(key, value)
656
657 def _Get(self, key, all=False):
658 key = 'remote.%s.%s' % (self.name, key)
659 return self._config.GetString(key, all = all)
660
661
662class Branch(object):
663 """Configuration options related to a single branch.
664 """
665 def __init__(self, config, name):
666 self._config = config
667 self.name = name
668 self.merge = self._Get('merge')
669
670 r = self._Get('remote')
671 if r:
672 self.remote = self._config.GetRemote(r)
673 else:
674 self.remote = None
675
676 @property
677 def LocalMerge(self):
678 """Convert the merge spec to a local name.
679 """
680 if self.remote and self.merge:
681 return self.remote.ToLocal(self.merge)
682 return None
683
684 def Save(self):
685 """Save this branch back into the configuration.
686 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700687 if self._config.HasSection('branch', self.name):
688 if self.remote:
689 self._Set('remote', self.remote.name)
690 else:
691 self._Set('remote', None)
692 self._Set('merge', self.merge)
693
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700695 fd = open(self._config.file, 'ab')
696 try:
697 fd.write('[branch "%s"]\n' % self.name)
698 if self.remote:
699 fd.write('\tremote = %s\n' % self.remote.name)
700 if self.merge:
701 fd.write('\tmerge = %s\n' % self.merge)
702 finally:
703 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704
705 def _Set(self, key, value):
706 key = 'branch.%s.%s' % (self.name, key)
707 return self._config.SetString(key, value)
708
709 def _Get(self, key, all=False):
710 key = 'branch.%s.%s' % (self.name, key)
711 return self._config.GetString(key, all = all)