blob: 9524df9b097d6f0ed585d018682517650e56397f [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
Sarah Owenscecd1d82012-11-01 22:59:27 -070016from __future__ import print_function
Chirayu Desai217ea7d2013-03-01 19:14:38 +053017
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
Chirayu Desai217ea7d2013-03-01 19:14:38 +053019import pickle
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070020import re
Shawn O. Pearcefb231612009-04-10 18:53:46 -070021import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import sys
Doug Anderson0048b692010-12-21 13:39:23 -080023try:
24 import threading as _threading
25except ImportError:
26 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070027import time
Sarah Owens1f7627f2012-10-31 09:21:55 -070028try:
29 import urllib2
30except ImportError:
31 # For python3
32 import urllib.request
33 import urllib.error
34else:
35 # For python2
36 import imp
37 urllib = imp.new_module('urllib')
38 urllib.request = urllib2
39 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070040
Shawn O. Pearcefb231612009-04-10 18:53:46 -070041from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080042from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070043from trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070044
45from git_command import GitCommand
46from git_command import ssh_sock
47from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048
49R_HEADS = 'refs/heads/'
50R_TAGS = 'refs/tags/'
David Pursehouse1d947b32012-10-25 12:23:11 +090051ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070052
Shawn O. Pearce146fe902009-03-25 14:06:43 -070053REVIEW_CACHE = dict()
54
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055def IsId(rev):
56 return ID_RE.match(rev)
57
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070058def _key(name):
59 parts = name.split('.')
60 if len(parts) < 2:
61 return name.lower()
62 parts[ 0] = parts[ 0].lower()
63 parts[-1] = parts[-1].lower()
64 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070065
66class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070067 _ForUser = None
68
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070069 @classmethod
70 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070071 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090072 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070073 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074
75 @classmethod
76 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090077 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070078 defaults = defaults)
79
David Pursehouse8a68ff92012-09-24 12:15:13 +090080 def __init__(self, configfile, defaults=None, pickleFile=None):
81 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082 self.defaults = defaults
83 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070084 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070085 self._remotes = {}
86 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070087
88 if pickleFile is None:
89 self._pickle = os.path.join(
90 os.path.dirname(self.file),
91 '.repopickle_' + os.path.basename(self.file))
92 else:
93 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070094
95 def Has(self, name, include_defaults = True):
96 """Return true if this configuration file has the key.
97 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070098 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070099 return True
100 if include_defaults and self.defaults:
101 return self.defaults.Has(name, include_defaults = True)
102 return False
103
104 def GetBoolean(self, name):
105 """Returns a boolean from the configuration file.
106 None : The value was not defined, or is not a boolean.
107 True : The value was set to true or yes.
108 False: The value was set to false or no.
109 """
110 v = self.GetString(name)
111 if v is None:
112 return None
113 v = v.lower()
114 if v in ('true', 'yes'):
115 return True
116 if v in ('false', 'no'):
117 return False
118 return None
119
David Pursehouse8a68ff92012-09-24 12:15:13 +0900120 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700121 """Get the first value for a key, or None if it is not defined.
122
123 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900124 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700125 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700126 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700127 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700128 except KeyError:
129 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900130 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131 v = []
132
David Pursehouse8a68ff92012-09-24 12:15:13 +0900133 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700134 if v:
135 return v[0]
136 return None
137
138 r = []
139 r.extend(v)
140 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900141 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700142 return r
143
144 def SetString(self, name, value):
145 """Set the value(s) for a key.
146 Only this configuration file is modified.
147
148 The supplied value should be either a string,
149 or a list of strings (to store multiple values).
150 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700151 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700152
153 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700154 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700155 except KeyError:
156 old = []
157
158 if value is None:
159 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700160 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 self._do('--unset-all', name)
162
163 elif isinstance(value, list):
164 if len(value) == 0:
165 self.SetString(name, None)
166
167 elif len(value) == 1:
168 self.SetString(name, value[0])
169
170 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700171 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700172 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700173 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174 self._do('--add', name, value[i])
175
176 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700177 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700178 self._do('--replace-all', name, value)
179
180 def GetRemote(self, name):
181 """Get the remote.$name.* configuration values as an object.
182 """
183 try:
184 r = self._remotes[name]
185 except KeyError:
186 r = Remote(self, name)
187 self._remotes[r.name] = r
188 return r
189
190 def GetBranch(self, name):
191 """Get the branch.$name.* configuration values as an object.
192 """
193 try:
194 b = self._branches[name]
195 except KeyError:
196 b = Branch(self, name)
197 self._branches[b.name] = b
198 return b
199
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700200 def GetSubSections(self, section):
201 """List all subsection names matching $section.*.*
202 """
203 return self._sections.get(section, set())
204
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700205 def HasSection(self, section, subsection = ''):
206 """Does at least one key in section.subsection exist?
207 """
208 try:
209 return subsection in self._sections[section]
210 except KeyError:
211 return False
212
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700213 def UrlInsteadOf(self, url):
214 """Resolve any url.*.insteadof references.
215 """
216 for new_url in self.GetSubSections('url'):
217 old_url = self.GetString('url.%s.insteadof' % new_url)
218 if old_url is not None and url.startswith(old_url):
219 return new_url + url[len(old_url):]
220 return url
221
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700222 @property
223 def _sections(self):
224 d = self._section_dict
225 if d is None:
226 d = {}
227 for name in self._cache.keys():
228 p = name.split('.')
229 if 2 == len(p):
230 section = p[0]
231 subsect = ''
232 else:
233 section = p[0]
234 subsect = '.'.join(p[1:-1])
235 if section not in d:
236 d[section] = set()
237 d[section].add(subsect)
238 self._section_dict = d
239 return d
240
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700241 @property
242 def _cache(self):
243 if self._cache_dict is None:
244 self._cache_dict = self._Read()
245 return self._cache_dict
246
247 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700248 d = self._ReadPickle()
249 if d is None:
250 d = self._ReadGit()
251 self._SavePickle(d)
252 return d
253
254 def _ReadPickle(self):
255 try:
256 if os.path.getmtime(self._pickle) \
257 <= os.path.getmtime(self.file):
258 os.remove(self._pickle)
259 return None
260 except OSError:
261 return None
262 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700263 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700264 fd = open(self._pickle, 'rb')
265 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530266 return pickle.load(fd)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700267 finally:
268 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700269 except EOFError:
270 os.remove(self._pickle)
271 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700272 except IOError:
273 os.remove(self._pickle)
274 return None
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530275 except pickle.PickleError:
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700276 os.remove(self._pickle)
277 return None
278
279 def _SavePickle(self, cache):
280 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700281 fd = open(self._pickle, 'wb')
282 try:
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530283 pickle.dump(cache, fd, pickle.HIGHEST_PROTOCOL)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700284 finally:
285 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700286 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700287 if os.path.exists(self._pickle):
288 os.remove(self._pickle)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530289 except pickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700290 if os.path.exists(self._pickle):
291 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700292
293 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700294 """
295 Read configuration data from git.
296
297 This internal method populates the GitConfig cache.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298
David Aguilar438c5472009-06-28 15:09:16 -0700299 """
David Aguilar438c5472009-06-28 15:09:16 -0700300 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700301 d = self._do('--null', '--list')
302 if d is None:
303 return c
David Pursehouse1d947b32012-10-25 12:23:11 +0900304 for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
305 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700306 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900307 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700308 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900309 key = line
310 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700311
312 if key in c:
313 c[key].append(val)
314 else:
315 c[key] = [val]
316
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700317 return c
318
319 def _do(self, *args):
320 command = ['config', '--file', self.file]
321 command.extend(args)
322
323 p = GitCommand(None,
324 command,
325 capture_stdout = True,
326 capture_stderr = True)
327 if p.Wait() == 0:
328 return p.stdout
329 else:
330 GitError('git config %s: %s' % (str(args), p.stderr))
331
332
333class RefSpec(object):
334 """A Git refspec line, split into its components:
335
336 forced: True if the line starts with '+'
337 src: Left side of the line
338 dst: Right side of the line
339 """
340
341 @classmethod
342 def FromString(cls, rs):
343 lhs, rhs = rs.split(':', 2)
344 if lhs.startswith('+'):
345 lhs = lhs[1:]
346 forced = True
347 else:
348 forced = False
349 return cls(forced, lhs, rhs)
350
351 def __init__(self, forced, lhs, rhs):
352 self.forced = forced
353 self.src = lhs
354 self.dst = rhs
355
356 def SourceMatches(self, rev):
357 if self.src:
358 if rev == self.src:
359 return True
360 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
361 return True
362 return False
363
364 def DestMatches(self, ref):
365 if self.dst:
366 if ref == self.dst:
367 return True
368 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
369 return True
370 return False
371
372 def MapSource(self, rev):
373 if self.src.endswith('/*'):
374 return self.dst[:-1] + rev[len(self.src) - 1:]
375 return self.dst
376
377 def __str__(self):
378 s = ''
379 if self.forced:
380 s += '+'
381 if self.src:
382 s += self.src
383 if self.dst:
384 s += ':'
385 s += self.dst
386 return s
387
388
Doug Anderson06d029c2010-10-27 17:06:01 -0700389_master_processes = []
390_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700391_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800392_master_keys_lock = None
393
394def init_ssh():
395 """Should be called once at the start of repo to init ssh master handling.
396
397 At the moment, all we do is to create our lock.
398 """
399 global _master_keys_lock
400 assert _master_keys_lock is None, "Should only call init_ssh once"
401 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700402
Josh Guilfoyle71985722009-08-16 09:44:40 -0700403def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700404 global _ssh_master
405
Doug Anderson0048b692010-12-21 13:39:23 -0800406 # Acquire the lock. This is needed to prevent opening multiple masters for
407 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
408 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
409 # one that was passed to repo init.
410 _master_keys_lock.acquire()
411 try:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700412
Doug Anderson0048b692010-12-21 13:39:23 -0800413 # Check to see whether we already think that the master is running; if we
414 # think it's already running, return right away.
415 if port is not None:
416 key = '%s:%s' % (host, port)
417 else:
418 key = host
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700419
Doug Anderson0048b692010-12-21 13:39:23 -0800420 if key in _master_keys:
421 return True
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700422
Doug Anderson0048b692010-12-21 13:39:23 -0800423 if not _ssh_master \
424 or 'GIT_SSH' in os.environ \
425 or sys.platform in ('win32', 'cygwin'):
426 # failed earlier, or cygwin ssh can't do this
427 #
428 return False
Doug Anderson06d029c2010-10-27 17:06:01 -0700429
Doug Anderson0048b692010-12-21 13:39:23 -0800430 # We will make two calls to ssh; this is the common part of both calls.
431 command_base = ['ssh',
432 '-o','ControlPath %s' % ssh_sock(),
433 host]
434 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900435 command_base[1:1] = ['-p', str(port)]
Doug Anderson06d029c2010-10-27 17:06:01 -0700436
Doug Anderson0048b692010-12-21 13:39:23 -0800437 # Since the key wasn't in _master_keys, we think that master isn't running.
438 # ...but before actually starting a master, we'll double-check. This can
439 # be important because we can't tell that that 'git@myhost.com' is the same
440 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
441 check_command = command_base + ['-O','check']
442 try:
443 Trace(': %s', ' '.join(check_command))
444 check_process = subprocess.Popen(check_command,
445 stdout=subprocess.PIPE,
446 stderr=subprocess.PIPE)
447 check_process.communicate() # read output, but ignore it...
448 isnt_running = check_process.wait()
Josh Guilfoyle71985722009-08-16 09:44:40 -0700449
Doug Anderson0048b692010-12-21 13:39:23 -0800450 if not isnt_running:
451 # Our double-check found that the master _was_ infact running. Add to
452 # the list of keys.
453 _master_keys.add(key)
454 return True
455 except Exception:
456 # Ignore excpetions. We we will fall back to the normal command and print
457 # to the log there.
458 pass
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700459
Doug Anderson0048b692010-12-21 13:39:23 -0800460 command = command_base[:1] + \
461 ['-M', '-N'] + \
462 command_base[1:]
463 try:
464 Trace(': %s', ' '.join(command))
465 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700466 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800467 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700468 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
469 % (host,port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800470 return False
471
472 _master_processes.append(p)
473 _master_keys.add(key)
474 time.sleep(1)
475 return True
476 finally:
477 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700478
479def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800480 global _master_keys_lock
481
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700482 terminate_ssh_clients()
483
Doug Anderson06d029c2010-10-27 17:06:01 -0700484 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700485 try:
486 os.kill(p.pid, SIGTERM)
487 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700488 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700489 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700490 del _master_processes[:]
491 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700492
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700493 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700494 if d:
495 try:
496 os.rmdir(os.path.dirname(d))
497 except OSError:
498 pass
499
Doug Anderson0048b692010-12-21 13:39:23 -0800500 # We're done with the lock, so we can delete it.
501 _master_keys_lock = None
502
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700503URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700504URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700505
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700506def GetSchemeFromUrl(url):
507 m = URI_ALL.match(url)
508 if m:
509 return m.group(1)
510 return None
511
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700512def _preconnect(url):
513 m = URI_ALL.match(url)
514 if m:
515 scheme = m.group(1)
516 host = m.group(2)
517 if ':' in host:
518 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700519 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700520 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700521 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
522 return _open_ssh(host, port)
523 return False
524
525 m = URI_SCP.match(url)
526 if m:
527 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700528 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700529
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700530 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700531
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700532class Remote(object):
533 """Configuration options related to a remote.
534 """
535 def __init__(self, config, name):
536 self._config = config
537 self.name = name
538 self.url = self._Get('url')
539 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800540 self.projectname = self._Get('projectname')
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530541 self.fetch = list(map(RefSpec.FromString,
542 self._Get('fetch', all_keys=True)))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800543 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800544
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100545 def _InsteadOf(self):
546 globCfg = GitConfig.ForUser()
547 urlList = globCfg.GetSubSections('url')
548 longest = ""
549 longestUrl = ""
550
551 for url in urlList:
552 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900553 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100554
555 for insteadOf in insteadOfList:
556 if self.url.startswith(insteadOf) \
557 and len(insteadOf) > len(longest):
558 longest = insteadOf
559 longestUrl = url
560
561 if len(longest) == 0:
562 return self.url
563
564 return self.url.replace(longest, longestUrl, 1)
565
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700566 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100567 connectionUrl = self._InsteadOf()
568 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700569
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800570 def ReviewUrl(self, userEmail):
571 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800572 if self.review is None:
573 return None
574
575 u = self.review
576 if not u.startswith('http:') and not u.startswith('https:'):
577 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700578 if u.endswith('/Gerrit'):
579 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800580 if u.endswith('/ssh_info'):
581 u = u[:len(u) - len('/ssh_info')]
582 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900583 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800584 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800585
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700586 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800587 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700588 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800589 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
590 self._review_url = self._SshReviewUrl(userEmail, host, port)
591 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700592 else:
593 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800594 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700595 info = urllib.request.urlopen(info_url).read()
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700596 if '<' in info:
597 # Assume the server gave us some sort of HTML
598 # response back, like maybe a login page.
599 #
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800600 raise UploadError('%s: Cannot parse response' % info_url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800601
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800602 if info == 'NOT_AVAILABLE':
603 # Assume HTTP if SSH is not enabled.
604 self._review_url = http_url + 'p/'
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700605 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800606 host, port = info.split()
607 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700608 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800609 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700610 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700611 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800612
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800613 REVIEW_CACHE[u] = self._review_url
614 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800615
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800616 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700617 username = self._config.GetString('review.%s.username' % self.review)
618 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800619 username = userEmail.split('@')[0]
620 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700621
622 def ToLocal(self, rev):
623 """Convert a remote revision string to something we have locally.
624 """
625 if IsId(rev):
626 return rev
627 if rev.startswith(R_TAGS):
628 return rev
629
630 if not rev.startswith('refs/'):
631 rev = R_HEADS + rev
632
633 for spec in self.fetch:
634 if spec.SourceMatches(rev):
635 return spec.MapSource(rev)
636 raise GitError('remote %s does not have %s' % (self.name, rev))
637
638 def WritesTo(self, ref):
639 """True if the remote stores to the tracking ref.
640 """
641 for spec in self.fetch:
642 if spec.DestMatches(ref):
643 return True
644 return False
645
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800646 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700647 """Set the fetch refspec to its default value.
648 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800649 if mirror:
650 dst = 'refs/heads/*'
651 else:
652 dst = 'refs/remotes/%s/*' % self.name
653 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700654
655 def Save(self):
656 """Save this remote to the configuration.
657 """
658 self._Set('url', self.url)
659 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800660 self._Set('projectname', self.projectname)
Chirayu Desai217ea7d2013-03-01 19:14:38 +0530661 self._Set('fetch', list(map(str, self.fetch)))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700662
663 def _Set(self, key, value):
664 key = 'remote.%s.%s' % (self.name, key)
665 return self._config.SetString(key, value)
666
David Pursehouse8a68ff92012-09-24 12:15:13 +0900667 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700668 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900669 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700670
671
672class Branch(object):
673 """Configuration options related to a single branch.
674 """
675 def __init__(self, config, name):
676 self._config = config
677 self.name = name
678 self.merge = self._Get('merge')
679
680 r = self._Get('remote')
681 if r:
682 self.remote = self._config.GetRemote(r)
683 else:
684 self.remote = None
685
686 @property
687 def LocalMerge(self):
688 """Convert the merge spec to a local name.
689 """
690 if self.remote and self.merge:
691 return self.remote.ToLocal(self.merge)
692 return None
693
694 def Save(self):
695 """Save this branch back into the configuration.
696 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700697 if self._config.HasSection('branch', self.name):
698 if self.remote:
699 self._Set('remote', self.remote.name)
700 else:
701 self._Set('remote', None)
702 self._Set('merge', self.merge)
703
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700704 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700705 fd = open(self._config.file, 'ab')
706 try:
707 fd.write('[branch "%s"]\n' % self.name)
708 if self.remote:
709 fd.write('\tremote = %s\n' % self.remote.name)
710 if self.merge:
711 fd.write('\tmerge = %s\n' % self.merge)
712 finally:
713 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700714
715 def _Set(self, key, value):
716 key = 'branch.%s.%s' % (self.name, key)
717 return self._config.SetString(key, value)
718
David Pursehouse8a68ff92012-09-24 12:15:13 +0900719 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900721 return self._config.GetString(key, all_keys = all_keys)