diff options
-rw-r--r-- | lib/git/__init__.py | 1 | ||||
-rw-r--r-- | lib/git/config.py | 100 | ||||
-rw-r--r-- | lib/git/repo.py | 53 | ||||
-rw-r--r-- | test/fixtures/git_config | 23 | ||||
-rw-r--r-- | test/git/test_config.py | 18 | ||||
-rw-r--r-- | test/git/test_repo.py | 18 |
6 files changed, 209 insertions, 4 deletions
diff --git a/lib/git/__init__.py b/lib/git/__init__.py index e2adac62..041d69f7 100644 --- a/lib/git/__init__.py +++ b/lib/git/__init__.py @@ -9,6 +9,7 @@ import inspect __version__ = 'git' +from git.config import GitConfigParser from git.objects import * from git.refs import * from git.actor import Actor diff --git a/lib/git/config.py b/lib/git/config.py new file mode 100644 index 00000000..c7f2c398 --- /dev/null +++ b/lib/git/config.py @@ -0,0 +1,100 @@ +# config.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php +""" +Module containing module parser implementation able to properly read and write +configuration files +""" + +import re +from ConfigParser import RawConfigParser + +class _MetaParserBuilder(type): + """ + Utlity class wrapping methods into decorators that assure read-only properties + """ + +def _needs_values(func): + """Returns method assuring we read values (on demand) before we try to access them""" + return func + +def _ensure_writable(non_const_func): + """Return method that checks whether given non constant function may be called. + If so, the instance will be set dirty""" + + + +class GitConfigParser(RawConfigParser, object): + """ + Implements specifics required to read git style configuration files. + + This variation behaves much like the git.config command such that the configuration + will be read on demand based on the filepath given during initialization. + + The changes will automatically be written once the instance goes out of scope, but + can be triggered manually as well. + + The configuration file will be locked if you intend to change values preventing other + instances to write concurrently. + """ + __metaclass__ = _MetaParserBuilder + + OPTCRE = re.compile( + r'\s?(?P<option>[^:=\s][^:=]*)' # very permissive, incuding leading whitespace + r'\s*(?P<vi>[:=])\s*' # any number of space/tab, + # followed by separator + # (either : or =), followed + # by any # space/tab + r'(?P<value>.*)$' # everything up to eol + ) + + # list of RawConfigParser methods able to change the instance + _mutating_methods_ = tuple() + + + def __init__(self, file_or_files, read_only=True): + """ + Initialize a configuration reader to read the given file_or_files and to + possibly allow changes to it by setting read_only False + """ + self._file_or_files = file_or_files + self._read_only = read_only + self._is_initialized = False + self._is_dirty = False + + def __del__(self): + """ + Write pending changes if required and release locks + """ + + def read(self): + """ + Read configuration information from our file or files + """ + if self._is_initialized: + return + + self._is_initialized = True + + @_ensure_writable + def write(self): + """ + Write our changes to our file + + Raise + AssertionError if this is a read-only writer instance + """ + if not self._is_dirty: + return + + self._is_dirty = False + + @property + def read_only(self): + """ + Returns + True if this instance may change the configuration file + """ + return self._read_only diff --git a/lib/git/repo.py b/lib/git/repo.py index cc4a6c6b..c53a4d9b 100644 --- a/lib/git/repo.py +++ b/lib/git/repo.py @@ -5,6 +5,7 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import os +import sys import re import gzip import StringIO @@ -15,7 +16,7 @@ from cmd import Git from actor import Actor from refs import * from objects import * - +from config import GitConfigParser class Repo(object): """ @@ -30,6 +31,10 @@ class Repo(object): re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$') re_author_committer_start = re.compile(r'^(author|committer)') re_tab_full_line = re.compile(r'^\t(.*)$') + + # invariants + # represents the configuration level of a configuration file + config_level = ("system", "global", "repository") def __init__(self, path=None): """ @@ -125,6 +130,52 @@ class Repo(object): """ return Tag.list_items(self) + def _get_config_path(self, config_level ): + # we do not support an absolute path of the gitconfig on windows , + # use the global config instead + if sys.platform == "win32" and config_level == "system": + config_level = "global" + + if config_level == "system": + return "/etc/gitconfig" + elif config_level == "global": + return os.path.expanduser("~/.gitconfig") + elif config_level == "repository": + return "%s/config" % self.git.git_dir + + raise ValueError( "Invalid configuration level: %r" % config_level ) + + @property + def config_reader(self): + """ + Returns + GitConfigParser allowing to read the full git configuration, but not to write it + + The configuration will include values from the system, user and repository + configuration files. + + NOTE: On windows, system configuration cannot currently be read as the path is + unknown, instead the global path will be used. + """ + files = [ self._get_config_path(f) for f in self.config_level ] + return GitConfigParser(files, read_only=True) + + def config_writer(self, config_level="repository"): + """ + Returns + GitConfigParser allowing to write values of the specified configuration file level. + Config writers should be retrieved, used to change the configuration ,and written + right away as they will lock the configuration file in question and prevent other's + to write it. + + ``config_level`` + One of the following values + system = sytem wide configuration file + global = user level configuration file + repository = configuration file for this repostory only + """ + return GitConfigParser(self._get_config_path(config_level), read_only = False) + def commit(self, rev=None): """ The Commit object for the specified revision diff --git a/test/fixtures/git_config b/test/fixtures/git_config new file mode 100644 index 00000000..3c91985f --- /dev/null +++ b/test/fixtures/git_config @@ -0,0 +1,23 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = git://gitorious.org/~byron/git-python/byrons-clone.git + pushurl = git@gitorious.org:~byron/git-python/byrons-clone.git +[branch "master"] + remote = origin + merge = refs/heads/master +[remote "mainline"] + url = git://gitorious.org/git-python/mainline.git + fetch = +refs/heads/*:refs/remotes/mainline/* +[remote "MartinMarcher"] + url = git://gitorious.org/~martin.marcher/git-python/serverhorror.git + fetch = +refs/heads/*:refs/remotes/MartinMarcher/* +[gui] + geometry = 1316x820+219+243 207 192 +[branch "mainline_performance"] + remote = mainline + merge = refs/heads/master diff --git a/test/git/test_config.py b/test/git/test_config.py new file mode 100644 index 00000000..ab08544f --- /dev/null +++ b/test/git/test_config.py @@ -0,0 +1,18 @@ +# test_config.py +# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors +# +# This module is part of GitPython and is released under +# the BSD License: http://www.opensource.org/licenses/bsd-license.php + +from test.testlib import * +from git import * + +class TestBase(TestCase): + + @classmethod + def setUpAll(cls): + cls.repo = Repo(GIT_REPO) + + def test_base(self): + path = fixture_path("git_config") + self.fail("TODO: Base config writer testing") diff --git a/test/git/test_repo.py b/test/git/test_repo.py index 87332067..843a4b4e 100644 --- a/test/git/test_repo.py +++ b/test/git/test_repo.py @@ -8,9 +8,11 @@ import os, sys from test.testlib import * from git import * -class TestRepo(object): - def setup(self): - self.repo = Repo(GIT_REPO) +class TestRepo(TestCase): + + @classmethod + def setUpAll(cls): + cls.repo = Repo(GIT_REPO) @raises(InvalidGitRepositoryError) def test_new_should_raise_on_invalid_repo_location(self): @@ -219,3 +221,13 @@ class TestRepo(object): # END handle files assert len(self.repo.untracked_files) == (num_recently_untracked - len(files)) + + def test_config_reader(self): + reader = self.repo.config_reader + assert reader.read_only + + def test_config_writer(self): + for config_level in self.repo.config_level: + writer = self.repo.config_writer(config_level) + assert not writer.read_only + # END for each config level |