blob: 19c19f192e8e8acf97f0d697bea754bb0927845c [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:
566 info = urlopen(u).read()
567 if info == 'NOT_AVAILABLE':
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700568 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700569 if '<' in info:
570 # Assume the server gave us some sort of HTML
571 # response back, like maybe a login page.
572 #
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700573 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800574
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700575 self._review_protocol = 'ssh'
576 self._review_host = info.split(" ")[0]
577 self._review_port = info.split(" ")[1]
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700578 except urllib2.URLError, e:
579 raise UploadError('%s: %s' % (self.review, e.reason[1]))
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700580 except HTTPError, e:
581 if e.code == 404:
582 self._review_protocol = 'http-post'
583 self._review_host = None
584 self._review_port = None
585 else:
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700586 raise UploadError('Upload over ssh unavailable')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800587
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700588 REVIEW_CACHE[u] = (
589 self._review_protocol,
590 self._review_host,
591 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800592 return self._review_protocol
593
594 def SshReviewUrl(self, userEmail):
595 if self.ReviewProtocol != 'ssh':
596 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700597 username = self._config.GetString('review.%s.username' % self.review)
598 if username is None:
599 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800600 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700601 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800602 self._review_host,
603 self._review_port,
604 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700605
606 def ToLocal(self, rev):
607 """Convert a remote revision string to something we have locally.
608 """
609 if IsId(rev):
610 return rev
611 if rev.startswith(R_TAGS):
612 return rev
613
614 if not rev.startswith('refs/'):
615 rev = R_HEADS + rev
616
617 for spec in self.fetch:
618 if spec.SourceMatches(rev):
619 return spec.MapSource(rev)
620 raise GitError('remote %s does not have %s' % (self.name, rev))
621
622 def WritesTo(self, ref):
623 """True if the remote stores to the tracking ref.
624 """
625 for spec in self.fetch:
626 if spec.DestMatches(ref):
627 return True
628 return False
629
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800630 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631 """Set the fetch refspec to its default value.
632 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800633 if mirror:
634 dst = 'refs/heads/*'
635 else:
636 dst = 'refs/remotes/%s/*' % self.name
637 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700638
639 def Save(self):
640 """Save this remote to the configuration.
641 """
642 self._Set('url', self.url)
643 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800644 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645 self._Set('fetch', map(lambda x: str(x), self.fetch))
646
647 def _Set(self, key, value):
648 key = 'remote.%s.%s' % (self.name, key)
649 return self._config.SetString(key, value)
650
651 def _Get(self, key, all=False):
652 key = 'remote.%s.%s' % (self.name, key)
653 return self._config.GetString(key, all = all)
654
655
656class Branch(object):
657 """Configuration options related to a single branch.
658 """
659 def __init__(self, config, name):
660 self._config = config
661 self.name = name
662 self.merge = self._Get('merge')
663
664 r = self._Get('remote')
665 if r:
666 self.remote = self._config.GetRemote(r)
667 else:
668 self.remote = None
669
670 @property
671 def LocalMerge(self):
672 """Convert the merge spec to a local name.
673 """
674 if self.remote and self.merge:
675 return self.remote.ToLocal(self.merge)
676 return None
677
678 def Save(self):
679 """Save this branch back into the configuration.
680 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700681 if self._config.HasSection('branch', self.name):
682 if self.remote:
683 self._Set('remote', self.remote.name)
684 else:
685 self._Set('remote', None)
686 self._Set('merge', self.merge)
687
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700688 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700689 fd = open(self._config.file, 'ab')
690 try:
691 fd.write('[branch "%s"]\n' % self.name)
692 if self.remote:
693 fd.write('\tremote = %s\n' % self.remote.name)
694 if self.merge:
695 fd.write('\tmerge = %s\n' % self.merge)
696 finally:
697 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700698
699 def _Set(self, key, value):
700 key = 'branch.%s.%s' % (self.name, key)
701 return self._config.SetString(key, value)
702
703 def _Get(self, key, all=False):
704 key = 'branch.%s.%s' % (self.name, key)
705 return self._config.GetString(key, all = all)