| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- #
- # 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):
- _ForUser = None
- @classmethod
- def ForUser(cls):
- if cls._ForUser is None:
- cls._ForUser = cls(file = os.path.expanduser('~/.gitconfig'))
- return cls._ForUser
- @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)
|