blob: ff815e35a1ba7ad2176cf64a053fd52caae26213 [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. Pearced2dfac82009-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
Shawn O. Pearce98ea26b2009-06-04 19:19:11 -070083 def ClearCache(self):
84 if os.path.exists(self._pickle):
85 os.remove(self._pickle)
86 self._cache_dict = None
87 self._section_dict = None
88 self._remotes = {}
89 self._branches = {}
90
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070091 def Has(self, name, include_defaults = True):
92 """Return true if this configuration file has the key.
93 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070094 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070095 return True
96 if include_defaults and self.defaults:
97 return self.defaults.Has(name, include_defaults = True)
98 return False
99
100 def GetBoolean(self, name):
101 """Returns a boolean from the configuration file.
102 None : The value was not defined, or is not a boolean.
103 True : The value was set to true or yes.
104 False: The value was set to false or no.
105 """
106 v = self.GetString(name)
107 if v is None:
108 return None
109 v = v.lower()
110 if v in ('true', 'yes'):
111 return True
112 if v in ('false', 'no'):
113 return False
114 return None
115
116 def GetString(self, name, all=False):
117 """Get the first value for a key, or None if it is not defined.
118
119 This configuration file is used first, if the key is not
120 defined or all = True then the defaults are also searched.
121 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700122 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700123 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700124 except KeyError:
125 if self.defaults:
126 return self.defaults.GetString(name, all = all)
127 v = []
128
129 if not all:
130 if v:
131 return v[0]
132 return None
133
134 r = []
135 r.extend(v)
136 if self.defaults:
137 r.extend(self.defaults.GetString(name, all = True))
138 return r
139
140 def SetString(self, name, value):
141 """Set the value(s) for a key.
142 Only this configuration file is modified.
143
144 The supplied value should be either a string,
145 or a list of strings (to store multiple values).
146 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700147 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700148
149 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700150 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700151 except KeyError:
152 old = []
153
154 if value is None:
155 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700156 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700157 self._do('--unset-all', name)
158
159 elif isinstance(value, list):
160 if len(value) == 0:
161 self.SetString(name, None)
162
163 elif len(value) == 1:
164 self.SetString(name, value[0])
165
166 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700167 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168 self._do('--replace-all', name, value[0])
169 for i in xrange(1, len(value)):
170 self._do('--add', name, value[i])
171
172 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700173 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174 self._do('--replace-all', name, value)
175
176 def GetRemote(self, name):
177 """Get the remote.$name.* configuration values as an object.
178 """
179 try:
180 r = self._remotes[name]
181 except KeyError:
182 r = Remote(self, name)
183 self._remotes[r.name] = r
184 return r
185
186 def GetBranch(self, name):
187 """Get the branch.$name.* configuration values as an object.
188 """
189 try:
190 b = self._branches[name]
191 except KeyError:
192 b = Branch(self, name)
193 self._branches[b.name] = b
194 return b
195
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700196 def GetSubSections(self, section):
197 """List all subsection names matching $section.*.*
198 """
199 return self._sections.get(section, set())
200
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700201 def HasSection(self, section, subsection = ''):
202 """Does at least one key in section.subsection exist?
203 """
204 try:
205 return subsection in self._sections[section]
206 except KeyError:
207 return False
208
209 @property
210 def _sections(self):
211 d = self._section_dict
212 if d is None:
213 d = {}
214 for name in self._cache.keys():
215 p = name.split('.')
216 if 2 == len(p):
217 section = p[0]
218 subsect = ''
219 else:
220 section = p[0]
221 subsect = '.'.join(p[1:-1])
222 if section not in d:
223 d[section] = set()
224 d[section].add(subsect)
225 self._section_dict = d
226 return d
227
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228 @property
229 def _cache(self):
230 if self._cache_dict is None:
231 self._cache_dict = self._Read()
232 return self._cache_dict
233
234 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700235 d = self._ReadPickle()
236 if d is None:
237 d = self._ReadGit()
238 self._SavePickle(d)
239 return d
240
241 def _ReadPickle(self):
242 try:
243 if os.path.getmtime(self._pickle) \
244 <= os.path.getmtime(self.file):
245 os.remove(self._pickle)
246 return None
247 except OSError:
248 return None
249 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700250 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700251 fd = open(self._pickle, 'rb')
252 try:
253 return cPickle.load(fd)
254 finally:
255 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700256 except EOFError:
257 os.remove(self._pickle)
258 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700259 except IOError:
260 os.remove(self._pickle)
261 return None
262 except cPickle.PickleError:
263 os.remove(self._pickle)
264 return None
265
266 def _SavePickle(self, cache):
267 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700268 fd = open(self._pickle, 'wb')
269 try:
270 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
271 finally:
272 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700273 except IOError:
Shawn O. Pearceb0ca41e2009-07-03 20:01:47 -0700274 if os.path.exists(self._pickle):
275 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700276 except cPickle.PickleError:
Shawn O. Pearceb0ca41e2009-07-03 20:01:47 -0700277 if os.path.exists(self._pickle):
278 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700279
280 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700281 """
282 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700283
David Aguilar438c5472009-06-28 15:09:16 -0700284 This internal method populates the GitConfig cache.
285
286 """
David Aguilar438c5472009-06-28 15:09:16 -0700287 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700288 d = self._do('--null', '--list')
289 if d is None:
290 return c
291 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700292 if '\n' in line:
293 key, val = line.split('\n', 1)
294 else:
295 key = line
296 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700297
298 if key in c:
299 c[key].append(val)
300 else:
301 c[key] = [val]
302
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700303 return c
304
305 def _do(self, *args):
306 command = ['config', '--file', self.file]
307 command.extend(args)
308
309 p = GitCommand(None,
310 command,
311 capture_stdout = True,
312 capture_stderr = True)
313 if p.Wait() == 0:
314 return p.stdout
315 else:
316 GitError('git config %s: %s' % (str(args), p.stderr))
317
318
319class RefSpec(object):
320 """A Git refspec line, split into its components:
321
322 forced: True if the line starts with '+'
323 src: Left side of the line
324 dst: Right side of the line
325 """
326
327 @classmethod
328 def FromString(cls, rs):
329 lhs, rhs = rs.split(':', 2)
330 if lhs.startswith('+'):
331 lhs = lhs[1:]
332 forced = True
333 else:
334 forced = False
335 return cls(forced, lhs, rhs)
336
337 def __init__(self, forced, lhs, rhs):
338 self.forced = forced
339 self.src = lhs
340 self.dst = rhs
341
342 def SourceMatches(self, rev):
343 if self.src:
344 if rev == self.src:
345 return True
346 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
347 return True
348 return False
349
350 def DestMatches(self, ref):
351 if self.dst:
352 if ref == self.dst:
353 return True
354 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
355 return True
356 return False
357
358 def MapSource(self, rev):
359 if self.src.endswith('/*'):
360 return self.dst[:-1] + rev[len(self.src) - 1:]
361 return self.dst
362
363 def __str__(self):
364 s = ''
365 if self.forced:
366 s += '+'
367 if self.src:
368 s += self.src
369 if self.dst:
370 s += ':'
371 s += self.dst
372 return s
373
374
Doug Anderson06d029c2010-10-27 17:06:01 -0700375_master_processes = []
376_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700377_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800378_master_keys_lock = None
379
380def init_ssh():
381 """Should be called once at the start of repo to init ssh master handling.
382
383 At the moment, all we do is to create our lock.
384 """
385 global _master_keys_lock
386 assert _master_keys_lock is None, "Should only call init_ssh once"
387 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700388
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700389def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700390 global _ssh_master
391
Doug Anderson0048b692010-12-21 13:39:23 -0800392 # Acquire the lock. This is needed to prevent opening multiple masters for
393 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
394 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
395 # one that was passed to repo init.
396 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700397 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700398
Doug Anderson0048b692010-12-21 13:39:23 -0800399 # Check to see whether we already think that the master is running; if we
400 # think it's already running, return right away.
401 if port is not None:
402 key = '%s:%s' % (host, port)
403 else:
404 key = host
405
406 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700407 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700408
Doug Anderson0048b692010-12-21 13:39:23 -0800409 if not _ssh_master \
410 or 'GIT_SSH' in os.environ \
411 or sys.platform in ('win32', 'cygwin'):
412 # failed earlier, or cygwin ssh can't do this
413 #
414 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700415
Doug Anderson0048b692010-12-21 13:39:23 -0800416 # We will make two calls to ssh; this is the common part of both calls.
417 command_base = ['ssh',
418 '-o','ControlPath %s' % ssh_sock(),
419 host]
420 if port is not None:
421 command_base[1:1] = ['-p',str(port)]
422
423 # Since the key wasn't in _master_keys, we think that master isn't running.
424 # ...but before actually starting a master, we'll double-check. This can
425 # be important because we can't tell that that 'git@myhost.com' is the same
426 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
427 check_command = command_base + ['-O','check']
428 try:
429 Trace(': %s', ' '.join(check_command))
430 check_process = subprocess.Popen(check_command,
431 stdout=subprocess.PIPE,
432 stderr=subprocess.PIPE)
433 check_process.communicate() # read output, but ignore it...
434 isnt_running = check_process.wait()
435
436 if not isnt_running:
437 # Our double-check found that the master _was_ infact running. Add to
438 # the list of keys.
439 _master_keys.add(key)
440 return True
441 except Exception:
442 # Ignore excpetions. We we will fall back to the normal command and print
443 # to the log there.
444 pass
445
446 command = command_base[:1] + \
447 ['-M', '-N'] + \
448 command_base[1:]
449 try:
450 Trace(': %s', ' '.join(command))
451 p = subprocess.Popen(command)
452 except Exception, e:
453 _ssh_master = False
454 print >>sys.stderr, \
455 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
456 % (host,port, str(e))
457 return False
458
459 _master_processes.append(p)
460 _master_keys.add(key)
461 time.sleep(1)
462 return True
463 finally:
464 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700465
466def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800467 global _master_keys_lock
468
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700469 terminate_ssh_clients()
470
Doug Anderson06d029c2010-10-27 17:06:01 -0700471 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700472 try:
473 os.kill(p.pid, SIGTERM)
474 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700475 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700476 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700477 del _master_processes[:]
478 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700479
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700480 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700481 if d:
482 try:
483 os.rmdir(os.path.dirname(d))
484 except OSError:
485 pass
486
Doug Anderson0048b692010-12-21 13:39:23 -0800487 # We're done with the lock, so we can delete it.
488 _master_keys_lock = None
489
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700490URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce2f968c92009-04-30 14:30:28 -0700491URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700492
493def _preconnect(url):
494 m = URI_ALL.match(url)
495 if m:
496 scheme = m.group(1)
497 host = m.group(2)
498 if ':' in host:
499 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700500 else:
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700501 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700502 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
503 return _open_ssh(host, port)
504 return False
505
506 m = URI_SCP.match(url)
507 if m:
508 host = m.group(1)
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700509 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700510
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700511 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700512
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700513class Remote(object):
514 """Configuration options related to a remote.
515 """
516 def __init__(self, config, name):
517 self._config = config
518 self.name = name
519 self.url = self._Get('url')
520 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800521 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700522 self.fetch = map(lambda x: RefSpec.FromString(x),
523 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800524 self._review_protocol = None
525
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100526 def _InsteadOf(self):
527 globCfg = GitConfig.ForUser()
528 urlList = globCfg.GetSubSections('url')
529 longest = ""
530 longestUrl = ""
531
532 for url in urlList:
533 key = "url." + url + ".insteadOf"
534 insteadOfList = globCfg.GetString(key, all=True)
535
536 for insteadOf in insteadOfList:
537 if self.url.startswith(insteadOf) \
538 and len(insteadOf) > len(longest):
539 longest = insteadOf
540 longestUrl = url
541
542 if len(longest) == 0:
543 return self.url
544
545 return self.url.replace(longest, longestUrl, 1)
546
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700547 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100548 connectionUrl = self._InsteadOf()
549 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700550
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800551 @property
552 def ReviewProtocol(self):
553 if self._review_protocol is None:
554 if self.review is None:
555 return None
556
557 u = self.review
558 if not u.startswith('http:') and not u.startswith('https:'):
559 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700560 if u.endswith('/Gerrit'):
561 u = u[:len(u) - len('/Gerrit')]
562 if not u.endswith('/ssh_info'):
563 if not u.endswith('/'):
564 u += '/'
565 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800566
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700567 if u in REVIEW_CACHE:
568 info = REVIEW_CACHE[u]
569 self._review_protocol = info[0]
570 self._review_host = info[1]
571 self._review_port = info[2]
572 else:
573 try:
574 info = urlopen(u).read()
575 if info == 'NOT_AVAILABLE':
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700576 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700577 if '<' in info:
578 # Assume the server gave us some sort of HTML
579 # response back, like maybe a login page.
580 #
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700581 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800582
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700583 self._review_protocol = 'ssh'
584 self._review_host = info.split(" ")[0]
585 self._review_port = info.split(" ")[1]
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700586 except urllib2.URLError, e:
587 raise UploadError('%s: %s' % (self.review, e.reason[1]))
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700588 except HTTPError, e:
589 if e.code == 404:
590 self._review_protocol = 'http-post'
591 self._review_host = None
592 self._review_port = None
593 else:
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700594 raise UploadError('Upload over ssh unavailable')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800595
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700596 REVIEW_CACHE[u] = (
597 self._review_protocol,
598 self._review_host,
599 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800600 return self._review_protocol
601
602 def SshReviewUrl(self, userEmail):
603 if self.ReviewProtocol != 'ssh':
604 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700605 username = self._config.GetString('review.%s.username' % self.review)
606 if username is None:
607 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800608 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700609 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800610 self._review_host,
611 self._review_port,
612 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700613
614 def ToLocal(self, rev):
615 """Convert a remote revision string to something we have locally.
616 """
617 if IsId(rev):
618 return rev
619 if rev.startswith(R_TAGS):
620 return rev
621
622 if not rev.startswith('refs/'):
623 rev = R_HEADS + rev
624
625 for spec in self.fetch:
626 if spec.SourceMatches(rev):
627 return spec.MapSource(rev)
628 raise GitError('remote %s does not have %s' % (self.name, rev))
629
630 def WritesTo(self, ref):
631 """True if the remote stores to the tracking ref.
632 """
633 for spec in self.fetch:
634 if spec.DestMatches(ref):
635 return True
636 return False
637
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800638 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700639 """Set the fetch refspec to its default value.
640 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800641 if mirror:
642 dst = 'refs/heads/*'
643 else:
644 dst = 'refs/remotes/%s/*' % self.name
645 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646
647 def Save(self):
648 """Save this remote to the configuration.
649 """
650 self._Set('url', self.url)
651 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800652 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653 self._Set('fetch', map(lambda x: str(x), self.fetch))
654
655 def _Set(self, key, value):
656 key = 'remote.%s.%s' % (self.name, key)
657 return self._config.SetString(key, value)
658
659 def _Get(self, key, all=False):
660 key = 'remote.%s.%s' % (self.name, key)
661 return self._config.GetString(key, all = all)
662
663
664class Branch(object):
665 """Configuration options related to a single branch.
666 """
667 def __init__(self, config, name):
668 self._config = config
669 self.name = name
670 self.merge = self._Get('merge')
671
672 r = self._Get('remote')
673 if r:
674 self.remote = self._config.GetRemote(r)
675 else:
676 self.remote = None
677
678 @property
679 def LocalMerge(self):
680 """Convert the merge spec to a local name.
681 """
682 if self.remote and self.merge:
683 return self.remote.ToLocal(self.merge)
684 return None
685
686 def Save(self):
687 """Save this branch back into the configuration.
688 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700689 if self._config.HasSection('branch', self.name):
690 if self.remote:
691 self._Set('remote', self.remote.name)
692 else:
693 self._Set('remote', None)
694 self._Set('merge', self.merge)
695
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700696 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700697 fd = open(self._config.file, 'ab')
698 try:
699 fd.write('[branch "%s"]\n' % self.name)
700 if self.remote:
701 fd.write('\tremote = %s\n' % self.remote.name)
702 if self.merge:
703 fd.write('\tmerge = %s\n' % self.merge)
704 finally:
705 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700706
707 def _Set(self, key, value):
708 key = 'branch.%s.%s' % (self.name, key)
709 return self._config.SetString(key, value)
710
711 def _Get(self, key, all=False):
712 key = 'branch.%s.%s' % (self.name, key)
713 return self._config.GetString(key, all = all)