Initial Contribution
diff --git a/git_config.py b/git_config.py
new file mode 100644
index 0000000..f6c5bd1
--- /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)