blob: d6510aae7df8c6a718339cb05b162c1e1e8dd5c4 [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/'
David Pursehouse1d947b32012-10-25 12:23:11 +090038ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070039
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:
David Pursehouse8a68ff92012-09-24 12:15:13 +090059 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070060 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070061
62 @classmethod
63 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090064 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065 defaults = defaults)
66
David Pursehouse8a68ff92012-09-24 12:15:13 +090067 def __init__(self, configfile, defaults=None, pickleFile=None):
68 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070069 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
David Pursehouse8a68ff92012-09-24 12:15:13 +0900107 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108 """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
David Pursehouse8a68ff92012-09-24 12:15:13 +0900111 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700112 """
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:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900117 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700118 v = []
119
David Pursehouse8a68ff92012-09-24 12:15:13 +0900120 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 if v:
122 return v[0]
123 return None
124
125 r = []
126 r.extend(v)
127 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900128 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700129 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.
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
David Pursehouse1d947b32012-10-25 12:23:11 +0900291 for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
292 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700293 if '\n' in line:
294 key, val = line.split('\n', 1)
295 else:
296 key = line
297 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298
299 if key in c:
300 c[key].append(val)
301 else:
302 c[key] = [val]
303
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700304 return c
305
306 def _do(self, *args):
307 command = ['config', '--file', self.file]
308 command.extend(args)
309
310 p = GitCommand(None,
311 command,
312 capture_stdout = True,
313 capture_stderr = True)
314 if p.Wait() == 0:
315 return p.stdout
316 else:
317 GitError('git config %s: %s' % (str(args), p.stderr))
318
319
320class RefSpec(object):
321 """A Git refspec line, split into its components:
322
323 forced: True if the line starts with '+'
324 src: Left side of the line
325 dst: Right side of the line
326 """
327
328 @classmethod
329 def FromString(cls, rs):
330 lhs, rhs = rs.split(':', 2)
331 if lhs.startswith('+'):
332 lhs = lhs[1:]
333 forced = True
334 else:
335 forced = False
336 return cls(forced, lhs, rhs)
337
338 def __init__(self, forced, lhs, rhs):
339 self.forced = forced
340 self.src = lhs
341 self.dst = rhs
342
343 def SourceMatches(self, rev):
344 if self.src:
345 if rev == self.src:
346 return True
347 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
348 return True
349 return False
350
351 def DestMatches(self, ref):
352 if self.dst:
353 if ref == self.dst:
354 return True
355 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
356 return True
357 return False
358
359 def MapSource(self, rev):
360 if self.src.endswith('/*'):
361 return self.dst[:-1] + rev[len(self.src) - 1:]
362 return self.dst
363
364 def __str__(self):
365 s = ''
366 if self.forced:
367 s += '+'
368 if self.src:
369 s += self.src
370 if self.dst:
371 s += ':'
372 s += self.dst
373 return s
374
375
Doug Anderson06d029c2010-10-27 17:06:01 -0700376_master_processes = []
377_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700378_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800379_master_keys_lock = None
380
381def init_ssh():
382 """Should be called once at the start of repo to init ssh master handling.
383
384 At the moment, all we do is to create our lock.
385 """
386 global _master_keys_lock
387 assert _master_keys_lock is None, "Should only call init_ssh once"
388 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700389
Josh Guilfoyle71985722009-08-16 09:44:40 -0700390def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700391 global _ssh_master
392
Doug Anderson0048b692010-12-21 13:39:23 -0800393 # Acquire the lock. This is needed to prevent opening multiple masters for
394 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
395 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
396 # one that was passed to repo init.
397 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700398 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700399
Doug Anderson0048b692010-12-21 13:39:23 -0800400 # Check to see whether we already think that the master is running; if we
401 # think it's already running, return right away.
402 if port is not None:
403 key = '%s:%s' % (host, port)
404 else:
405 key = host
406
407 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700408 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700409
Doug Anderson0048b692010-12-21 13:39:23 -0800410 if not _ssh_master \
411 or 'GIT_SSH' in os.environ \
412 or sys.platform in ('win32', 'cygwin'):
413 # failed earlier, or cygwin ssh can't do this
414 #
415 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700416
Doug Anderson0048b692010-12-21 13:39:23 -0800417 # We will make two calls to ssh; this is the common part of both calls.
418 command_base = ['ssh',
419 '-o','ControlPath %s' % ssh_sock(),
420 host]
421 if port is not None:
422 command_base[1:1] = ['-p',str(port)]
423
424 # Since the key wasn't in _master_keys, we think that master isn't running.
425 # ...but before actually starting a master, we'll double-check. This can
426 # be important because we can't tell that that 'git@myhost.com' is the same
427 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
428 check_command = command_base + ['-O','check']
429 try:
430 Trace(': %s', ' '.join(check_command))
431 check_process = subprocess.Popen(check_command,
432 stdout=subprocess.PIPE,
433 stderr=subprocess.PIPE)
434 check_process.communicate() # read output, but ignore it...
435 isnt_running = check_process.wait()
436
437 if not isnt_running:
438 # Our double-check found that the master _was_ infact running. Add to
439 # the list of keys.
440 _master_keys.add(key)
441 return True
442 except Exception:
443 # Ignore excpetions. We we will fall back to the normal command and print
444 # to the log there.
445 pass
446
447 command = command_base[:1] + \
448 ['-M', '-N'] + \
449 command_base[1:]
450 try:
451 Trace(': %s', ' '.join(command))
452 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700453 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800454 _ssh_master = False
455 print >>sys.stderr, \
456 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
457 % (host,port, str(e))
458 return False
459
460 _master_processes.append(p)
461 _master_keys.add(key)
462 time.sleep(1)
463 return True
464 finally:
465 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700466
467def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800468 global _master_keys_lock
469
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700470 terminate_ssh_clients()
471
Doug Anderson06d029c2010-10-27 17:06:01 -0700472 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700473 try:
474 os.kill(p.pid, SIGTERM)
475 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700476 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700477 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700478 del _master_processes[:]
479 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700480
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700481 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700482 if d:
483 try:
484 os.rmdir(os.path.dirname(d))
485 except OSError:
486 pass
487
Doug Anderson0048b692010-12-21 13:39:23 -0800488 # We're done with the lock, so we can delete it.
489 _master_keys_lock = None
490
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700491URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700492URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700493
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700494def GetSchemeFromUrl(url):
495 m = URI_ALL.match(url)
496 if m:
497 return m.group(1)
498 return None
499
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700500def _preconnect(url):
501 m = URI_ALL.match(url)
502 if m:
503 scheme = m.group(1)
504 host = m.group(2)
505 if ':' in host:
506 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700507 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700508 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700509 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
510 return _open_ssh(host, port)
511 return False
512
513 m = URI_SCP.match(url)
514 if m:
515 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700516 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700517
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700518 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700519
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700520class Remote(object):
521 """Configuration options related to a remote.
522 """
523 def __init__(self, config, name):
524 self._config = config
525 self.name = name
526 self.url = self._Get('url')
527 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800528 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529 self.fetch = map(lambda x: RefSpec.FromString(x),
David Pursehouse8a68ff92012-09-24 12:15:13 +0900530 self._Get('fetch', all_keys=True))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800531 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800532
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100533 def _InsteadOf(self):
534 globCfg = GitConfig.ForUser()
535 urlList = globCfg.GetSubSections('url')
536 longest = ""
537 longestUrl = ""
538
539 for url in urlList:
540 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900541 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100542
543 for insteadOf in insteadOfList:
544 if self.url.startswith(insteadOf) \
545 and len(insteadOf) > len(longest):
546 longest = insteadOf
547 longestUrl = url
548
549 if len(longest) == 0:
550 return self.url
551
552 return self.url.replace(longest, longestUrl, 1)
553
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700554 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100555 connectionUrl = self._InsteadOf()
556 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700557
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800558 def ReviewUrl(self, userEmail):
559 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800560 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')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800568 if u.endswith('/ssh_info'):
569 u = u[:len(u) - len('/ssh_info')]
570 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900571 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800572 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800573
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700574 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800575 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700576 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800577 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
578 self._review_url = self._SshReviewUrl(userEmail, host, port)
579 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700580 else:
581 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800582 info_url = u + 'ssh_info'
583 info = urllib2.urlopen(info_url).read()
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700584 if '<' in info:
585 # Assume the server gave us some sort of HTML
586 # response back, like maybe a login page.
587 #
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800588 raise UploadError('%s: Cannot parse response' % info_url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800589
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800590 if info == 'NOT_AVAILABLE':
591 # Assume HTTP if SSH is not enabled.
592 self._review_url = http_url + 'p/'
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700593 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800594 host, port = info.split()
595 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700596 except urllib2.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800597 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owensa5be53f2012-09-09 15:37:57 -0700598 except urllib2.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700599 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800600
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800601 REVIEW_CACHE[u] = self._review_url
602 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800603
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800604 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700605 username = self._config.GetString('review.%s.username' % self.review)
606 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800607 username = userEmail.split('@')[0]
608 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700609
610 def ToLocal(self, rev):
611 """Convert a remote revision string to something we have locally.
612 """
613 if IsId(rev):
614 return rev
615 if rev.startswith(R_TAGS):
616 return rev
617
618 if not rev.startswith('refs/'):
619 rev = R_HEADS + rev
620
621 for spec in self.fetch:
622 if spec.SourceMatches(rev):
623 return spec.MapSource(rev)
624 raise GitError('remote %s does not have %s' % (self.name, rev))
625
626 def WritesTo(self, ref):
627 """True if the remote stores to the tracking ref.
628 """
629 for spec in self.fetch:
630 if spec.DestMatches(ref):
631 return True
632 return False
633
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800634 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700635 """Set the fetch refspec to its default value.
636 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800637 if mirror:
638 dst = 'refs/heads/*'
639 else:
640 dst = 'refs/remotes/%s/*' % self.name
641 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700642
643 def Save(self):
644 """Save this remote to the configuration.
645 """
646 self._Set('url', self.url)
647 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800648 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700649 self._Set('fetch', map(lambda x: str(x), self.fetch))
650
651 def _Set(self, key, value):
652 key = 'remote.%s.%s' % (self.name, key)
653 return self._config.SetString(key, value)
654
David Pursehouse8a68ff92012-09-24 12:15:13 +0900655 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900657 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700658
659
660class Branch(object):
661 """Configuration options related to a single branch.
662 """
663 def __init__(self, config, name):
664 self._config = config
665 self.name = name
666 self.merge = self._Get('merge')
667
668 r = self._Get('remote')
669 if r:
670 self.remote = self._config.GetRemote(r)
671 else:
672 self.remote = None
673
674 @property
675 def LocalMerge(self):
676 """Convert the merge spec to a local name.
677 """
678 if self.remote and self.merge:
679 return self.remote.ToLocal(self.merge)
680 return None
681
682 def Save(self):
683 """Save this branch back into the configuration.
684 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700685 if self._config.HasSection('branch', self.name):
686 if self.remote:
687 self._Set('remote', self.remote.name)
688 else:
689 self._Set('remote', None)
690 self._Set('merge', self.merge)
691
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700692 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700693 fd = open(self._config.file, 'ab')
694 try:
695 fd.write('[branch "%s"]\n' % self.name)
696 if self.remote:
697 fd.write('\tremote = %s\n' % self.remote.name)
698 if self.merge:
699 fd.write('\tmerge = %s\n' % self.merge)
700 finally:
701 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700702
703 def _Set(self, key, value):
704 key = 'branch.%s.%s' % (self.name, key)
705 return self._config.SetString(key, value)
706
David Pursehouse8a68ff92012-09-24 12:15:13 +0900707 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700708 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900709 return self._config.GetString(key, all_keys = all_keys)