blob: 8e3dfb1bcfd249b06aa54c91fe13626783a85ac1 [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
22from signal import SIGTERM
Shawn O. Pearceb54a3922009-01-05 16:18:58 -080023from urllib2 import urlopen, HTTPError
24from error import GitError, UploadError
Shawn O. Pearcead3193a2009-04-18 09:54:51 -070025from trace import Trace
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -070026
27from git_command import GitCommand
28from git_command import ssh_sock
29from git_command import terminate_ssh_clients
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070030
31R_HEADS = 'refs/heads/'
32R_TAGS = 'refs/tags/'
33ID_RE = re.compile('^[0-9a-f]{40}$')
34
Shawn O. Pearce146fe902009-03-25 14:06:43 -070035REVIEW_CACHE = dict()
36
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070037def IsId(rev):
38 return ID_RE.match(rev)
39
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070040def _key(name):
41 parts = name.split('.')
42 if len(parts) < 2:
43 return name.lower()
44 parts[ 0] = parts[ 0].lower()
45 parts[-1] = parts[-1].lower()
46 return '.'.join(parts)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070047
48class GitConfig(object):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070049 _ForUser = None
50
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070051 @classmethod
52 def ForUser(cls):
Shawn O. Pearce90be5c02008-10-29 15:21:24 -070053 if cls._ForUser is None:
54 cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
55 return cls._ForUser
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070056
57 @classmethod
58 def ForRepository(cls, gitdir, defaults=None):
59 return cls(file = os.path.join(gitdir, 'config'),
60 defaults = defaults)
61
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070062 def __init__(self, file, defaults=None, pickleFile=None):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063 self.file = file
64 self.defaults = defaults
65 self._cache_dict = None
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -070066 self._section_dict = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067 self._remotes = {}
68 self._branches = {}
Shawn O. Pearce1b34c912009-05-21 18:52:49 -070069
70 if pickleFile is None:
71 self._pickle = os.path.join(
72 os.path.dirname(self.file),
73 '.repopickle_' + os.path.basename(self.file))
74 else:
75 self._pickle = pickleFile
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070076
77 def Has(self, name, include_defaults = True):
78 """Return true if this configuration file has the key.
79 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -070080 if _key(name) in self._cache:
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081 return True
82 if include_defaults and self.defaults:
83 return self.defaults.Has(name, include_defaults = True)
84 return False
85
86 def GetBoolean(self, name):
87 """Returns a boolean from the configuration file.
88 None : The value was not defined, or is not a boolean.
89 True : The value was set to true or yes.
90 False: The value was set to false or no.
91 """
92 v = self.GetString(name)
93 if v is None:
94 return None
95 v = v.lower()
96 if v in ('true', 'yes'):
97 return True
98 if v in ('false', 'no'):
99 return False
100 return None
101
102 def GetString(self, name, all=False):
103 """Get the first value for a key, or None if it is not defined.
104
105 This configuration file is used first, if the key is not
106 defined or all = True then the defaults are also searched.
107 """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700108 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700109 v = self._cache[_key(name)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700110 except KeyError:
111 if self.defaults:
112 return self.defaults.GetString(name, all = all)
113 v = []
114
115 if not all:
116 if v:
117 return v[0]
118 return None
119
120 r = []
121 r.extend(v)
122 if self.defaults:
123 r.extend(self.defaults.GetString(name, all = True))
124 return r
125
126 def SetString(self, name, value):
127 """Set the value(s) for a key.
128 Only this configuration file is modified.
129
130 The supplied value should be either a string,
131 or a list of strings (to store multiple values).
132 """
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700133 key = _key(name)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700134
135 try:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700136 old = self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700137 except KeyError:
138 old = []
139
140 if value is None:
141 if old:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700142 del self._cache[key]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700143 self._do('--unset-all', name)
144
145 elif isinstance(value, list):
146 if len(value) == 0:
147 self.SetString(name, None)
148
149 elif len(value) == 1:
150 self.SetString(name, value[0])
151
152 elif old != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700153 self._cache[key] = list(value)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700154 self._do('--replace-all', name, value[0])
155 for i in xrange(1, len(value)):
156 self._do('--add', name, value[i])
157
158 elif len(old) != 1 or old[0] != value:
Shawn O. Pearcef8e32732009-04-17 11:00:31 -0700159 self._cache[key] = [value]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700160 self._do('--replace-all', name, value)
161
162 def GetRemote(self, name):
163 """Get the remote.$name.* configuration values as an object.
164 """
165 try:
166 r = self._remotes[name]
167 except KeyError:
168 r = Remote(self, name)
169 self._remotes[r.name] = r
170 return r
171
172 def GetBranch(self, name):
173 """Get the branch.$name.* configuration values as an object.
174 """
175 try:
176 b = self._branches[name]
177 except KeyError:
178 b = Branch(self, name)
179 self._branches[b.name] = b
180 return b
181
Shawn O. Pearce366ad212009-05-19 12:47:37 -0700182 def GetSubSections(self, section):
183 """List all subsection names matching $section.*.*
184 """
185 return self._sections.get(section, set())
186
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700187 def HasSection(self, section, subsection = ''):
188 """Does at least one key in section.subsection exist?
189 """
190 try:
191 return subsection in self._sections[section]
192 except KeyError:
193 return False
194
195 @property
196 def _sections(self):
197 d = self._section_dict
198 if d is None:
199 d = {}
200 for name in self._cache.keys():
201 p = name.split('.')
202 if 2 == len(p):
203 section = p[0]
204 subsect = ''
205 else:
206 section = p[0]
207 subsect = '.'.join(p[1:-1])
208 if section not in d:
209 d[section] = set()
210 d[section].add(subsect)
211 self._section_dict = d
212 return d
213
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700214 @property
215 def _cache(self):
216 if self._cache_dict is None:
217 self._cache_dict = self._Read()
218 return self._cache_dict
219
220 def _Read(self):
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700221 d = self._ReadPickle()
222 if d is None:
223 d = self._ReadGit()
224 self._SavePickle(d)
225 return d
226
227 def _ReadPickle(self):
228 try:
229 if os.path.getmtime(self._pickle) \
230 <= os.path.getmtime(self.file):
231 os.remove(self._pickle)
232 return None
233 except OSError:
234 return None
235 try:
Shawn O. Pearcead3193a2009-04-18 09:54:51 -0700236 Trace(': unpickle %s', self.file)
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700237 fd = open(self._pickle, 'rb')
238 try:
239 return cPickle.load(fd)
240 finally:
241 fd.close()
Shawn O. Pearce2a3a81b2009-06-12 09:10:07 -0700242 except EOFError:
243 os.remove(self._pickle)
244 return None
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700245 except IOError:
246 os.remove(self._pickle)
247 return None
248 except cPickle.PickleError:
249 os.remove(self._pickle)
250 return None
251
252 def _SavePickle(self, cache):
253 try:
Shawn O. Pearce76ca9f82009-04-18 14:48:03 -0700254 fd = open(self._pickle, 'wb')
255 try:
256 cPickle.dump(cache, fd, cPickle.HIGHEST_PROTOCOL)
257 finally:
258 fd.close()
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700259 except IOError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700260 if os.path.exists(self._pickle):
261 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700262 except cPickle.PickleError:
Ulrik Sjölin99482ae2010-10-29 08:23:30 -0700263 if os.path.exists(self._pickle):
264 os.remove(self._pickle)
Shawn O. Pearcec12c3602009-04-17 21:03:32 -0700265
266 def _ReadGit(self):
David Aguilar438c5472009-06-28 15:09:16 -0700267 """
268 Read configuration data from git.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
David Aguilar438c5472009-06-28 15:09:16 -0700270 This internal method populates the GitConfig cache.
271
272 """
David Aguilar438c5472009-06-28 15:09:16 -0700273 c = {}
Shawn O. Pearcec24c7202009-07-02 16:12:57 -0700274 d = self._do('--null', '--list')
275 if d is None:
276 return c
277 for line in d.rstrip('\0').split('\0'):
David Aguilar438c5472009-06-28 15:09:16 -0700278 if '\n' in line:
279 key, val = line.split('\n', 1)
280 else:
281 key = line
282 val = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700283
284 if key in c:
285 c[key].append(val)
286 else:
287 c[key] = [val]
288
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700289 return c
290
291 def _do(self, *args):
292 command = ['config', '--file', self.file]
293 command.extend(args)
294
295 p = GitCommand(None,
296 command,
297 capture_stdout = True,
298 capture_stderr = True)
299 if p.Wait() == 0:
300 return p.stdout
301 else:
302 GitError('git config %s: %s' % (str(args), p.stderr))
303
304
305class RefSpec(object):
306 """A Git refspec line, split into its components:
307
308 forced: True if the line starts with '+'
309 src: Left side of the line
310 dst: Right side of the line
311 """
312
313 @classmethod
314 def FromString(cls, rs):
315 lhs, rhs = rs.split(':', 2)
316 if lhs.startswith('+'):
317 lhs = lhs[1:]
318 forced = True
319 else:
320 forced = False
321 return cls(forced, lhs, rhs)
322
323 def __init__(self, forced, lhs, rhs):
324 self.forced = forced
325 self.src = lhs
326 self.dst = rhs
327
328 def SourceMatches(self, rev):
329 if self.src:
330 if rev == self.src:
331 return True
332 if self.src.endswith('/*') and rev.startswith(self.src[:-1]):
333 return True
334 return False
335
336 def DestMatches(self, ref):
337 if self.dst:
338 if ref == self.dst:
339 return True
340 if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]):
341 return True
342 return False
343
344 def MapSource(self, rev):
345 if self.src.endswith('/*'):
346 return self.dst[:-1] + rev[len(self.src) - 1:]
347 return self.dst
348
349 def __str__(self):
350 s = ''
351 if self.forced:
352 s += '+'
353 if self.src:
354 s += self.src
355 if self.dst:
356 s += ':'
357 s += self.dst
358 return s
359
360
Doug Anderson06d029c2010-10-27 17:06:01 -0700361_master_processes = []
362_master_keys = set()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700363_ssh_master = True
364
Josh Guilfoyle71985722009-08-16 09:44:40 -0700365def _open_ssh(host, port=None):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700366 global _ssh_master
367
Doug Anderson06d029c2010-10-27 17:06:01 -0700368 # Check to see whether we already think that the master is running; if we
369 # think it's already running, return right away.
Josh Guilfoyle71985722009-08-16 09:44:40 -0700370 if port is not None:
371 key = '%s:%s' % (host, port)
372 else:
373 key = host
374
Doug Anderson06d029c2010-10-27 17:06:01 -0700375 if key in _master_keys:
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700376 return True
377
378 if not _ssh_master \
379 or 'GIT_SSH' in os.environ \
Shawn O. Pearce2b5b4ac2009-04-23 17:22:18 -0700380 or sys.platform in ('win32', 'cygwin'):
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700381 # failed earlier, or cygwin ssh can't do this
382 #
383 return False
384
Doug Anderson06d029c2010-10-27 17:06:01 -0700385 # We will make two calls to ssh; this is the common part of both calls.
386 command_base = ['ssh',
387 '-o','ControlPath %s' % ssh_sock(),
388 host]
Josh Guilfoyle71985722009-08-16 09:44:40 -0700389 if port is not None:
Doug Anderson06d029c2010-10-27 17:06:01 -0700390 command_base[1:1] = ['-p',str(port)]
Josh Guilfoyle71985722009-08-16 09:44:40 -0700391
Doug Anderson06d029c2010-10-27 17:06:01 -0700392 # Since the key wasn't in _master_keys, we think that master isn't running.
393 # ...but before actually starting a master, we'll double-check. This can
394 # be important because we can't tell that that 'git@myhost.com' is the same
395 # as 'myhost.com' where "User git" is setup in the user's ~/.ssh/config file.
396 check_command = command_base + ['-O','check']
397 try:
398 Trace(': %s', ' '.join(check_command))
399 check_process = subprocess.Popen(check_command,
400 stdout=subprocess.PIPE,
401 stderr=subprocess.PIPE)
402 check_process.communicate() # read output, but ignore it...
403 isnt_running = check_process.wait()
404
405 if not isnt_running:
406 # Our double-check found that the master _was_ infact running. Add to
407 # the list of keys.
408 _master_keys.add(key)
409 return True
410 except Exception:
411 # Ignore excpetions. We we will fall back to the normal command and print
412 # to the log there.
413 pass
414
415 command = command_base[:1] + \
416 ['-M', '-N'] + \
417 command_base[1:]
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700418 try:
419 Trace(': %s', ' '.join(command))
420 p = subprocess.Popen(command)
421 except Exception, e:
422 _ssh_master = False
423 print >>sys.stderr, \
424 '\nwarn: cannot enable ssh control master for %s:%s\n%s' \
425 % (host,port, str(e))
426 return False
427
Doug Anderson06d029c2010-10-27 17:06:01 -0700428 _master_processes.append(p)
429 _master_keys.add(key)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700430 time.sleep(1)
431 return True
432
433def close_ssh():
Shawn O. Pearceca8c32c2010-05-11 18:21:33 -0700434 terminate_ssh_clients()
435
Doug Anderson06d029c2010-10-27 17:06:01 -0700436 for p in _master_processes:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700437 try:
438 os.kill(p.pid, SIGTERM)
439 p.wait()
Shawn O. Pearcefb5c8fd2009-06-16 14:57:46 -0700440 except OSError:
Shawn O. Pearce26120ca2009-06-16 11:49:10 -0700441 pass
Doug Anderson06d029c2010-10-27 17:06:01 -0700442 del _master_processes[:]
443 _master_keys.clear()
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700444
Nico Sallembien1c85f4e2010-04-27 14:35:27 -0700445 d = ssh_sock(create=False)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700446 if d:
447 try:
448 os.rmdir(os.path.dirname(d))
449 except OSError:
450 pass
451
452URI_SCP = re.compile(r'^([^@:]*@?[^:/]{1,}):')
Shawn O. Pearce2f968c92009-04-30 14:30:28 -0700453URI_ALL = re.compile(r'^([a-z][a-z+]*)://([^@/]*@?[^/]*)/')
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700454
455def _preconnect(url):
456 m = URI_ALL.match(url)
457 if m:
458 scheme = m.group(1)
459 host = m.group(2)
460 if ':' in host:
461 host, port = host.split(':')
Shawn O. Pearce896d5df2009-04-21 14:51:04 -0700462 else:
Josh Guilfoyle71985722009-08-16 09:44:40 -0700463 port = None
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700464 if scheme in ('ssh', 'git+ssh', 'ssh+git'):
465 return _open_ssh(host, port)
466 return False
467
468 m = URI_SCP.match(url)
469 if m:
470 host = m.group(1)
Josh Guilfoyle71985722009-08-16 09:44:40 -0700471 return _open_ssh(host)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700472
Shawn O. Pearce7b4f4352009-06-12 09:06:35 -0700473 return False
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700474
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700475class Remote(object):
476 """Configuration options related to a remote.
477 """
478 def __init__(self, config, name):
479 self._config = config
480 self.name = name
481 self.url = self._Get('url')
482 self.review = self._Get('review')
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800483 self.projectname = self._Get('projectname')
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700484 self.fetch = map(lambda x: RefSpec.FromString(x),
485 self._Get('fetch', all=True))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800486 self._review_protocol = None
487
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100488 def _InsteadOf(self):
489 globCfg = GitConfig.ForUser()
490 urlList = globCfg.GetSubSections('url')
491 longest = ""
492 longestUrl = ""
493
494 for url in urlList:
495 key = "url." + url + ".insteadOf"
496 insteadOfList = globCfg.GetString(key, all=True)
497
498 for insteadOf in insteadOfList:
499 if self.url.startswith(insteadOf) \
500 and len(insteadOf) > len(longest):
501 longest = insteadOf
502 longestUrl = url
503
504 if len(longest) == 0:
505 return self.url
506
507 return self.url.replace(longest, longestUrl, 1)
508
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700509 def PreConnectFetch(self):
Ulrik Sjolinb6ea3bf2010-01-03 18:20:17 +0100510 connectionUrl = self._InsteadOf()
511 return _preconnect(connectionUrl)
Shawn O. Pearcefb231612009-04-10 18:53:46 -0700512
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800513 @property
514 def ReviewProtocol(self):
515 if self._review_protocol is None:
516 if self.review is None:
517 return None
518
519 u = self.review
520 if not u.startswith('http:') and not u.startswith('https:'):
521 u = 'http://%s' % u
Shawn O. Pearce13cc3842009-03-25 13:54:54 -0700522 if u.endswith('/Gerrit'):
523 u = u[:len(u) - len('/Gerrit')]
524 if not u.endswith('/ssh_info'):
525 if not u.endswith('/'):
526 u += '/'
527 u += 'ssh_info'
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800528
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700529 if u in REVIEW_CACHE:
530 info = REVIEW_CACHE[u]
531 self._review_protocol = info[0]
532 self._review_host = info[1]
533 self._review_port = info[2]
534 else:
535 try:
536 info = urlopen(u).read()
537 if info == 'NOT_AVAILABLE':
538 raise UploadError('Upload over ssh unavailable')
539 if '<' in info:
540 # Assume the server gave us some sort of HTML
541 # response back, like maybe a login page.
542 #
543 raise UploadError('Cannot read %s:\n%s' % (u, info))
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800544
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700545 self._review_protocol = 'ssh'
546 self._review_host = info.split(" ")[0]
547 self._review_port = info.split(" ")[1]
548 except HTTPError, e:
549 if e.code == 404:
550 self._review_protocol = 'http-post'
551 self._review_host = None
552 self._review_port = None
553 else:
554 raise UploadError('Cannot guess Gerrit version')
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800555
Shawn O. Pearce146fe902009-03-25 14:06:43 -0700556 REVIEW_CACHE[u] = (
557 self._review_protocol,
558 self._review_host,
559 self._review_port)
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800560 return self._review_protocol
561
562 def SshReviewUrl(self, userEmail):
563 if self.ReviewProtocol != 'ssh':
564 return None
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700565 username = self._config.GetString('review.%s.username' % self.review)
566 if username is None:
567 username = userEmail.split("@")[0]
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800568 return 'ssh://%s@%s:%s/%s' % (
Shawn O. Pearce3575b8f2010-07-15 17:00:14 -0700569 username,
Shawn O. Pearceb54a3922009-01-05 16:18:58 -0800570 self._review_host,
571 self._review_port,
572 self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700573
574 def ToLocal(self, rev):
575 """Convert a remote revision string to something we have locally.
576 """
577 if IsId(rev):
578 return rev
579 if rev.startswith(R_TAGS):
580 return rev
581
582 if not rev.startswith('refs/'):
583 rev = R_HEADS + rev
584
585 for spec in self.fetch:
586 if spec.SourceMatches(rev):
587 return spec.MapSource(rev)
588 raise GitError('remote %s does not have %s' % (self.name, rev))
589
590 def WritesTo(self, ref):
591 """True if the remote stores to the tracking ref.
592 """
593 for spec in self.fetch:
594 if spec.DestMatches(ref):
595 return True
596 return False
597
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800598 def ResetFetch(self, mirror=False):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599 """Set the fetch refspec to its default value.
600 """
Shawn O. Pearcee284ad12008-11-04 07:37:10 -0800601 if mirror:
602 dst = 'refs/heads/*'
603 else:
604 dst = 'refs/remotes/%s/*' % self.name
605 self.fetch = [RefSpec(True, 'refs/heads/*', dst)]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700606
607 def Save(self):
608 """Save this remote to the configuration.
609 """
610 self._Set('url', self.url)
611 self._Set('review', self.review)
Shawn O. Pearce339ba9f2008-11-06 09:52:51 -0800612 self._Set('projectname', self.projectname)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700613 self._Set('fetch', map(lambda x: str(x), self.fetch))
614
615 def _Set(self, key, value):
616 key = 'remote.%s.%s' % (self.name, key)
617 return self._config.SetString(key, value)
618
619 def _Get(self, key, all=False):
620 key = 'remote.%s.%s' % (self.name, key)
621 return self._config.GetString(key, all = all)
622
623
624class Branch(object):
625 """Configuration options related to a single branch.
626 """
627 def __init__(self, config, name):
628 self._config = config
629 self.name = name
630 self.merge = self._Get('merge')
631
632 r = self._Get('remote')
633 if r:
634 self.remote = self._config.GetRemote(r)
635 else:
636 self.remote = None
637
638 @property
639 def LocalMerge(self):
640 """Convert the merge spec to a local name.
641 """
642 if self.remote and self.merge:
643 return self.remote.ToLocal(self.merge)
644 return None
645
646 def Save(self):
647 """Save this branch back into the configuration.
648 """
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700649 if self._config.HasSection('branch', self.name):
650 if self.remote:
651 self._Set('remote', self.remote.name)
652 else:
653 self._Set('remote', None)
654 self._Set('merge', self.merge)
655
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700656 else:
Shawn O. Pearceaccc56d2009-04-18 14:45:51 -0700657 fd = open(self._config.file, 'ab')
658 try:
659 fd.write('[branch "%s"]\n' % self.name)
660 if self.remote:
661 fd.write('\tremote = %s\n' % self.remote.name)
662 if self.merge:
663 fd.write('\tmerge = %s\n' % self.merge)
664 finally:
665 fd.close()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700666
667 def _Set(self, key, value):
668 key = 'branch.%s.%s' % (self.name, key)
669 return self._config.SetString(key, value)
670
671 def _Get(self, key, all=False):
672 key = 'branch.%s.%s' % (self.name, key)
673 return self._config.GetString(key, all = all)