blob: 26fc970b04fdcf3decabb5131a4d346bcc2b7595 [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
26from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080027from urllib2 import urlopen, HTTPError
28from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070029from trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070030
31from git_command import GitCommand
32from git_command import ssh_sock
33from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
35R_HEADS = 'refs/heads/'
36R_TAGS = 'refs/tags/'
37ID_RE = re.compile('^[0-9a-f]{40}$')
38
Shawn O. Pearce146fe902009-03-25 14:06:43 -070039REVIEW_CACHE = dict()
40
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070041def IsId(rev):
42 return ID_RE.match(rev)
43
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070044def _key(name):
45 parts = name.split('.')
46 if len(parts) < 2:
47 return name.lower()
48 parts[ 0] = parts[ 0].lower()
49 parts[-1] = parts[-1].lower()
50 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051
52class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070053 _ForUser = None
54
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055 @classmethod
56 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070057 if cls._ForUser is None:
58 cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
59 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070060
61 @classmethod
62 def ForRepository(cls, gitdir, defaults=None):
63 return cls(file = os.path.join(gitdir, 'config'),
64 defaults = defaults)
65
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070066 def __init__(self, file, defaults=None, pickleFile=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067 self.file = file
68 self.defaults = defaults
69 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070070 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070071 self._remotes = {}
72 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070073
74 if pickleFile is None:
75 self._pickle = os.path.join(
76 os.path.dirname(self.file),
77 '.repopickle_' + os.path.basename(self.file))
78 else:
79 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070080
81 def Has(self, name, include_defaults = True):
82 """Return true if this configuration file has the key.
83 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070084 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070085 return True
86 if include_defaults and self.defaults:
87 return self.defaults.Has(name, include_defaults = True)
88 return False
89
90 def GetBoolean(self, name):
91 """Returns a boolean from the configuration file.
92 None : The value was not defined, or is not a boolean.
93 True : The value was set to true or yes.
94 False: The value was set to false or no.
95 """
96 v = self.GetString(name)
97 if v is None:
98 return None
99 v = v.lower()
100 if v in ('true', 'yes'):
101 return True
102 if v in ('false', 'no'):
103 return False
104 return None
105
106 def GetString(self, name, all=False):
107 """Get the first value for a key, or None if it is not defined.
108
109 This configuration file is used first, if the key is not
110 defined or all = True then the defaults are also searched.
111 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700112 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700113 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700114 except KeyError:
115 if self.defaults:
116 return self.defaults.GetString(name, all = all)
117 v = []
118
119 if not all:
120 if v:
121 return v[0]
122 return None
123
124 r = []
125 r.extend(v)
126 if self.defaults:
127 r.extend(self.defaults.GetString(name, all = True))
128 return r
129
130 def SetString(self, name, value):
131 """Set the value(s) for a key.
132 Only this configuration file is modified.
133
134 The supplied value should be either a string,
135 or a list of strings (to store multiple values).
136 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700137 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700138
139 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700140 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700141 except KeyError:
142 old = []
143
144 if value is None:
145 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700146 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700147 self._do('--unset-all', name)
148
149 elif isinstance(value, list):
150 if len(value) == 0:
151 self.SetString(name, None)
152
153 elif len(value) == 1:
154 self.SetString(name, value[0])
155
156 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700157 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700158 self._do('--replace-all', name, value[0])
159 for i in xrange(1, len(value)):
160 self._do('--add', name, value[i])
161
162 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700163 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700164 self._do('--replace-all', name, value)
165
166 def GetRemote(self, name):
167 """Get the remote.$name.* configuration values as an object.
168 """
169 try:
170 r = self._remotes[name]
171 except KeyError:
172 r = Remote(self, name)
173 self._remotes[r.name] = r
174 return r
175
176 def GetBranch(self, name):
177 """Get the branch.$name.* configuration values as an object.
178 """
179 try:
180 b = self._branches[name]
181 except KeyError:
182 b = Branch(self, name)
183 self._branches[b.name] = b
184 return b
185
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700186 def GetSubSections(self, section):
187 """List all subsection names matching $section.*.*
188 """
189 return self._sections.get(section, set())
190
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700191 def HasSection(self, section, subsection = ''):
192 """Does at least one key in section.subsection exist?
193 """
194 try:
195 return subsection in self._sections[section]
196 except KeyError:
197 return False
198
199 @property
200 def _sections(self):
201 d = self._section_dict
202 if d is None:
203 d = {}
204 for name in self._cache.keys():
205 p = name.split('.')
206 if 2 == len(p):
207 section = p[0]
208 subsect = ''
209 else:
210 section = p[0]
211 subsect = '.'.join(p[1:-1])
212 if section not in d:
213 d[section] = set()
214 d[section].add(subsect)
215 self._section_dict = d
216 return d
217
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700218 @property
219 def _cache(self):
220 if self._cache_dict is None:
221 self._cache_dict = self._Read()
222 return self._cache_dict
223
224 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700225 d = self._ReadPickle()
226 if d is None:
227 d = self._ReadGit()
228 self._SavePickle(d)
229 return d
230
231 def _ReadPickle(self):
232 try:
233 if os.path.getmtime(self._pickle) \
234 <= os.path.getmtime(self.file):
235 os.remove(self._pickle)
236 return None
237 except OSError:
238 return None
239 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700240 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700241 fd = open(self._pickle, 'rb')
242 try:
243 return cPickle.load(fd)
244 finally:
245 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700246 except EOFError:
247 os.remove(self._pickle)
248 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700249 except IOError:
250 os.remove(self._pickle)
251 return None
252 except cPickle.PickleError:
253 os.remove(self._pickle)
254 return None
255
256 def _SavePickle(self, cache):
257 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700258 fd = open(self._pickle, 'wb')
259 try:
260 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
261 finally:
262 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700263 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700264 if os.path.exists(self._pickle):
265 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700266 except cPickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700267 if os.path.exists(self._pickle):
268 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700269
270 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700271 """
272 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700273
David Aguilar438c5472009-06-28 15:09:16 -0700274 This internal method populates the GitConfig cache.
275
276 """
David Aguilar438c5472009-06-28 15:09:16 -0700277 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700278 d = self._do('--null', '--list')
279 if d is None:
280 return c
281 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700282 if '\n' in line:
283 key, val = line.split('\n', 1)
284 else:
285 key = line
286 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700287
288 if key in c:
289 c[key].append(val)
290 else:
291 c[key] = [val]
292
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700293 return c
294
295 def _do(self, *args):
296 command = ['config', '--file', self.file]
297 command.extend(args)
298
299 p = GitCommand(None,
300 command,
301 capture_stdout = True,
302 capture_stderr = True)
303 if p.Wait() == 0:
304 return p.stdout
305 else:
306 GitError('git config %s: %s' % (str(args), p.stderr))
307
308
309class RefSpec(object):
310 """A Git refspec line, split into its components:
311
312 forced: True if the line starts with '+'
313 src: Left side of the line
314 dst: Right side of the line
315 """
316
317 @classmethod
318 def FromString(cls, rs):
319 lhs, rhs = rs.split(':', 2)
320 if lhs.startswith('+'):
321 lhs = lhs[1:]
322 forced = True
323 else:
324 forced = False
325 return cls(forced, lhs, rhs)
326
327 def __init__(self, forced, lhs, rhs):
328 self.forced = forced
329 self.src = lhs
330 self.dst = rhs
331
332 def SourceMatches(self, rev):
333 if self.src:
334 if rev == self.src:
335 return True
336 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
337 return True
338 return False
339
340 def DestMatches(self, ref):
341 if self.dst:
342 if ref == self.dst:
343 return True
344 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
345 return True
346 return False
347
348 def MapSource(self, rev):
349 if self.src.endswith('/*'):
350 return self.dst[:-1] + rev[len(self.src) - 1:]
351 return self.dst
352
353 def __str__(self):
354 s = ''
355 if self.forced:
356 s += '+'
357 if self.src:
358 s += self.src
359 if self.dst:
360 s += ':'
361 s += self.dst
362 return s
363
364
Doug Anderson06d029c2010-10-27 17:06:01 -0700365_master_processes = []
366_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700367_ssh_master = True
Doug Anderson0048b692010-12-21 13:39:23 -0800368_master_keys_lock = None
369
370def init_ssh():
371 """Should be called once at the start of repo to init ssh master handling.
372
373 At the moment, all we do is to create our lock.
374 """
375 global _master_keys_lock
376 assert _master_keys_lock is None, "Should only call init_ssh once"
377 _master_keys_lock = _threading.Lock()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700378
Josh Guilfoyle71985722009-08-16 09:44:40 -0700379def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700380 global _ssh_master
381
Doug Anderson0048b692010-12-21 13:39:23 -0800382 # Acquire the lock. This is needed to prevent opening multiple masters for
383 # the same host when we're running "repo sync -jN" (for N > 1) _and_ the
384 # manifest <remote fetch="ssh://xyz"> specifies a different host from the
385 # one that was passed to repo init.
386 _master_keys_lock.acquire()
Doug Anderson06d029c2010-10-27 17:06:01 -0700387 try:
Doug Anderson06d029c2010-10-27 17:06:01 -0700388
Doug Anderson0048b692010-12-21 13:39:23 -0800389 # Check to see whether we already think that the master is running; if we
390 # think it's already running, return right away.
391 if port is not None:
392 key = '%s:%s' % (host, port)
393 else:
394 key = host
395
396 if key in _master_keys:
Doug Anderson06d029c2010-10-27 17:06:01 -0700397 return True
Doug Anderson06d029c2010-10-27 17:06:01 -0700398
Doug Anderson0048b692010-12-21 13:39:23 -0800399 if not _ssh_master \
400 or 'GIT_SSH' in os.environ \
401 or sys.platform in ('win32', 'cygwin'):
402 # failed earlier, or cygwin ssh can't do this
403 #
404 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700405
Doug Anderson0048b692010-12-21 13:39:23 -0800406 # We will make two calls to ssh; this is the common part of both calls.
407 command_base = ['ssh',
408 '-o','ControlPath %s' % ssh_sock(),
409 host]
410 if port is not None:
411 command_base[1:1] = ['-p',str(port)]
412
413 # Since the key wasn't in _master_keys, we think that master isn't running.
414 # ...but before actually starting a master, we'll double-check. This can
415 # be important because we can't tell that that 'git@myhost.com' is the same
416 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
417 check_command = command_base + ['-O','check']
418 try:
419 Trace(': %s', ' '.join(check_command))
420 check_process = subprocess.Popen(check_command,
421 stdout=subprocess.PIPE,
422 stderr=subprocess.PIPE)
423 check_process.communicate() # read output, but ignore it...
424 isnt_running = check_process.wait()
425
426 if not isnt_running:
427 # Our double-check found that the master _was_ infact running. Add to
428 # the list of keys.
429 _master_keys.add(key)
430 return True
431 except Exception:
432 # Ignore excpetions. We we will fall back to the normal command and print
433 # to the log there.
434 pass
435
436 command = command_base[:1] + \
437 ['-M', '-N'] + \
438 command_base[1:]
439 try:
440 Trace(': %s', ' '.join(command))
441 p = subprocess.Popen(command)
442 except Exception, e:
443 _ssh_master = False
444 print >>sys.stderr, \
445 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
446 % (host,port, str(e))
447 return False
448
449 _master_processes.append(p)
450 _master_keys.add(key)
451 time.sleep(1)
452 return True
453 finally:
454 _master_keys_lock.release()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700455
456def close_ssh():
Doug Anderson0048b692010-12-21 13:39:23 -0800457 global _master_keys_lock
458
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700459 terminate_ssh_clients()
460
Doug Anderson06d029c2010-10-27 17:06:01 -0700461 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700462 try:
463 os.kill(p.pid, SIGTERM)
464 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700465 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700466 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700467 del _master_processes[:]
468 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700469
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700470 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700471 if d:
472 try:
473 os.rmdir(os.path.dirname(d))
474 except OSError:
475 pass
476
Doug Anderson0048b692010-12-21 13:39:23 -0800477 # We're done with the lock, so we can delete it.
478 _master_keys_lock = None
479
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700480URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce2f968c92009-04-30 14:30:28 -0700481URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700482
483def _preconnect(url):
484 m = URI_ALL.match(url)
485 if m:
486 scheme = m.group(1)
487 host = m.group(2)
488 if ':' in host:
489 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700490 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700491 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700492 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
493 return _open_ssh(host, port)
494 return False
495
496 m = URI_SCP.match(url)
497 if m:
498 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700499 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700500
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700501 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700502
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700503class Remote(object):
504 """Configuration options related to a remote.
505 """
506 def __init__(self, config, name):
507 self._config = config
508 self.name = name
509 self.url = self._Get('url')
510 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800511 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700512 self.fetch = map(lambda x: RefSpec.FromString(x),
513 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800514 self._review_protocol = None
515
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100516 def _InsteadOf(self):
517 globCfg = GitConfig.ForUser()
518 urlList = globCfg.GetSubSections('url')
519 longest = ""
520 longestUrl = ""
521
522 for url in urlList:
523 key = "url." + url + ".insteadOf"
524 insteadOfList = globCfg.GetString(key, all=True)
525
526 for insteadOf in insteadOfList:
527 if self.url.startswith(insteadOf) \
528 and len(insteadOf) > len(longest):
529 longest = insteadOf
530 longestUrl = url
531
532 if len(longest) == 0:
533 return self.url
534
535 return self.url.replace(longest, longestUrl, 1)
536
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700537 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100538 connectionUrl = self._InsteadOf()
539 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700540
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800541 @property
542 def ReviewProtocol(self):
543 if self._review_protocol is None:
544 if self.review is None:
545 return None
546
547 u = self.review
548 if not u.startswith('http:') and not u.startswith('https:'):
549 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700550 if u.endswith('/Gerrit'):
551 u = u[:len(u) - len('/Gerrit')]
552 if not u.endswith('/ssh_info'):
553 if not u.endswith('/'):
554 u += '/'
555 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800556
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700557 if u in REVIEW_CACHE:
558 info = REVIEW_CACHE[u]
559 self._review_protocol = info[0]
560 self._review_host = info[1]
561 self._review_port = info[2]
562 else:
563 try:
564 info = urlopen(u).read()
565 if info == 'NOT_AVAILABLE':
566 raise UploadError('Upload over ssh unavailable')
567 if '<' in info:
568 # Assume the server gave us some sort of HTML
569 # response back, like maybe a login page.
570 #
571 raise UploadError('Cannot read %s:\n%s' % (u, info))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800572
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700573 self._review_protocol = 'ssh'
574 self._review_host = info.split(" ")[0]
575 self._review_port = info.split(" ")[1]
576 except HTTPError, e:
577 if e.code == 404:
578 self._review_protocol = 'http-post'
579 self._review_host = None
580 self._review_port = None
581 else:
582 raise UploadError('Cannot guess Gerrit version')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800583
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700584 REVIEW_CACHE[u] = (
585 self._review_protocol,
586 self._review_host,
587 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800588 return self._review_protocol
589
590 def SshReviewUrl(self, userEmail):
591 if self.ReviewProtocol != 'ssh':
592 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700593 username = self._config.GetString('review.%s.username' % self.review)
594 if username is None:
595 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800596 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700597 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800598 self._review_host,
599 self._review_port,
600 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601
602 def ToLocal(self, rev):
603 """Convert a remote revision string to something we have locally.
604 """
605 if IsId(rev):
606 return rev
607 if rev.startswith(R_TAGS):
608 return rev
609
610 if not rev.startswith('refs/'):
611 rev = R_HEADS + rev
612
613 for spec in self.fetch:
614 if spec.SourceMatches(rev):
615 return spec.MapSource(rev)
616 raise GitError('remote %s does not have %s' % (self.name, rev))
617
618 def WritesTo(self, ref):
619 """True if the remote stores to the tracking ref.
620 """
621 for spec in self.fetch:
622 if spec.DestMatches(ref):
623 return True
624 return False
625
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800626 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700627 """Set the fetch refspec to its default value.
628 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800629 if mirror:
630 dst = 'refs/heads/*'
631 else:
632 dst = 'refs/remotes/%s/*' % self.name
633 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700634
635 def Save(self):
636 """Save this remote to the configuration.
637 """
638 self._Set('url', self.url)
639 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800640 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641 self._Set('fetch', map(lambda x: str(x), self.fetch))
642
643 def _Set(self, key, value):
644 key = 'remote.%s.%s' % (self.name, key)
645 return self._config.SetString(key, value)
646
647 def _Get(self, key, all=False):
648 key = 'remote.%s.%s' % (self.name, key)
649 return self._config.GetString(key, all = all)
650
651
652class Branch(object):
653 """Configuration options related to a single branch.
654 """
655 def __init__(self, config, name):
656 self._config = config
657 self.name = name
658 self.merge = self._Get('merge')
659
660 r = self._Get('remote')
661 if r:
662 self.remote = self._config.GetRemote(r)
663 else:
664 self.remote = None
665
666 @property
667 def LocalMerge(self):
668 """Convert the merge spec to a local name.
669 """
670 if self.remote and self.merge:
671 return self.remote.ToLocal(self.merge)
672 return None
673
674 def Save(self):
675 """Save this branch back into the configuration.
676 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700677 if self._config.HasSection('branch', self.name):
678 if self.remote:
679 self._Set('remote', self.remote.name)
680 else:
681 self._Set('remote', None)
682 self._Set('merge', self.merge)
683
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700684 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700685 fd = open(self._config.file, 'ab')
686 try:
687 fd.write('[branch "%s"]\n' % self.name)
688 if self.remote:
689 fd.write('\tremote = %s\n' % self.remote.name)
690 if self.merge:
691 fd.write('\tmerge = %s\n' % self.merge)
692 finally:
693 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700694
695 def _Set(self, key, value):
696 key = 'branch.%s.%s' % (self.name, key)
697 return self._config.SetString(key, value)
698
699 def _Get(self, key, all=False):
700 key = 'branch.%s.%s' % (self.name, key)
701 return self._config.GetString(key, all = all)