blob: 56cc6a24163d888c9ff865a935a25739a5282231 [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
Shawn O. Pearcec12c3602009-04-17 21:03:32 -070017import cPickle
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070018import os
19import re
Shawn O. Pearcefb231612009-04-10 18:53:46 -070020import subprocess
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import sys
Doug Anderson0048b692010-12-21 13:39:23 -080022try:
23 import threading as _threading
24except ImportError:
25 import dummy_threading as _threading
Shawn O. Pearcefb231612009-04-10 18:53:46 -070026import time
Sarah Owens1f7627f2012-10-31 09:21:55 -070027try:
28 import urllib2
29except ImportError:
30 # For python3
31 import urllib.request
32 import urllib.error
33else:
34 # For python2
35 import imp
36 urllib = imp.new_module('urllib')
37 urllib.request = urllib2
38 urllib.error = urllib2
Shawn O. Pearcef00e0ce2009-08-22 18:39:49 -070039
Shawn O. Pearcefb231612009-04-10 18:53:46 -070040from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080041from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070042from trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070043
44from git_command import GitCommand
45from git_command import ssh_sock
46from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070047
48R_HEADS = 'refs/heads/'
49R_TAGS = 'refs/tags/'
David Pursehouse1d947b32012-10-25 12:23:11 +090050ID_RE = re.compile(r'^[0-9a-f]{40}$')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051
Shawn O. Pearce146fe902009-03-25 14:06:43 -070052REVIEW_CACHE = dict()
53
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070054def IsId(rev):
55 return ID_RE.match(rev)
56
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070057def _key(name):
58 parts = name.split('.')
59 if len(parts) < 2:
60 return name.lower()
61 parts[ 0] = parts[ 0].lower()
62 parts[-1] = parts[-1].lower()
63 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070064
65class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070066 _ForUser = None
67
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068 @classmethod
69 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070070 if cls._ForUser is None:
David Pursehouse8a68ff92012-09-24 12:15:13 +090071 cls._ForUser = cls(configfile = os.path.expanduser('~/.gitconfig'))
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070072 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070073
74 @classmethod
75 def ForRepository(cls, gitdir, defaults=None):
David Pursehouse8a68ff92012-09-24 12:15:13 +090076 return cls(configfile = os.path.join(gitdir, 'config'),
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077 defaults = defaults)
78
David Pursehouse8a68ff92012-09-24 12:15:13 +090079 def __init__(self, configfile, defaults=None, pickleFile=None):
80 self.file = configfile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081 self.defaults = defaults
82 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070083 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070084 self._remotes = {}
85 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070086
87 if pickleFile is None:
88 self._pickle = os.path.join(
89 os.path.dirname(self.file),
90 '.repopickle_' + os.path.basename(self.file))
91 else:
92 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093
94 def Has(self, name, include_defaults = True):
95 """Return true if this configuration file has the key.
96 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070097 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070098 return True
99 if include_defaults and self.defaults:
100 return self.defaults.Has(name, include_defaults = True)
101 return False
102
103 def GetBoolean(self, name):
104 """Returns a boolean from the configuration file.
105 None : The value was not defined, or is not a boolean.
106 True : The value was set to true or yes.
107 False: The value was set to false or no.
108 """
109 v = self.GetString(name)
110 if v is None:
111 return None
112 v = v.lower()
113 if v in ('true', 'yes'):
114 return True
115 if v in ('false', 'no'):
116 return False
117 return None
118
David Pursehouse8a68ff92012-09-24 12:15:13 +0900119 def GetString(self, name, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700120 """Get the first value for a key, or None if it is not defined.
121
122 This configuration file is used first, if the key is not
David Pursehouse8a68ff92012-09-24 12:15:13 +0900123 defined or all_keys = True then the defaults are also searched.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700124 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700125 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700126 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700127 except KeyError:
128 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900129 return self.defaults.GetString(name, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130 v = []
131
David Pursehouse8a68ff92012-09-24 12:15:13 +0900132 if not all_keys:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133 if v:
134 return v[0]
135 return None
136
137 r = []
138 r.extend(v)
139 if self.defaults:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900140 r.extend(self.defaults.GetString(name, all_keys = True))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700141 return r
142
143 def SetString(self, name, value):
144 """Set the value(s) for a key.
145 Only this configuration file is modified.
146
147 The supplied value should be either a string,
148 or a list of strings (to store multiple values).
149 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700150 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700151
152 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700153 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 except KeyError:
155 old = []
156
157 if value is None:
158 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700159 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160 self._do('--unset-all', name)
161
162 elif isinstance(value, list):
163 if len(value) == 0:
164 self.SetString(name, None)
165
166 elif len(value) == 1:
167 self.SetString(name, value[0])
168
169 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700170 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171 self._do('--replace-all', name, value[0])
Sarah Owensa6053d52012-11-01 13:36:50 -0700172 for i in range(1, len(value)):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700173 self._do('--add', name, value[i])
174
175 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700176 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700177 self._do('--replace-all', name, value)
178
179 def GetRemote(self, name):
180 """Get the remote.$name.* configuration values as an object.
181 """
182 try:
183 r = self._remotes[name]
184 except KeyError:
185 r = Remote(self, name)
186 self._remotes[r.name] = r
187 return r
188
189 def GetBranch(self, name):
190 """Get the branch.$name.* configuration values as an object.
191 """
192 try:
193 b = self._branches[name]
194 except KeyError:
195 b = Branch(self, name)
196 self._branches[b.name] = b
197 return b
198
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700199 def GetSubSections(self, section):
200 """List all subsection names matching $section.*.*
201 """
202 return self._sections.get(section, set())
203
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700204 def HasSection(self, section, subsection = ''):
205 """Does at least one key in section.subsection exist?
206 """
207 try:
208 return subsection in self._sections[section]
209 except KeyError:
210 return False
211
Shawn O. Pearce13111b42011-09-19 11:00:31 -0700212 def UrlInsteadOf(self, url):
213 """Resolve any url.*.insteadof references.
214 """
215 for new_url in self.GetSubSections('url'):
216 old_url = self.GetString('url.%s.insteadof' % new_url)
217 if old_url is not None and url.startswith(old_url):
218 return new_url + url[len(old_url):]
219 return url
220
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700221 @property
222 def _sections(self):
223 d = self._section_dict
224 if d is None:
225 d = {}
226 for name in self._cache.keys():
227 p = name.split('.')
228 if 2 == len(p):
229 section = p[0]
230 subsect = ''
231 else:
232 section = p[0]
233 subsect = '.'.join(p[1:-1])
234 if section not in d:
235 d[section] = set()
236 d[section].add(subsect)
237 self._section_dict = d
238 return d
239
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700240 @property
241 def _cache(self):
242 if self._cache_dict is None:
243 self._cache_dict = self._Read()
244 return self._cache_dict
245
246 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700247 d = self._ReadPickle()
248 if d is None:
249 d = self._ReadGit()
250 self._SavePickle(d)
251 return d
252
253 def _ReadPickle(self):
254 try:
255 if os.path.getmtime(self._pickle) \
256 <= os.path.getmtime(self.file):
257 os.remove(self._pickle)
258 return None
259 except OSError:
260 return None
261 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700262 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700263 fd = open(self._pickle, 'rb')
264 try:
265 return cPickle.load(fd)
266 finally:
267 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700268 except EOFError:
269 os.remove(self._pickle)
270 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700271 except IOError:
272 os.remove(self._pickle)
273 return None
274 except cPickle.PickleError:
275 os.remove(self._pickle)
276 return None
277
278 def _SavePickle(self, cache):
279 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700280 fd = open(self._pickle, 'wb')
281 try:
282 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
283 finally:
284 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700285 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700286 if os.path.exists(self._pickle):
287 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700288 except cPickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700289 if os.path.exists(self._pickle):
290 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700291
292 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700293 """
294 Read configuration data from git.
295
296 This internal method populates the GitConfig cache.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700297
David Aguilar438c5472009-06-28 15:09:16 -0700298 """
David Aguilar438c5472009-06-28 15:09:16 -0700299 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700300 d = self._do('--null', '--list')
301 if d is None:
302 return c
David Pursehouse1d947b32012-10-25 12:23:11 +0900303 for line in d.rstrip('\0').split('\0'): # pylint: disable=W1401
304 # Backslash is not anomalous
David Aguilar438c5472009-06-28 15:09:16 -0700305 if '\n' in line:
David Pursehousec1b86a22012-11-14 11:36:51 +0900306 key, val = line.split('\n', 1)
David Aguilar438c5472009-06-28 15:09:16 -0700307 else:
David Pursehousec1b86a22012-11-14 11:36:51 +0900308 key = line
309 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700310
311 if key in c:
312 c[key].append(val)
313 else:
314 c[key] = [val]
315
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700316 return c
317
318 def _do(self, *args):
319 command = ['config', '--file', self.file]
320 command.extend(args)
321
322 p = GitCommand(None,
323 command,
324 capture_stdout = True,
325 capture_stderr = True)
326 if p.Wait() == 0:
327 return p.stdout
328 else:
329 GitError('git config %s: %s' % (str(args), p.stderr))
330
331
332class RefSpec(object):
333 """A Git refspec line, split into its components:
334
335 forced: True if the line starts with '+'
336 src: Left side of the line
337 dst: Right side of the line
338 """
339
340 @classmethod
341 def FromString(cls, rs):
342 lhs, rhs = rs.split(':', 2)
343 if lhs.startswith('+'):
344 lhs = lhs[1:]
345 forced = True
346 else:
347 forced = False
348 return cls(forced, lhs, rhs)
349
350 def __init__(self, forced, lhs, rhs):
351 self.forced = forced
352 self.src = lhs
353 self.dst = rhs
354
355 def SourceMatches(self, rev):
356 if self.src:
357 if rev == self.src:
358 return True
359 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
360 return True
361 return False
362
363 def DestMatches(self, ref):
364 if self.dst:
365 if ref == self.dst:
366 return True
367 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
368 return True
369 return False
370
371 def MapSource(self, rev):
372 if self.src.endswith('/*'):
373 return self.dst[:-1] + rev[len(self.src) - 1:]
374 return self.dst
375
376 def __str__(self):
377 s = ''
378 if self.forced:
379 s += '+'
380 if self.src:
381 s += self.src
382 if self.dst:
383 s += ':'
384 s += self.dst
385 return s
386
387
Doug Anderson06d029c2010-10-27 17:06:01 -0700388_master_processes = []
389_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700390_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800391_master_keys_lock = None
392
393def init_ssh():
394 """Should be called once at the start of repo to init ssh master handling.
395
396 At the moment, all we do is to create our lock.
397 """
398 global _master_keys_lock
399 assert _master_keys_lock is None, "Should only call init_ssh once"
400 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700401
Josh Guilfoyle71985722009-08-16 09:44:40 -0700402def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700403 global _ssh_master
404
Doug Anderson0048b692010-12-21 13:39:23 -0800405 # Acquire the lock. This is needed to prevent opening multiple masters for
406 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
407 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
408 # one that was passed to repo init.
409 _master_keys_lock.acquire()
410 try:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700411
Doug Anderson0048b692010-12-21 13:39:23 -0800412 # Check to see whether we already think that the master is running; if we
413 # think it's already running, return right away.
414 if port is not None:
415 key = '%s:%s' % (host, port)
416 else:
417 key = host
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700418
Doug Anderson0048b692010-12-21 13:39:23 -0800419 if key in _master_keys:
420 return True
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700421
Doug Anderson0048b692010-12-21 13:39:23 -0800422 if not _ssh_master \
423 or 'GIT_SSH' in os.environ \
424 or sys.platform in ('win32', 'cygwin'):
425 # failed earlier, or cygwin ssh can't do this
426 #
427 return False
Doug Anderson06d029c2010-10-27 17:06:01 -0700428
Doug Anderson0048b692010-12-21 13:39:23 -0800429 # We will make two calls to ssh; this is the common part of both calls.
430 command_base = ['ssh',
431 '-o','ControlPath %s' % ssh_sock(),
432 host]
433 if port is not None:
David Pursehouse8f62fb72012-11-14 12:09:38 +0900434 command_base[1:1] = ['-p', str(port)]
Doug Anderson06d029c2010-10-27 17:06:01 -0700435
Doug Anderson0048b692010-12-21 13:39:23 -0800436 # Since the key wasn't in _master_keys, we think that master isn't running.
437 # ...but before actually starting a master, we'll double-check. This can
438 # be important because we can't tell that that 'git@myhost.com' is the same
439 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
440 check_command = command_base + ['-O','check']
441 try:
442 Trace(': %s', ' '.join(check_command))
443 check_process = subprocess.Popen(check_command,
444 stdout=subprocess.PIPE,
445 stderr=subprocess.PIPE)
446 check_process.communicate() # read output, but ignore it...
447 isnt_running = check_process.wait()
Josh Guilfoyle71985722009-08-16 09:44:40 -0700448
Doug Anderson0048b692010-12-21 13:39:23 -0800449 if not isnt_running:
450 # Our double-check found that the master _was_ infact running. Add to
451 # the list of keys.
452 _master_keys.add(key)
453 return True
454 except Exception:
455 # Ignore excpetions. We we will fall back to the normal command and print
456 # to the log there.
457 pass
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700458
Doug Anderson0048b692010-12-21 13:39:23 -0800459 command = command_base[:1] + \
460 ['-M', '-N'] + \
461 command_base[1:]
462 try:
463 Trace(': %s', ' '.join(command))
464 p = subprocess.Popen(command)
Sarah Owensa5be53f2012-09-09 15:37:57 -0700465 except Exception as e:
Doug Anderson0048b692010-12-21 13:39:23 -0800466 _ssh_master = False
Sarah Owenscecd1d82012-11-01 22:59:27 -0700467 print('\nwarn: cannot enable ssh control master for %s:%s\n%s'
468 % (host,port, str(e)), file=sys.stderr)
Doug Anderson0048b692010-12-21 13:39:23 -0800469 return False
470
471 _master_processes.append(p)
472 _master_keys.add(key)
473 time.sleep(1)
474 return True
475 finally:
476 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700477
478def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800479 global _master_keys_lock
480
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700481 terminate_ssh_clients()
482
Doug Anderson06d029c2010-10-27 17:06:01 -0700483 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700484 try:
485 os.kill(p.pid, SIGTERM)
486 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700487 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700488 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700489 del _master_processes[:]
490 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700491
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700492 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700493 if d:
494 try:
495 os.rmdir(os.path.dirname(d))
496 except OSError:
497 pass
498
Doug Anderson0048b692010-12-21 13:39:23 -0800499 # We're done with the lock, so we can delete it.
500 _master_keys_lock = None
501
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700502URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce898e12a2012-03-14 15:22:28 -0700503URI_ALL = re.compile(r'^([a-z][a-z+-]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700504
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700505def GetSchemeFromUrl(url):
506 m = URI_ALL.match(url)
507 if m:
508 return m.group(1)
509 return None
510
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700511def _preconnect(url):
512 m = URI_ALL.match(url)
513 if m:
514 scheme = m.group(1)
515 host = m.group(2)
516 if ':' in host:
517 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700518 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700519 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700520 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
521 return _open_ssh(host, port)
522 return False
523
524 m = URI_SCP.match(url)
525 if m:
526 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700527 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700528
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700529 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700530
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700531class Remote(object):
532 """Configuration options related to a remote.
533 """
534 def __init__(self, config, name):
535 self._config = config
536 self.name = name
537 self.url = self._Get('url')
538 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800539 self.projectname = self._Get('projectname')
David Pursehouse7e6dd2d2012-10-25 12:40:51 +0900540 self.fetch = map(RefSpec.FromString,
David Pursehouse8a68ff92012-09-24 12:15:13 +0900541 self._Get('fetch', all_keys=True))
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800542 self._review_url = None
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800543
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100544 def _InsteadOf(self):
545 globCfg = GitConfig.ForUser()
546 urlList = globCfg.GetSubSections('url')
547 longest = ""
548 longestUrl = ""
549
550 for url in urlList:
551 key = "url." + url + ".insteadOf"
David Pursehouse8a68ff92012-09-24 12:15:13 +0900552 insteadOfList = globCfg.GetString(key, all_keys=True)
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100553
554 for insteadOf in insteadOfList:
555 if self.url.startswith(insteadOf) \
556 and len(insteadOf) > len(longest):
557 longest = insteadOf
558 longestUrl = url
559
560 if len(longest) == 0:
561 return self.url
562
563 return self.url.replace(longest, longestUrl, 1)
564
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700565 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100566 connectionUrl = self._InsteadOf()
567 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700568
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800569 def ReviewUrl(self, userEmail):
570 if self._review_url is None:
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800571 if self.review is None:
572 return None
573
574 u = self.review
575 if not u.startswith('http:') and not u.startswith('https:'):
576 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700577 if u.endswith('/Gerrit'):
578 u = u[:len(u) - len('/Gerrit')]
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800579 if u.endswith('/ssh_info'):
580 u = u[:len(u) - len('/ssh_info')]
581 if not u.endswith('/'):
David Pursehouse8a68ff92012-09-24 12:15:13 +0900582 u += '/'
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800583 http_url = u
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800584
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700585 if u in REVIEW_CACHE:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800586 self._review_url = REVIEW_CACHE[u]
Shawn O. Pearce1a68dc52011-10-11 14:12:46 -0700587 elif 'REPO_HOST_PORT_INFO' in os.environ:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800588 host, port = os.environ['REPO_HOST_PORT_INFO'].split()
589 self._review_url = self._SshReviewUrl(userEmail, host, port)
590 REVIEW_CACHE[u] = self._review_url
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700591 else:
592 try:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800593 info_url = u + 'ssh_info'
Sarah Owens1f7627f2012-10-31 09:21:55 -0700594 info = urllib.request.urlopen(info_url).read()
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700595 if '<' in info:
596 # Assume the server gave us some sort of HTML
597 # response back, like maybe a login page.
598 #
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800599 raise UploadError('%s: Cannot parse response' % info_url)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800600
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800601 if info == 'NOT_AVAILABLE':
602 # Assume HTTP if SSH is not enabled.
603 self._review_url = http_url + 'p/'
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700604 else:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800605 host, port = info.split()
606 self._review_url = self._SshReviewUrl(userEmail, host, port)
Sarah Owens1f7627f2012-10-31 09:21:55 -0700607 except urllib.error.HTTPError as e:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800608 raise UploadError('%s: %s' % (self.review, str(e)))
Sarah Owens1f7627f2012-10-31 09:21:55 -0700609 except urllib.error.URLError as e:
Shawn O. Pearcebf1fbb22011-10-11 09:31:58 -0700610 raise UploadError('%s: %s' % (self.review, str(e)))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800611
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800612 REVIEW_CACHE[u] = self._review_url
613 return self._review_url + self.projectname
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800614
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800615 def _SshReviewUrl(self, userEmail, host, port):
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700616 username = self._config.GetString('review.%s.username' % self.review)
617 if username is None:
Shawn O. Pearcec9571422012-01-11 14:58:54 -0800618 username = userEmail.split('@')[0]
619 return 'ssh://%s@%s:%s/' % (username, host, port)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700620
621 def ToLocal(self, rev):
622 """Convert a remote revision string to something we have locally.
623 """
624 if IsId(rev):
625 return rev
626 if rev.startswith(R_TAGS):
627 return rev
628
629 if not rev.startswith('refs/'):
630 rev = R_HEADS + rev
631
632 for spec in self.fetch:
633 if spec.SourceMatches(rev):
634 return spec.MapSource(rev)
635 raise GitError('remote %s does not have %s' % (self.name, rev))
636
637 def WritesTo(self, ref):
638 """True if the remote stores to the tracking ref.
639 """
640 for spec in self.fetch:
641 if spec.DestMatches(ref):
642 return True
643 return False
644
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800645 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700646 """Set the fetch refspec to its default value.
647 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800648 if mirror:
649 dst = 'refs/heads/*'
650 else:
651 dst = 'refs/remotes/%s/*' % self.name
652 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654 def Save(self):
655 """Save this remote to the configuration.
656 """
657 self._Set('url', self.url)
658 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800659 self._Set('projectname', self.projectname)
David Pursehouse7e6dd2d2012-10-25 12:40:51 +0900660 self._Set('fetch', map(str, self.fetch))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700661
662 def _Set(self, key, value):
663 key = 'remote.%s.%s' % (self.name, key)
664 return self._config.SetString(key, value)
665
David Pursehouse8a68ff92012-09-24 12:15:13 +0900666 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700667 key = 'remote.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900668 return self._config.GetString(key, all_keys = all_keys)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700669
670
671class Branch(object):
672 """Configuration options related to a single branch.
673 """
674 def __init__(self, config, name):
675 self._config = config
676 self.name = name
677 self.merge = self._Get('merge')
678
679 r = self._Get('remote')
680 if r:
681 self.remote = self._config.GetRemote(r)
682 else:
683 self.remote = None
684
685 @property
686 def LocalMerge(self):
687 """Convert the merge spec to a local name.
688 """
689 if self.remote and self.merge:
690 return self.remote.ToLocal(self.merge)
691 return None
692
693 def Save(self):
694 """Save this branch back into the configuration.
695 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700696 if self._config.HasSection('branch', self.name):
697 if self.remote:
698 self._Set('remote', self.remote.name)
699 else:
700 self._Set('remote', None)
701 self._Set('merge', self.merge)
702
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700703 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700704 fd = open(self._config.file, 'ab')
705 try:
706 fd.write('[branch "%s"]\n' % self.name)
707 if self.remote:
708 fd.write('\tremote = %s\n' % self.remote.name)
709 if self.merge:
710 fd.write('\tmerge = %s\n' % self.merge)
711 finally:
712 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700713
714 def _Set(self, key, value):
715 key = 'branch.%s.%s' % (self.name, key)
716 return self._config.SetString(key, value)
717
David Pursehouse8a68ff92012-09-24 12:15:13 +0900718 def _Get(self, key, all_keys=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700719 key = 'branch.%s.%s' % (self.name, key)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900720 return self._config.GetString(key, all_keys = all_keys)