From cf31fe9b4fb650b27e19f5d7ee7297e383660caf Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- git_config.py | 344 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 git_config.py (limited to 'git_config.py') diff --git a/git_config.py b/git_config.py new file mode 100644 index 00000000..f6c5bd1e --- /dev/null +++ b/git_config.py @@ -0,0 +1,344 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import sys +from error import GitError +from git_command import GitCommand + +R_HEADS = 'refs/heads/' +R_TAGS = 'refs/tags/' +ID_RE = re.compile('^[0-9a-f]{40}$') + +def IsId(rev): + return ID_RE.match(rev) + + +class GitConfig(object): + @classmethod + def ForUser(cls): + return cls(file = os.path.expanduser('~/.gitconfig')) + + @classmethod + def ForRepository(cls, gitdir, defaults=None): + return cls(file = os.path.join(gitdir, 'config'), + defaults = defaults) + + def __init__(self, file, defaults=None): + self.file = file + self.defaults = defaults + self._cache_dict = None + self._remotes = {} + self._branches = {} + + def Has(self, name, include_defaults = True): + """Return true if this configuration file has the key. + """ + name = name.lower() + if name in self._cache: + return True + if include_defaults and self.defaults: + return self.defaults.Has(name, include_defaults = True) + return False + + def GetBoolean(self, name): + """Returns a boolean from the configuration file. + None : The value was not defined, or is not a boolean. + True : The value was set to true or yes. + False: The value was set to false or no. + """ + v = self.GetString(name) + if v is None: + return None + v = v.lower() + if v in ('true', 'yes'): + return True + if v in ('false', 'no'): + return False + return None + + def GetString(self, name, all=False): + """Get the first value for a key, or None if it is not defined. + + This configuration file is used first, if the key is not + defined or all = True then the defaults are also searched. + """ + name = name.lower() + + try: + v = self._cache[name] + except KeyError: + if self.defaults: + return self.defaults.GetString(name, all = all) + v = [] + + if not all: + if v: + return v[0] + return None + + r = [] + r.extend(v) + if self.defaults: + r.extend(self.defaults.GetString(name, all = True)) + return r + + def SetString(self, name, value): + """Set the value(s) for a key. + Only this configuration file is modified. + + The supplied value should be either a string, + or a list of strings (to store multiple values). + """ + name = name.lower() + + try: + old = self._cache[name] + except KeyError: + old = [] + + if value is None: + if old: + del self._cache[name] + self._do('--unset-all', name) + + elif isinstance(value, list): + if len(value) == 0: + self.SetString(name, None) + + elif len(value) == 1: + self.SetString(name, value[0]) + + elif old != value: + self._cache[name] = list(value) + self._do('--replace-all', name, value[0]) + for i in xrange(1, len(value)): + self._do('--add', name, value[i]) + + elif len(old) != 1 or old[0] != value: + self._cache[name] = [value] + self._do('--replace-all', name, value) + + def GetRemote(self, name): + """Get the remote.$name.* configuration values as an object. + """ + try: + r = self._remotes[name] + except KeyError: + r = Remote(self, name) + self._remotes[r.name] = r + return r + + def GetBranch(self, name): + """Get the branch.$name.* configuration values as an object. + """ + try: + b = self._branches[name] + except KeyError: + b = Branch(self, name) + self._branches[b.name] = b + return b + + @property + def _cache(self): + if self._cache_dict is None: + self._cache_dict = self._Read() + return self._cache_dict + + def _Read(self): + d = self._do('--null', '--list') + c = {} + while d: + lf = d.index('\n') + nul = d.index('\0', lf + 1) + + key = d[0:lf] + val = d[lf + 1:nul] + + if key in c: + c[key].append(val) + else: + c[key] = [val] + + d = d[nul + 1:] + return c + + def _do(self, *args): + command = ['config', '--file', self.file] + command.extend(args) + + p = GitCommand(None, + command, + capture_stdout = True, + capture_stderr = True) + if p.Wait() == 0: + return p.stdout + else: + GitError('git config %s: %s' % (str(args), p.stderr)) + + +class RefSpec(object): + """A Git refspec line, split into its components: + + forced: True if the line starts with '+' + src: Left side of the line + dst: Right side of the line + """ + + @classmethod + def FromString(cls, rs): + lhs, rhs = rs.split(':', 2) + if lhs.startswith('+'): + lhs = lhs[1:] + forced = True + else: + forced = False + return cls(forced, lhs, rhs) + + def __init__(self, forced, lhs, rhs): + self.forced = forced + self.src = lhs + self.dst = rhs + + def SourceMatches(self, rev): + if self.src: + if rev == self.src: + return True + if self.src.endswith('/*') and rev.startswith(self.src[:-1]): + return True + return False + + def DestMatches(self, ref): + if self.dst: + if ref == self.dst: + return True + if self.dst.endswith('/*') and ref.startswith(self.dst[:-1]): + return True + return False + + def MapSource(self, rev): + if self.src.endswith('/*'): + return self.dst[:-1] + rev[len(self.src) - 1:] + return self.dst + + def __str__(self): + s = '' + if self.forced: + s += '+' + if self.src: + s += self.src + if self.dst: + s += ':' + s += self.dst + return s + + +class Remote(object): + """Configuration options related to a remote. + """ + def __init__(self, config, name): + self._config = config + self.name = name + self.url = self._Get('url') + self.review = self._Get('review') + self.fetch = map(lambda x: RefSpec.FromString(x), + self._Get('fetch', all=True)) + + def ToLocal(self, rev): + """Convert a remote revision string to something we have locally. + """ + if IsId(rev): + return rev + if rev.startswith(R_TAGS): + return rev + + if not rev.startswith('refs/'): + rev = R_HEADS + rev + + for spec in self.fetch: + if spec.SourceMatches(rev): + return spec.MapSource(rev) + raise GitError('remote %s does not have %s' % (self.name, rev)) + + def WritesTo(self, ref): + """True if the remote stores to the tracking ref. + """ + for spec in self.fetch: + if spec.DestMatches(ref): + return True + return False + + def ResetFetch(self): + """Set the fetch refspec to its default value. + """ + self.fetch = [RefSpec(True, + 'refs/heads/*', + 'refs/remotes/%s/*' % self.name)] + + def Save(self): + """Save this remote to the configuration. + """ + self._Set('url', self.url) + self._Set('review', self.review) + self._Set('fetch', map(lambda x: str(x), self.fetch)) + + def _Set(self, key, value): + key = 'remote.%s.%s' % (self.name, key) + return self._config.SetString(key, value) + + def _Get(self, key, all=False): + key = 'remote.%s.%s' % (self.name, key) + return self._config.GetString(key, all = all) + + +class Branch(object): + """Configuration options related to a single branch. + """ + def __init__(self, config, name): + self._config = config + self.name = name + self.merge = self._Get('merge') + + r = self._Get('remote') + if r: + self.remote = self._config.GetRemote(r) + else: + self.remote = None + + @property + def LocalMerge(self): + """Convert the merge spec to a local name. + """ + if self.remote and self.merge: + return self.remote.ToLocal(self.merge) + return None + + def Save(self): + """Save this branch back into the configuration. + """ + self._Set('merge', self.merge) + if self.remote: + self._Set('remote', self.remote.name) + else: + self._Set('remote', None) + + def _Set(self, key, value): + key = 'branch.%s.%s' % (self.name, key) + return self._config.SetString(key, value) + + def _Get(self, key, all=False): + key = 'branch.%s.%s' % (self.name, key) + return self._config.GetString(key, all = all) -- cgit v1.2.3-54-g00ecf