blob: 4a42c04721a064e45c71ca5abcb7895446047366 [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
Shawn O. Pearcefb231612009-04-10 18:53:46 -070021import time
Shawn O. Pearced2dfac82009-08-22 18:39:49 -070022import urllib2
23
Shawn O. Pearcefb231612009-04-10 18:53:46 -070024from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080025from urllib2 import urlopen, HTTPError
26from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070027from trace import Trace
Shawn O. Pearcefb231612009-04-10 18:53:46 -070028from git_command import GitCommand, _ssh_sock
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070029
30R_HEADS = 'refs/heads/'
31R_TAGS = 'refs/tags/'
32ID_RE = re.compile('^[0-9a-f]{40}$')
33
Shawn O. Pearce146fe902009-03-25 14:06:43 -070034REVIEW_CACHE = dict()
35
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070036def IsId(rev):
37 return ID_RE.match(rev)
38
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070039def _key(name):
40 parts = name.split('.')
41 if len(parts) < 2:
42 return name.lower()
43 parts[ 0] = parts[ 0].lower()
44 parts[-1] = parts[-1].lower()
45 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070046
47class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070048 _ForUser = None
49
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070050 @classmethod
51 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070052 if cls._ForUser is None:
53 cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
54 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070055
56 @classmethod
57 def ForRepository(cls, gitdir, defaults=None):
58 return cls(file = os.path.join(gitdir, 'config'),
59 defaults = defaults)
60
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070061 def __init__(self, file, defaults=None, pickleFile=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070062 self.file = file
63 self.defaults = defaults
64 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070065 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070066 self._remotes = {}
67 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070068
69 if pickleFile is None:
70 self._pickle = os.path.join(
71 os.path.dirname(self.file),
72 '.repopickle_' + os.path.basename(self.file))
73 else:
74 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070075
Shawn O. Pearce98ea26b2009-06-04 19:19:11 -070076 def ClearCache(self):
77 if os.path.exists(self._pickle):
78 os.remove(self._pickle)
79 self._cache_dict = None
80 self._section_dict = None
81 self._remotes = {}
82 self._branches = {}
83
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070084 def Has(self, name, include_defaults = True):
85 """Return true if this configuration file has the key.
86 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070087 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070088 return True
89 if include_defaults and self.defaults:
90 return self.defaults.Has(name, include_defaults = True)
91 return False
92
93 def GetBoolean(self, name):
94 """Returns a boolean from the configuration file.
95 None : The value was not defined, or is not a boolean.
96 True : The value was set to true or yes.
97 False: The value was set to false or no.
98 """
99 v = self.GetString(name)
100 if v is None:
101 return None
102 v = v.lower()
103 if v in ('true', 'yes'):
104 return True
105 if v in ('false', 'no'):
106 return False
107 return None
108
109 def GetString(self, name, all=False):
110 """Get the first value for a key, or None if it is not defined.
111
112 This configuration file is used first, if the key is not
113 defined or all = True then the defaults are also searched.
114 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700115 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700116 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700117 except KeyError:
118 if self.defaults:
119 return self.defaults.GetString(name, all = all)
120 v = []
121
122 if not all:
123 if v:
124 return v[0]
125 return None
126
127 r = []
128 r.extend(v)
129 if self.defaults:
130 r.extend(self.defaults.GetString(name, all = True))
131 return r
132
133 def SetString(self, name, value):
134 """Set the value(s) for a key.
135 Only this configuration file is modified.
136
137 The supplied value should be either a string,
138 or a list of strings (to store multiple values).
139 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700140 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700141
142 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700143 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700144 except KeyError:
145 old = []
146
147 if value is None:
148 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700149 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700150 self._do('--unset-all', name)
151
152 elif isinstance(value, list):
153 if len(value) == 0:
154 self.SetString(name, None)
155
156 elif len(value) == 1:
157 self.SetString(name, value[0])
158
159 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700160 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700161 self._do('--replace-all', name, value[0])
162 for i in xrange(1, len(value)):
163 self._do('--add', name, value[i])
164
165 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700166 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167 self._do('--replace-all', name, value)
168
169 def GetRemote(self, name):
170 """Get the remote.$name.* configuration values as an object.
171 """
172 try:
173 r = self._remotes[name]
174 except KeyError:
175 r = Remote(self, name)
176 self._remotes[r.name] = r
177 return r
178
179 def GetBranch(self, name):
180 """Get the branch.$name.* configuration values as an object.
181 """
182 try:
183 b = self._branches[name]
184 except KeyError:
185 b = Branch(self, name)
186 self._branches[b.name] = b
187 return b
188
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700189 def GetSubSections(self, section):
190 """List all subsection names matching $section.*.*
191 """
192 return self._sections.get(section, set())
193
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700194 def HasSection(self, section, subsection = ''):
195 """Does at least one key in section.subsection exist?
196 """
197 try:
198 return subsection in self._sections[section]
199 except KeyError:
200 return False
201
202 @property
203 def _sections(self):
204 d = self._section_dict
205 if d is None:
206 d = {}
207 for name in self._cache.keys():
208 p = name.split('.')
209 if 2 == len(p):
210 section = p[0]
211 subsect = ''
212 else:
213 section = p[0]
214 subsect = '.'.join(p[1:-1])
215 if section not in d:
216 d[section] = set()
217 d[section].add(subsect)
218 self._section_dict = d
219 return d
220
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700221 @property
222 def _cache(self):
223 if self._cache_dict is None:
224 self._cache_dict = self._Read()
225 return self._cache_dict
226
227 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700228 d = self._ReadPickle()
229 if d is None:
230 d = self._ReadGit()
231 self._SavePickle(d)
232 return d
233
234 def _ReadPickle(self):
235 try:
236 if os.path.getmtime(self._pickle) \
237 <= os.path.getmtime(self.file):
238 os.remove(self._pickle)
239 return None
240 except OSError:
241 return None
242 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700243 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700244 fd = open(self._pickle, 'rb')
245 try:
246 return cPickle.load(fd)
247 finally:
248 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700249 except EOFError:
250 os.remove(self._pickle)
251 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700252 except IOError:
253 os.remove(self._pickle)
254 return None
255 except cPickle.PickleError:
256 os.remove(self._pickle)
257 return None
258
259 def _SavePickle(self, cache):
260 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700261 fd = open(self._pickle, 'wb')
262 try:
263 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
264 finally:
265 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700266 except IOError:
Shawn O. Pearceb0ca41e2009-07-03 20:01:47 -0700267 if os.path.exists(self._pickle):
268 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700269 except cPickle.PickleError:
Shawn O. Pearceb0ca41e2009-07-03 20:01:47 -0700270 if os.path.exists(self._pickle):
271 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700272
273 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700274 """
275 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700276
David Aguilar438c5472009-06-28 15:09:16 -0700277 This internal method populates the GitConfig cache.
278
279 """
David Aguilar438c5472009-06-28 15:09:16 -0700280 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700281 d = self._do('--null', '--list')
282 if d is None:
283 return c
284 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700285 if '\n' in line:
286 key, val = line.split('\n', 1)
287 else:
288 key = line
289 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700290
291 if key in c:
292 c[key].append(val)
293 else:
294 c[key] = [val]
295
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700296 return c
297
298 def _do(self, *args):
299 command = ['config', '--file', self.file]
300 command.extend(args)
301
302 p = GitCommand(None,
303 command,
304 capture_stdout = True,
305 capture_stderr = True)
306 if p.Wait() == 0:
307 return p.stdout
308 else:
309 GitError('git config %s: %s' % (str(args), p.stderr))
310
311
312class RefSpec(object):
313 """A Git refspec line, split into its components:
314
315 forced: True if the line starts with '+'
316 src: Left side of the line
317 dst: Right side of the line
318 """
319
320 @classmethod
321 def FromString(cls, rs):
322 lhs, rhs = rs.split(':', 2)
323 if lhs.startswith('+'):
324 lhs = lhs[1:]
325 forced = True
326 else:
327 forced = False
328 return cls(forced, lhs, rhs)
329
330 def __init__(self, forced, lhs, rhs):
331 self.forced = forced
332 self.src = lhs
333 self.dst = rhs
334
335 def SourceMatches(self, rev):
336 if self.src:
337 if rev == self.src:
338 return True
339 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
340 return True
341 return False
342
343 def DestMatches(self, ref):
344 if self.dst:
345 if ref == self.dst:
346 return True
347 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
348 return True
349 return False
350
351 def MapSource(self, rev):
352 if self.src.endswith('/*'):
353 return self.dst[:-1] + rev[len(self.src) - 1:]
354 return self.dst
355
356 def __str__(self):
357 s = ''
358 if self.forced:
359 s += '+'
360 if self.src:
361 s += self.src
362 if self.dst:
363 s += ':'
364 s += self.dst
365 return s
366
367
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700368_ssh_cache = {}
369_ssh_master = True
370
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700371def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700372 global _ssh_master
373
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700374 if port is not None:
375 key = '%s:%s' % (host, port)
376 else:
377 key = host
378
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700379 if key in _ssh_cache:
380 return True
381
382 if not _ssh_master \
383 or 'GIT_SSH' in os.environ \
Shawn O. Pearce2b5b4ac2009-04-23 17:22:18 -0700384 or sys.platform in ('win32', 'cygwin'):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700385 # failed earlier, or cygwin ssh can't do this
386 #
387 return False
388
389 command = ['ssh',
390 '-o','ControlPath %s' % _ssh_sock(),
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700391 '-M',
392 '-N',
393 host]
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700394
395 if port is not None:
396 command[3:3] = ['-p',str(port)]
397
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700398 try:
399 Trace(': %s', ' '.join(command))
400 p = subprocess.Popen(command)
401 except Exception, e:
402 _ssh_master = False
403 print >>sys.stderr, \
404 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
405 % (host,port, str(e))
406 return False
407
408 _ssh_cache[key] = p
409 time.sleep(1)
410 return True
411
412def close_ssh():
413 for key,p in _ssh_cache.iteritems():
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700414 try:
415 os.kill(p.pid, SIGTERM)
416 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700417 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700418 pass
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700419 _ssh_cache.clear()
420
421 d = _ssh_sock(create=False)
422 if d:
423 try:
424 os.rmdir(os.path.dirname(d))
425 except OSError:
426 pass
427
428URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce2f968c92009-04-30 14:30:28 -0700429URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700430
431def _preconnect(url):
432 m = URI_ALL.match(url)
433 if m:
434 scheme = m.group(1)
435 host = m.group(2)
436 if ':' in host:
437 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700438 else:
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700439 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700440 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
441 return _open_ssh(host, port)
442 return False
443
444 m = URI_SCP.match(url)
445 if m:
446 host = m.group(1)
Josh Guilfoyle4c0f6702009-08-16 09:44:40 -0700447 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700448
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700449 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700450
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700451class Remote(object):
452 """Configuration options related to a remote.
453 """
454 def __init__(self, config, name):
455 self._config = config
456 self.name = name
457 self.url = self._Get('url')
458 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800459 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700460 self.fetch = map(lambda x: RefSpec.FromString(x),
461 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800462 self._review_protocol = None
463
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100464 def _InsteadOf(self):
465 globCfg = GitConfig.ForUser()
466 urlList = globCfg.GetSubSections('url')
467 longest = ""
468 longestUrl = ""
469
470 for url in urlList:
471 key = "url." + url + ".insteadOf"
472 insteadOfList = globCfg.GetString(key, all=True)
473
474 for insteadOf in insteadOfList:
475 if self.url.startswith(insteadOf) \
476 and len(insteadOf) > len(longest):
477 longest = insteadOf
478 longestUrl = url
479
480 if len(longest) == 0:
481 return self.url
482
483 return self.url.replace(longest, longestUrl, 1)
484
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700485 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100486 connectionUrl = self._InsteadOf()
487 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700488
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800489 @property
490 def ReviewProtocol(self):
491 if self._review_protocol is None:
492 if self.review is None:
493 return None
494
495 u = self.review
496 if not u.startswith('http:') and not u.startswith('https:'):
497 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700498 if u.endswith('/Gerrit'):
499 u = u[:len(u) - len('/Gerrit')]
500 if not u.endswith('/ssh_info'):
501 if not u.endswith('/'):
502 u += '/'
503 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800504
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700505 if u in REVIEW_CACHE:
506 info = REVIEW_CACHE[u]
507 self._review_protocol = info[0]
508 self._review_host = info[1]
509 self._review_port = info[2]
510 else:
511 try:
512 info = urlopen(u).read()
513 if info == 'NOT_AVAILABLE':
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700514 raise UploadError('%s: SSH disabled' % self.review)
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700515 if '<' in info:
516 # Assume the server gave us some sort of HTML
517 # response back, like maybe a login page.
518 #
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700519 raise UploadError('%s: Cannot parse response' % u)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800520
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700521 self._review_protocol = 'ssh'
522 self._review_host = info.split(" ")[0]
523 self._review_port = info.split(" ")[1]
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700524 except urllib2.URLError, e:
525 raise UploadError('%s: %s' % (self.review, e.reason[1]))
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700526 except HTTPError, e:
527 if e.code == 404:
528 self._review_protocol = 'http-post'
529 self._review_host = None
530 self._review_port = None
531 else:
Shawn O. Pearced2dfac82009-08-22 18:39:49 -0700532 raise UploadError('Upload over ssh unavailable')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800533
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700534 REVIEW_CACHE[u] = (
535 self._review_protocol,
536 self._review_host,
537 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800538 return self._review_protocol
539
540 def SshReviewUrl(self, userEmail):
541 if self.ReviewProtocol != 'ssh':
542 return None
543 return 'ssh://%s@%s:%s/%s' % (
544 userEmail.split("@")[0],
545 self._review_host,
546 self._review_port,
547 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700548
549 def ToLocal(self, rev):
550 """Convert a remote revision string to something we have locally.
551 """
552 if IsId(rev):
553 return rev
554 if rev.startswith(R_TAGS):
555 return rev
556
557 if not rev.startswith('refs/'):
558 rev = R_HEADS + rev
559
560 for spec in self.fetch:
561 if spec.SourceMatches(rev):
562 return spec.MapSource(rev)
563 raise GitError('remote %s does not have %s' % (self.name, rev))
564
565 def WritesTo(self, ref):
566 """True if the remote stores to the tracking ref.
567 """
568 for spec in self.fetch:
569 if spec.DestMatches(ref):
570 return True
571 return False
572
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800573 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700574 """Set the fetch refspec to its default value.
575 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800576 if mirror:
577 dst = 'refs/heads/*'
578 else:
579 dst = 'refs/remotes/%s/*' % self.name
580 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700581
582 def Save(self):
583 """Save this remote to the configuration.
584 """
585 self._Set('url', self.url)
586 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800587 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700588 self._Set('fetch', map(lambda x: str(x), self.fetch))
589
590 def _Set(self, key, value):
591 key = 'remote.%s.%s' % (self.name, key)
592 return self._config.SetString(key, value)
593
594 def _Get(self, key, all=False):
595 key = 'remote.%s.%s' % (self.name, key)
596 return self._config.GetString(key, all = all)
597
598
599class Branch(object):
600 """Configuration options related to a single branch.
601 """
602 def __init__(self, config, name):
603 self._config = config
604 self.name = name
605 self.merge = self._Get('merge')
606
607 r = self._Get('remote')
608 if r:
609 self.remote = self._config.GetRemote(r)
610 else:
611 self.remote = None
612
613 @property
614 def LocalMerge(self):
615 """Convert the merge spec to a local name.
616 """
617 if self.remote and self.merge:
618 return self.remote.ToLocal(self.merge)
619 return None
620
621 def Save(self):
622 """Save this branch back into the configuration.
623 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700624 if self._config.HasSection('branch', self.name):
625 if self.remote:
626 self._Set('remote', self.remote.name)
627 else:
628 self._Set('remote', None)
629 self._Set('merge', self.merge)
630
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700631 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700632 fd = open(self._config.file, 'ab')
633 try:
634 fd.write('[branch "%s"]\n' % self.name)
635 if self.remote:
636 fd.write('\tremote = %s\n' % self.remote.name)
637 if self.merge:
638 fd.write('\tmerge = %s\n' % self.merge)
639 finally:
640 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700641
642 def _Set(self, key, value):
643 key = 'branch.%s.%s' % (self.name, key)
644 return self._config.SetString(key, value)
645
646 def _Get(self, key, all=False):
647 key = 'branch.%s.%s' % (self.name, key)
648 return self._config.GetString(key, all = all)