blob: a52677cf8cd07e207f10081d2d0deb558897902c [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 error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070030from trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070031
32from git_command import GitCommand
33from git_command import ssh_sock
34from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070035
36R_HEADS = 'refs/heads/'
37R_TAGS = 'refs/tags/'
38ID_RE = re.compile('^[0-9a-f]{40}$')
39
Shawn O. Pearce146fe902009-03-25 14:06:43 -070040REVIEW_CACHE = dict()
41
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070042def IsId(rev):
43 return ID_RE.match(rev)
44
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070045def _key(name):
46 parts = name.split('.')
47 if len(parts) < 2:
48 return name.lower()
49 parts[ 0] = parts[ 0].lower()
50 parts[-1] = parts[-1].lower()
51 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070052
53class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070054 _ForUser = None
55
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056 @classmethod
57 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070058 if cls._ForUser is None:
59 cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
60 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070061
62 @classmethod
63 def ForRepository(cls, gitdir, defaults=None):
64 return cls(file = os.path.join(gitdir, 'config'),
65 defaults = defaults)
66
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070067 def __init__(self, file, defaults=None, pickleFile=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068 self.file = file
69 self.defaults = defaults
70 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070071 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070072 self._remotes = {}
73 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070074
75 if pickleFile is None:
76 self._pickle = os.path.join(
77 os.path.dirname(self.file),
78 '.repopickle_' + os.path.basename(self.file))
79 else:
80 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081
82 def Has(self, name, include_defaults = True):
83 """Return true if this configuration file has the key.
84 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070085 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070086 return True
87 if include_defaults and self.defaults:
88 return self.defaults.Has(name, include_defaults = True)
89 return False
90
91 def GetBoolean(self, name):
92 """Returns a boolean from the configuration file.
93 None : The value was not defined, or is not a boolean.
94 True : The value was set to true or yes.
95 False: The value was set to false or no.
96 """
97 v = self.GetString(name)
98 if v is None:
99 return None
100 v = v.lower()
101 if v in ('true', 'yes'):
102 return True
103 if v in ('false', 'no'):
104 return False
105 return None
106
107 def GetString(self, name, all=False):
108 """Get the first value for a key, or None if it is not defined.
109
110 This configuration file is used first, if the key is not
111 defined or all = True then the defaults are also searched.
112 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700113 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700114 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115 except KeyError:
116 if self.defaults:
117 return self.defaults.GetString(name, all = all)
118 v = []
119
120 if not all:
121 if v:
122 return v[0]
123 return None
124
125 r = []
126 r.extend(v)
127 if self.defaults:
128 r.extend(self.defaults.GetString(name, all = True))
129 return r
130
131 def SetString(self, name, value):
132 """Set the value(s) for a key.
133 Only this configuration file is modified.
134
135 The supplied value should be either a string,
136 or a list of strings (to store multiple values).
137 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700138 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139
140 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700141 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142 except KeyError:
143 old = []
144
145 if value is None:
146 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700147 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700148 self._do('--unset-all', name)
149
150 elif isinstance(value, list):
151 if len(value) == 0:
152 self.SetString(name, None)
153
154 elif len(value) == 1:
155 self.SetString(name, value[0])
156
157 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700158 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700159 self._do('--replace-all', name, value[0])
160 for i in xrange(1, len(value)):
161 self._do('--add', name, value[i])
162
163 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700164 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700165 self._do('--replace-all', name, value)
166
167 def GetRemote(self, name):
168 """Get the remote.$name.* configuration values as an object.
169 """
170 try:
171 r = self._remotes[name]
172 except KeyError:
173 r = Remote(self, name)
174 self._remotes[r.name] = r
175 return r
176
177 def GetBranch(self, name):
178 """Get the branch.$name.* configuration values as an object.
179 """
180 try:
181 b = self._branches[name]
182 except KeyError:
183 b = Branch(self, name)
184 self._branches[b.name] = b
185 return b
186
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700187 def GetSubSections(self, section):
188 """List all subsection names matching $section.*.*
189 """
190 return self._sections.get(section, set())
191
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700192 def HasSection(self, section, subsection = ''):
193 """Does at least one key in section.subsection exist?
194 """
195 try:
196 return subsection in self._sections[section]
197 except KeyError:
198 return False
199
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700200 def UrlInsteadOf(self, url):
201 """Resolve any url.*.insteadof references.
202 """
203 for new_url in self.GetSubSections('url'):
204 old_url = self.GetString('url.%s.insteadof' % new_url)
205 if old_url is not None and url.startswith(old_url):
206 return new_url + url[len(old_url):]
207 return url
208
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700209 @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:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700274 if os.path.exists(self._pickle):
275 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700276 except cPickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -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.
283
284 This internal method populates the GitConfig cache.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700285
David Aguilar438c5472009-06-28 15:09:16 -0700286 """
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 Guilfoyle71985722009-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()
397 try:
Josh Guilfoyle71985722009-08-16 09:44:40 -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
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700405
Doug Anderson0048b692010-12-21 13:39:23 -0800406 if key in _master_keys:
407 return True
Shawn O. Pearcefb231612009-04-10 18:53:46 -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
Doug Anderson06d029c2010-10-27 17:06:01 -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)]
Doug Anderson06d029c2010-10-27 17:06:01 -0700422
Doug Anderson0048b692010-12-21 13:39:23 -0800423 # 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()
Josh Guilfoyle71985722009-08-16 09:44:40 -0700435
Doug Anderson0048b692010-12-21 13:39:23 -0800436 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
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700445
Doug Anderson0048b692010-12-21 13:39:23 -0800446 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
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700493def GetSchemeFromUrl(url):
494 m = URI_ALL.match(url)
495 if m:
496 return m.group(1)
497 return None
498
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700499def _preconnect(url):
500 m = URI_ALL.match(url)
501 if m:
502 scheme = m.group(1)
503 host = m.group(2)
504 if ':' in host:
505 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700506 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700507 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700508 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
509 return _open_ssh(host, port)
510 return False
511
512 m = URI_SCP.match(url)
513 if m:
514 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700515 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700516
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700517 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700518
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700519class Remote(object):
520 """Configuration options related to a remote.
521 """
522 def __init__(self, config, name):
523 self._config = config
524 self.name = name
525 self.url = self._Get('url')
526 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800527 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700528 self.fetch = map(lambda x: RefSpec.FromString(x),
529 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800530 self._review_protocol = None
531
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100532 def _InsteadOf(self):
533 globCfg = GitConfig.ForUser()
534 urlList = globCfg.GetSubSections('url')
535 longest = ""
536 longestUrl = ""
537
538 for url in urlList:
539 key = "url." + url + ".insteadOf"
540 insteadOfList = globCfg.GetString(key, all=True)
541
542 for insteadOf in insteadOfList:
543 if self.url.startswith(insteadOf) \
544 and len(insteadOf) > len(longest):
545 longest = insteadOf
546 longestUrl = url
547
548 if len(longest) == 0:
549 return self.url
550
551 return self.url.replace(longest, longestUrl, 1)
552
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700553 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100554 connectionUrl = self._InsteadOf()
555 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700556
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800557 @property
558 def ReviewProtocol(self):
559 if self._review_protocol is None:
560 if self.review is None:
561 return None
562
563 u = self.review
564 if not u.startswith('http:') and not u.startswith('https:'):
565 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700566 if u.endswith('/Gerrit'):
567 u = u[:len(u) - len('/Gerrit')]
568 if not u.endswith('/ssh_info'):
569 if not u.endswith('/'):
570 u += '/'
571 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800572
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700573 if u in REVIEW_CACHE:
574 info = REVIEW_CACHE[u]
575 self._review_protocol = info[0]
576 self._review_host = info[1]
577 self._review_port = info[2]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700578 elif 'REPO_HOST_PORT_INFO' in os.environ:
579 info = os.environ['REPO_HOST_PORT_INFO']
580 self._review_protocol = 'ssh'
581 self._review_host = info.split(" ")[0]
582 self._review_port = info.split(" ")[1]
583
584 REVIEW_CACHE[u] = (
585 self._review_protocol,
586 self._review_host,
587 self._review_port)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700588 else:
589 try:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700590 info = urllib2.urlopen(u).read()
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700591 if info == 'NOT_AVAILABLE':
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700592 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700593 if '<' in info:
594 # Assume the server gave us some sort of HTML
595 # response back, like maybe a login page.
596 #
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700597 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800598
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700599 self._review_protocol = 'ssh'
600 self._review_host = info.split(" ")[0]
601 self._review_port = info.split(" ")[1]
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700602 except urllib2.HTTPError, e:
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700603 if e.code == 404:
604 self._review_protocol = 'http-post'
605 self._review_host = None
606 self._review_port = None
607 else:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700608 raise UploadError('Upload over SSH unavailable')
609 except urllib2.URLError, e:
610 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800611
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700612 REVIEW_CACHE[u] = (
613 self._review_protocol,
614 self._review_host,
615 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800616 return self._review_protocol
617
618 def SshReviewUrl(self, userEmail):
619 if self.ReviewProtocol != 'ssh':
620 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700621 username = self._config.GetString('review.%s.username' % self.review)
622 if username is None:
623 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800624 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700625 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800626 self._review_host,
627 self._review_port,
628 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629
630 def ToLocal(self, rev):
631 """Convert a remote revision string to something we have locally.
632 """
633 if IsId(rev):
634 return rev
635 if rev.startswith(R_TAGS):
636 return rev
637
638 if not rev.startswith('refs/'):
639 rev = R_HEADS + rev
640
641 for spec in self.fetch:
642 if spec.SourceMatches(rev):
643 return spec.MapSource(rev)
644 raise GitError('remote %s does not have %s' % (self.name, rev))
645
646 def WritesTo(self, ref):
647 """True if the remote stores to the tracking ref.
648 """
649 for spec in self.fetch:
650 if spec.DestMatches(ref):
651 return True
652 return False
653
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800654 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655 """Set the fetch refspec to its default value.
656 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800657 if mirror:
658 dst = 'refs/heads/*'
659 else:
660 dst = 'refs/remotes/%s/*' % self.name
661 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662
663 def Save(self):
664 """Save this remote to the configuration.
665 """
666 self._Set('url', self.url)
667 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800668 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669 self._Set('fetch', map(lambda x: str(x), self.fetch))
670
671 def _Set(self, key, value):
672 key = 'remote.%s.%s' % (self.name, key)
673 return self._config.SetString(key, value)
674
675 def _Get(self, key, all=False):
676 key = 'remote.%s.%s' % (self.name, key)
677 return self._config.GetString(key, all = all)
678
679
680class Branch(object):
681 """Configuration options related to a single branch.
682 """
683 def __init__(self, config, name):
684 self._config = config
685 self.name = name
686 self.merge = self._Get('merge')
687
688 r = self._Get('remote')
689 if r:
690 self.remote = self._config.GetRemote(r)
691 else:
692 self.remote = None
693
694 @property
695 def LocalMerge(self):
696 """Convert the merge spec to a local name.
697 """
698 if self.remote and self.merge:
699 return self.remote.ToLocal(self.merge)
700 return None
701
702 def Save(self):
703 """Save this branch back into the configuration.
704 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700705 if self._config.HasSection('branch', self.name):
706 if self.remote:
707 self._Set('remote', self.remote.name)
708 else:
709 self._Set('remote', None)
710 self._Set('merge', self.merge)
711
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700713 fd = open(self._config.file, 'ab')
714 try:
715 fd.write('[branch "%s"]\n' % self.name)
716 if self.remote:
717 fd.write('\tremote = %s\n' % self.remote.name)
718 if self.merge:
719 fd.write('\tmerge = %s\n' % self.merge)
720 finally:
721 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700722
723 def _Set(self, key, value):
724 key = 'branch.%s.%s' % (self.name, key)
725 return self._config.SetString(key, value)
726
727 def _Get(self, key, all=False):
728 key = 'branch.%s.%s' % (self.name, key)
729 return self._config.GetString(key, all = all)