blob: ac41d5b0018d686029f320035f32a5599b26f8bd [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]
578 else:
579 try:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700580 info = urllib2.urlopen(u).read()
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700581 if info == 'NOT_AVAILABLE':
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700582 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700583 if '<' in info:
584 # Assume the server gave us some sort of HTML
585 # response back, like maybe a login page.
586 #
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -0700587 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800588
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700589 self._review_protocol = 'ssh'
590 self._review_host = info.split(" ")[0]
591 self._review_port = info.split(" ")[1]
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700592 except urllib2.HTTPError, e:
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700593 if e.code == 404:
594 self._review_protocol = 'http-post'
595 self._review_host = None
596 self._review_port = None
597 else:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700598 raise UploadError('Upload over SSH unavailable')
599 except urllib2.URLError, e:
600 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800601
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700602 REVIEW_CACHE[u] = (
603 self._review_protocol,
604 self._review_host,
605 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800606 return self._review_protocol
607
608 def SshReviewUrl(self, userEmail):
609 if self.ReviewProtocol != 'ssh':
610 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700611 username = self._config.GetString('review.%s.username' % self.review)
612 if username is None:
613 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800614 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700615 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800616 self._review_host,
617 self._review_port,
618 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700619
620 def ToLocal(self, rev):
621 """Convert a remote revision string to something we have locally.
622 """
623 if IsId(rev):
624 return rev
625 if rev.startswith(R_TAGS):
626 return rev
627
628 if not rev.startswith('refs/'):
629 rev = R_HEADS + rev
630
631 for spec in self.fetch:
632 if spec.SourceMatches(rev):
633 return spec.MapSource(rev)
634 raise GitError('remote %s does not have %s' % (self.name, rev))
635
636 def WritesTo(self, ref):
637 """True if the remote stores to the tracking ref.
638 """
639 for spec in self.fetch:
640 if spec.DestMatches(ref):
641 return True
642 return False
643
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800644 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700645 """Set the fetch refspec to its default value.
646 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800647 if mirror:
648 dst = 'refs/heads/*'
649 else:
650 dst = 'refs/remotes/%s/*' % self.name
651 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700652
653 def Save(self):
654 """Save this remote to the configuration.
655 """
656 self._Set('url', self.url)
657 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800658 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700659 self._Set('fetch', map(lambda x: str(x), self.fetch))
660
661 def _Set(self, key, value):
662 key = 'remote.%s.%s' % (self.name, key)
663 return self._config.SetString(key, value)
664
665 def _Get(self, key, all=False):
666 key = 'remote.%s.%s' % (self.name, key)
667 return self._config.GetString(key, all = all)
668
669
670class Branch(object):
671 """Configuration options related to a single branch.
672 """
673 def __init__(self, config, name):
674 self._config = config
675 self.name = name
676 self.merge = self._Get('merge')
677
678 r = self._Get('remote')
679 if r:
680 self.remote = self._config.GetRemote(r)
681 else:
682 self.remote = None
683
684 @property
685 def LocalMerge(self):
686 """Convert the merge spec to a local name.
687 """
688 if self.remote and self.merge:
689 return self.remote.ToLocal(self.merge)
690 return None
691
692 def Save(self):
693 """Save this branch back into the configuration.
694 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700695 if self._config.HasSection('branch', self.name):
696 if self.remote:
697 self._Set('remote', self.remote.name)
698 else:
699 self._Set('remote', None)
700 self._Set('merge', self.merge)
701
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700703 fd = open(self._config.file, 'ab')
704 try:
705 fd.write('[branch "%s"]\n' % self.name)
706 if self.remote:
707 fd.write('\tremote = %s\n' % self.remote.name)
708 if self.merge:
709 fd.write('\tmerge = %s\n' % self.merge)
710 finally:
711 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712
713 def _Set(self, key, value):
714 key = 'branch.%s.%s' % (self.name, key)
715 return self._config.SetString(key, value)
716
717 def _Get(self, key, all=False):
718 key = 'branch.%s.%s' % (self.name, key)
719 return self._config.GetString(key, all = all)