blob: afaa6f15fd80f87ba315f3ca5579eec05490981f [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:
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
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()
Doug Anderson06d029c2010-10-27 17:06:01 -0700397 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700398
Doug Anderson0048b692010-12-21 13:39:23 -0800399 # Check to see whether we already think that the master is running; if we
400 # think it's already running, return right away.
401 if port is not None:
402 key = '%s:%s' % (host, port)
403 else:
404 key = host
405
406 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700407 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700408
Doug Anderson0048b692010-12-21 13:39:23 -0800409 if not _ssh_master \
410 or 'GIT_SSH' in os.environ \
411 or sys.platform in ('win32', 'cygwin'):
412 # failed earlier, or cygwin ssh can't do this
413 #
414 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700415
Doug Anderson0048b692010-12-21 13:39:23 -0800416 # We will make two calls to ssh; this is the common part of both calls.
417 command_base = ['ssh',
418 '-o','ControlPath %s' % ssh_sock(),
419 host]
420 if port is not None:
421 command_base[1:1] = ['-p',str(port)]
422
423 # Since the key wasn't in _master_keys, we think that master isn't running.
424 # ...but before actually starting a master, we'll double-check. This can
425 # be important because we can't tell that that 'git@myhost.com' is the same
426 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
427 check_command = command_base + ['-O','check']
428 try:
429 Trace(': %s', ' '.join(check_command))
430 check_process = subprocess.Popen(check_command,
431 stdout=subprocess.PIPE,
432 stderr=subprocess.PIPE)
433 check_process.communicate() # read output, but ignore it...
434 isnt_running = check_process.wait()
435
436 if not isnt_running:
437 # Our double-check found that the master _was_ infact running. Add to
438 # the list of keys.
439 _master_keys.add(key)
440 return True
441 except Exception:
442 # Ignore excpetions. We we will fall back to the normal command and print
443 # to the log there.
444 pass
445
446 command = command_base[:1] + \
447 ['-M', '-N'] + \
448 command_base[1:]
449 try:
450 Trace(': %s', ' '.join(command))
451 p = subprocess.Popen(command)
452 except Exception, e:
453 _ssh_master = False
454 print >>sys.stderr, \
455 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
456 % (host,port, str(e))
457 return False
458
459 _master_processes.append(p)
460 _master_keys.add(key)
461 time.sleep(1)
462 return True
463 finally:
464 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700465
466def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800467 global _master_keys_lock
468
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700469 terminate_ssh_clients()
470
Doug Anderson06d029c2010-10-27 17:06:01 -0700471 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700472 try:
473 os.kill(p.pid, SIGTERM)
474 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700475 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700476 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700477 del _master_processes[:]
478 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700479
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700480 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700481 if d:
482 try:
483 os.rmdir(os.path.dirname(d))
484 except OSError:
485 pass
486
Doug Anderson0048b692010-12-21 13:39:23 -0800487 # We're done with the lock, so we can delete it.
488 _master_keys_lock = None
489
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700490URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22: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),
David Pursehouse8a68ff92012-09-24 12:15:13 +0900529 self._Get('fetch', all_keys=True))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800530 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800531
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"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900540 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100541
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. Pearcec9571422012-01-11 14:58:54 -0800557 def ReviewUrl(self, userEmail):
558 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800559 if self.review is None:
560 return None
561
562 u = self.review
563 if not u.startswith('http:') and not u.startswith('https:'):
564 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700565 if u.endswith('/Gerrit'):
566 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800567 if u.endswith('/ssh_info'):
568 u = u[:len(u) - len('/ssh_info')]
569 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900570 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800571 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800572
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700573 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800574 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700575 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800576 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
577 self._review_url = self._SshReviewUrl(userEmail, host, port)
578 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700579 else:
580 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800581 info_url = u + 'ssh_info'
582 info = urllib2.urlopen(info_url).read()
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. Pearcec9571422012-01-11 14:58:54 -0800587 raise UploadError('%s: Cannot parse response' % info_url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800588
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800589 if info == 'NOT_AVAILABLE':
590 # Assume HTTP if SSH is not enabled.
591 self._review_url = http_url + 'p/'
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700592 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800593 host, port = info.split()
594 self._review_url = self._SshReviewUrl(userEmail, host, port)
595 except urllib2.HTTPError, e:
596 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700597 except urllib2.URLError, e:
598 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800599
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800600 REVIEW_CACHE[u] = self._review_url
601 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800602
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800603 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700604 username = self._config.GetString('review.%s.username' % self.review)
605 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800606 username = userEmail.split('@')[0]
607 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700608
609 def ToLocal(self, rev):
610 """Convert a remote revision string to something we have locally.
611 """
612 if IsId(rev):
613 return rev
614 if rev.startswith(R_TAGS):
615 return rev
616
617 if not rev.startswith('refs/'):
618 rev = R_HEADS + rev
619
620 for spec in self.fetch:
621 if spec.SourceMatches(rev):
622 return spec.MapSource(rev)
623 raise GitError('remote %s does not have %s' % (self.name, rev))
624
625 def WritesTo(self, ref):
626 """True if the remote stores to the tracking ref.
627 """
628 for spec in self.fetch:
629 if spec.DestMatches(ref):
630 return True
631 return False
632
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800633 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634 """Set the fetch refspec to its default value.
635 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800636 if mirror:
637 dst = 'refs/heads/*'
638 else:
639 dst = 'refs/remotes/%s/*' % self.name
640 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641
642 def Save(self):
643 """Save this remote to the configuration.
644 """
645 self._Set('url', self.url)
646 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800647 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700648 self._Set('fetch', map(lambda x: str(x), self.fetch))
649
650 def _Set(self, key, value):
651 key = 'remote.%s.%s' % (self.name, key)
652 return self._config.SetString(key, value)
653
David Pursehouse8a68ff92012-09-24 12:15:13 +0900654 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700655 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900656 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700657
658
659class Branch(object):
660 """Configuration options related to a single branch.
661 """
662 def __init__(self, config, name):
663 self._config = config
664 self.name = name
665 self.merge = self._Get('merge')
666
667 r = self._Get('remote')
668 if r:
669 self.remote = self._config.GetRemote(r)
670 else:
671 self.remote = None
672
673 @property
674 def LocalMerge(self):
675 """Convert the merge spec to a local name.
676 """
677 if self.remote and self.merge:
678 return self.remote.ToLocal(self.merge)
679 return None
680
681 def Save(self):
682 """Save this branch back into the configuration.
683 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700684 if self._config.HasSection('branch', self.name):
685 if self.remote:
686 self._Set('remote', self.remote.name)
687 else:
688 self._Set('remote', None)
689 self._Set('merge', self.merge)
690
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700691 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700692 fd = open(self._config.file, 'ab')
693 try:
694 fd.write('[branch "%s"]\n' % self.name)
695 if self.remote:
696 fd.write('\tremote = %s\n' % self.remote.name)
697 if self.merge:
698 fd.write('\tmerge = %s\n' % self.merge)
699 finally:
700 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700701
702 def _Set(self, key, value):
703 key = 'branch.%s.%s' % (self.name, key)
704 return self._config.SetString(key, value)
705
David Pursehouse8a68ff92012-09-24 12:15:13 +0900706 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700707 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900708 return self._config.GetString(key, all_keys = all_keys)