From cb68eef0865df6aedbc11cd81888625a70da6777 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 25 Nov 2010 17:01:25 +0100 Subject: Moved everything into the git subdirectory - some tests still need to be adjusted --- git/__init__.py | 48 + git/cmd.py | 515 +++++++ git/config.py | 420 ++++++ git/db.py | 61 + git/diff.py | 346 +++++ git/exc.py | 58 + git/ext/gitdb | 1 + git/ez_setup.py | 222 ++++ git/index/__init__.py | 4 + git/index/base.py | 1153 ++++++++++++++++ git/index/fun.py | 322 +++++ git/index/typ.py | 173 +++ git/index/util.py | 86 ++ git/objects/__init__.py | 21 + git/objects/base.py | 172 +++ git/objects/blob.py | 27 + git/objects/commit.py | 465 +++++++ git/objects/fun.py | 199 +++ git/objects/submodule/__init__.py | 2 + git/objects/submodule/base.py | 924 +++++++++++++ git/objects/submodule/root.py | 315 +++++ git/objects/submodule/util.py | 101 ++ git/objects/tag.py | 76 ++ git/objects/tree.py | 280 ++++ git/objects/util.py | 315 +++++ git/odict.py | 1399 ++++++++++++++++++++ git/refs/__init__.py | 21 + git/refs/head.py | 246 ++++ git/refs/log.py | 282 ++++ git/refs/reference.py | 84 ++ git/refs/remote.py | 63 + git/refs/symbolic.py | 618 +++++++++ git/refs/tag.py | 91 ++ git/remote.py | 603 +++++++++ git/repo/__init__.py | 3 + git/repo/base.py | 753 +++++++++++ git/repo/fun.py | 280 ++++ git/setup.py | 86 ++ git/test/__init__.py | 5 + git/test/fixtures/blame | 131 ++ git/test/fixtures/cat_file_blob | 1 + git/test/fixtures/cat_file_blob_nl | 1 + git/test/fixtures/cat_file_blob_size | 1 + git/test/fixtures/diff_2 | 54 + git/test/fixtures/diff_2f | 19 + git/test/fixtures/diff_f | 15 + git/test/fixtures/diff_i | 201 +++ git/test/fixtures/diff_mode_only | 1152 ++++++++++++++++ git/test/fixtures/diff_new_mode | 14 + git/test/fixtures/diff_numstat | 2 + git/test/fixtures/diff_p | 610 +++++++++ git/test/fixtures/diff_rename | 12 + git/test/fixtures/diff_tree_numstat_root | 3 + git/test/fixtures/for_each_ref_with_path_component | Bin 0 -> 84 bytes git/test/fixtures/git_config | 23 + git/test/fixtures/git_config_global | 24 + git/test/fixtures/index | Bin 0 -> 163616 bytes git/test/fixtures/index_merge | Bin 0 -> 9192 bytes git/test/fixtures/ls_tree_a | 7 + git/test/fixtures/ls_tree_b | 2 + git/test/fixtures/ls_tree_commit | 3 + git/test/fixtures/reflog_HEAD | 460 +++++++ git/test/fixtures/reflog_invalid_date | 2 + git/test/fixtures/reflog_invalid_email | 2 + git/test/fixtures/reflog_invalid_newsha | 2 + git/test/fixtures/reflog_invalid_oldsha | 2 + git/test/fixtures/reflog_invalid_sep | 2 + git/test/fixtures/reflog_master | 124 ++ git/test/fixtures/rev_list | 3 + git/test/fixtures/rev_list_bisect_all | 51 + git/test/fixtures/rev_list_commit_diffs | 8 + git/test/fixtures/rev_list_commit_idabbrev | 8 + git/test/fixtures/rev_list_commit_stats | 7 + git/test/fixtures/rev_list_count | 655 +++++++++ git/test/fixtures/rev_list_delta_a | 8 + git/test/fixtures/rev_list_delta_b | 11 + git/test/fixtures/rev_list_single | 7 + git/test/fixtures/rev_parse | 1 + git/test/fixtures/show_empty_commit | 6 + git/test/lib/__init__.py | 13 + git/test/lib/asserts.py | 50 + git/test/lib/helper.py | 245 ++++ git/test/performance/lib.py | 78 ++ git/test/performance/test_commit.py | 99 ++ git/test/performance/test_odb.py | 70 + git/test/performance/test_streams.py | 131 ++ git/test/performance/test_utils.py | 174 +++ git/test/test_actor.py | 36 + git/test/test_base.py | 100 ++ git/test/test_blob.py | 23 + git/test/test_commit.py | 275 ++++ git/test/test_config.py | 102 ++ git/test/test_db.py | 25 + git/test/test_diff.py | 108 ++ git/test/test_fun.py | 251 ++++ git/test/test_git.py | 84 ++ git/test/test_index.py | 669 ++++++++++ git/test/test_reflog.py | 102 ++ git/test/test_refs.py | 521 ++++++++ git/test/test_remote.py | 445 +++++++ git/test/test_repo.py | 604 +++++++++ git/test/test_stats.py | 25 + git/test/test_submodule.py | 546 ++++++++ git/test/test_tree.py | 144 ++ git/test/test_util.py | 109 ++ git/util.py | 602 +++++++++ 106 files changed, 20105 insertions(+) create mode 100644 git/__init__.py create mode 100644 git/cmd.py create mode 100644 git/config.py create mode 100644 git/db.py create mode 100644 git/diff.py create mode 100644 git/exc.py create mode 160000 git/ext/gitdb create mode 100644 git/ez_setup.py create mode 100644 git/index/__init__.py create mode 100644 git/index/base.py create mode 100644 git/index/fun.py create mode 100644 git/index/typ.py create mode 100644 git/index/util.py create mode 100644 git/objects/__init__.py create mode 100644 git/objects/base.py create mode 100644 git/objects/blob.py create mode 100644 git/objects/commit.py create mode 100644 git/objects/fun.py create mode 100644 git/objects/submodule/__init__.py create mode 100644 git/objects/submodule/base.py create mode 100644 git/objects/submodule/root.py create mode 100644 git/objects/submodule/util.py create mode 100644 git/objects/tag.py create mode 100644 git/objects/tree.py create mode 100644 git/objects/util.py create mode 100644 git/odict.py create mode 100644 git/refs/__init__.py create mode 100644 git/refs/head.py create mode 100644 git/refs/log.py create mode 100644 git/refs/reference.py create mode 100644 git/refs/remote.py create mode 100644 git/refs/symbolic.py create mode 100644 git/refs/tag.py create mode 100644 git/remote.py create mode 100644 git/repo/__init__.py create mode 100644 git/repo/base.py create mode 100644 git/repo/fun.py create mode 100755 git/setup.py create mode 100644 git/test/__init__.py create mode 100644 git/test/fixtures/blame create mode 100644 git/test/fixtures/cat_file_blob create mode 100644 git/test/fixtures/cat_file_blob_nl create mode 100644 git/test/fixtures/cat_file_blob_size create mode 100644 git/test/fixtures/diff_2 create mode 100644 git/test/fixtures/diff_2f create mode 100644 git/test/fixtures/diff_f create mode 100644 git/test/fixtures/diff_i create mode 100755 git/test/fixtures/diff_mode_only create mode 100644 git/test/fixtures/diff_new_mode create mode 100644 git/test/fixtures/diff_numstat create mode 100644 git/test/fixtures/diff_p create mode 100644 git/test/fixtures/diff_rename create mode 100644 git/test/fixtures/diff_tree_numstat_root create mode 100644 git/test/fixtures/for_each_ref_with_path_component create mode 100644 git/test/fixtures/git_config create mode 100644 git/test/fixtures/git_config_global create mode 100644 git/test/fixtures/index create mode 100644 git/test/fixtures/index_merge create mode 100644 git/test/fixtures/ls_tree_a create mode 100644 git/test/fixtures/ls_tree_b create mode 100644 git/test/fixtures/ls_tree_commit create mode 100644 git/test/fixtures/reflog_HEAD create mode 100644 git/test/fixtures/reflog_invalid_date create mode 100644 git/test/fixtures/reflog_invalid_email create mode 100644 git/test/fixtures/reflog_invalid_newsha create mode 100644 git/test/fixtures/reflog_invalid_oldsha create mode 100644 git/test/fixtures/reflog_invalid_sep create mode 100644 git/test/fixtures/reflog_master create mode 100644 git/test/fixtures/rev_list create mode 100644 git/test/fixtures/rev_list_bisect_all create mode 100644 git/test/fixtures/rev_list_commit_diffs create mode 100644 git/test/fixtures/rev_list_commit_idabbrev create mode 100644 git/test/fixtures/rev_list_commit_stats create mode 100644 git/test/fixtures/rev_list_count create mode 100644 git/test/fixtures/rev_list_delta_a create mode 100644 git/test/fixtures/rev_list_delta_b create mode 100644 git/test/fixtures/rev_list_single create mode 100644 git/test/fixtures/rev_parse create mode 100644 git/test/fixtures/show_empty_commit create mode 100644 git/test/lib/__init__.py create mode 100644 git/test/lib/asserts.py create mode 100644 git/test/lib/helper.py create mode 100644 git/test/performance/lib.py create mode 100644 git/test/performance/test_commit.py create mode 100644 git/test/performance/test_odb.py create mode 100644 git/test/performance/test_streams.py create mode 100644 git/test/performance/test_utils.py create mode 100644 git/test/test_actor.py create mode 100644 git/test/test_base.py create mode 100644 git/test/test_blob.py create mode 100644 git/test/test_commit.py create mode 100644 git/test/test_config.py create mode 100644 git/test/test_db.py create mode 100644 git/test/test_diff.py create mode 100644 git/test/test_fun.py create mode 100644 git/test/test_git.py create mode 100644 git/test/test_index.py create mode 100644 git/test/test_reflog.py create mode 100644 git/test/test_refs.py create mode 100644 git/test/test_remote.py create mode 100644 git/test/test_repo.py create mode 100644 git/test/test_stats.py create mode 100644 git/test/test_submodule.py create mode 100644 git/test/test_tree.py create mode 100644 git/test/test_util.py create mode 100644 git/util.py (limited to 'git') diff --git a/git/__init__.py b/git/__init__.py new file mode 100644 index 00000000..483ac091 --- /dev/null +++ b/git/__init__.py @@ -0,0 +1,48 @@ +# __init__.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 + +import os +import sys +import inspect + +__version__ = 'git' + + +#{ Initialization +def _init_externals(): + """Initialize external projects by putting them into the path""" + sys.path.append(os.path.join(os.path.dirname(__file__), 'ext')) + +#} END initialization + +################# +_init_externals() +################# + +#{ Imports + +from git.config import GitConfigParser +from git.objects import * +from git.refs import * +from git.diff import * +from git.exc import * +from git.db import * +from git.cmd import Git +from git.repo import Repo +from git.remote import * +from git.index import * +from git.util import ( + LockFile, + BlockingLockFile, + Stats, + Actor + ) + +#} END imports + +__all__ = [ name for name, obj in locals().items() + if not (name.startswith('_') or inspect.ismodule(obj)) ] + diff --git a/git/cmd.py b/git/cmd.py new file mode 100644 index 00000000..60887f5d --- /dev/null +++ b/git/cmd.py @@ -0,0 +1,515 @@ +# cmd.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 + +import os, sys +from util import * +from exc import GitCommandError + +from subprocess import ( + call, + Popen, + PIPE + ) + +# Enables debugging of GitPython's git commands +GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False) + +execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output', + 'with_exceptions', 'as_process', + 'output_stream' ) + +__all__ = ('Git', ) + +def dashify(string): + return string.replace('_', '-') + +class Git(object): + """ + The Git class manages communication with the Git binary. + + It provides a convenient interface to calling the Git binary, such as in:: + + g = Git( git_dir ) + g.init() # calls 'git init' program + rval = g.ls_files() # calls 'git ls-files' program + + ``Debugging`` + Set the GIT_PYTHON_TRACE environment variable print each invocation + of the command to stdout. + Set its value to 'full' to see details about the returned values. + """ + __slots__ = ("_working_dir", "cat_file_all", "cat_file_header") + + # CONFIGURATION + # The size in bytes read from stdout when copying git's output to another stream + max_chunk_size = 1024*64 + + class AutoInterrupt(object): + """Kill/Interrupt the stored process instance once this instance goes out of scope. It is + used to prevent processes piling up in case iterators stop reading. + Besides all attributes are wired through to the contained process object. + + The wait method was overridden to perform automatic status code checking + and possibly raise.""" + __slots__= ("proc", "args") + + def __init__(self, proc, args ): + self.proc = proc + self.args = args + + def __del__(self): + # did the process finish already so we have a return code ? + if self.proc.poll() is not None: + return + + # can be that nothing really exists anymore ... + if os is None: + return + + # try to kill it + try: + os.kill(self.proc.pid, 2) # interrupt signal + except AttributeError: + # try windows + # for some reason, providing None for stdout/stderr still prints something. This is why + # we simply use the shell and redirect to nul. Its slower than CreateProcess, question + # is whether we really want to see all these messages. Its annoying no matter what. + call(("TASKKILL /F /T /PID %s 2>nul 1>nul" % str(self.proc.pid)), shell=True) + # END exception handling + + def __getattr__(self, attr): + return getattr(self.proc, attr) + + def wait(self): + """Wait for the process and return its status code. + + :raise GitCommandError: if the return status is not 0""" + status = self.proc.wait() + if status != 0: + raise GitCommandError(self.args, status, self.proc.stderr.read()) + # END status handling + return status + # END auto interrupt + + class CatFileContentStream(object): + """Object representing a sized read-only stream returning the contents of + an object. + It behaves like a stream, but counts the data read and simulates an empty + stream once our sized content region is empty. + If not all data is read to the end of the objects's lifetime, we read the + rest to assure the underlying stream continues to work""" + + __slots__ = ('_stream', '_nbr', '_size') + + def __init__(self, size, stream): + self._stream = stream + self._size = size + self._nbr = 0 # num bytes read + + # special case: if the object is empty, has null bytes, get the + # final newline right away. + if size == 0: + stream.read(1) + # END handle empty streams + + def read(self, size=-1): + bytes_left = self._size - self._nbr + if bytes_left == 0: + return '' + if size > -1: + # assure we don't try to read past our limit + size = min(bytes_left, size) + else: + # they try to read all, make sure its not more than what remains + size = bytes_left + # END check early depletion + data = self._stream.read(size) + self._nbr += len(data) + + # check for depletion, read our final byte to make the stream usable by others + if self._size - self._nbr == 0: + self._stream.read(1) # final newline + # END finish reading + return data + + def readline(self, size=-1): + if self._nbr == self._size: + return '' + + # clamp size to lowest allowed value + bytes_left = self._size - self._nbr + if size > -1: + size = min(bytes_left, size) + else: + size = bytes_left + # END handle size + + data = self._stream.readline(size) + self._nbr += len(data) + + # handle final byte + if self._size - self._nbr == 0: + self._stream.read(1) + # END finish reading + + return data + + def readlines(self, size=-1): + if self._nbr == self._size: + return list() + + # leave all additional logic to our readline method, we just check the size + out = list() + nbr = 0 + while True: + line = self.readline() + if not line: + break + out.append(line) + if size > -1: + nbr += len(line) + if nbr > size: + break + # END handle size constraint + # END readline loop + return out + + def __iter__(self): + return self + + def next(self): + line = self.readline() + if not line: + raise StopIteration + return line + + def __del__(self): + bytes_left = self._size - self._nbr + if bytes_left: + # read and discard - seeking is impossible within a stream + # includes terminating newline + self._stream.read(bytes_left + 1) + # END handle incomplete read + + + def __init__(self, working_dir=None): + """Initialize this instance with: + + :param working_dir: + Git directory we should work in. If None, we always work in the current + directory as returned by os.getcwd(). + It is meant to be the working tree directory if available, or the + .git directory in case of bare repositories.""" + super(Git, self).__init__() + self._working_dir = working_dir + + # cached command slots + self.cat_file_header = None + self.cat_file_all = None + + def __getattr__(self, name): + """A convenience method as it allows to call the command as if it was + an object. + :return: Callable object that will execute call _call_process with your arguments.""" + if name[:1] == '_': + raise AttributeError(name) + return lambda *args, **kwargs: self._call_process(name, *args, **kwargs) + + @property + def working_dir(self): + """:return: Git directory we are working on""" + return self._working_dir + + def execute(self, command, + istream=None, + with_keep_cwd=False, + with_extended_output=False, + with_exceptions=True, + as_process=False, + output_stream=None, + **subprocess_kwargs + ): + """Handles executing the command on the shell and consumes and returns + the returned information (stdout) + + :param command: + The command argument list to execute. + It should be a string, or a sequence of program arguments. The + program to execute is the first item in the args sequence or string. + + :param istream: + Standard input filehandle passed to subprocess.Popen. + + :param with_keep_cwd: + Whether to use the current working directory from os.getcwd(). + The cmd otherwise uses its own working_dir that it has been initialized + with if possible. + + :param with_extended_output: + Whether to return a (status, stdout, stderr) tuple. + + :param with_exceptions: + Whether to raise an exception when git returns a non-zero status. + + :param as_process: + Whether to return the created process instance directly from which + streams can be read on demand. This will render with_extended_output and + with_exceptions ineffective - the caller will have + to deal with the details himself. + It is important to note that the process will be placed into an AutoInterrupt + wrapper that will interrupt the process once it goes out of scope. If you + use the command in iterators, you should pass the whole process instance + instead of a single stream. + + :param output_stream: + If set to a file-like object, data produced by the git command will be + output to the given stream directly. + This feature only has any effect if as_process is False. Processes will + always be created with a pipe due to issues with subprocess. + This merely is a workaround as data will be copied from the + output pipe to the given output stream directly. + + :param subprocess_kwargs: + Keyword arguments to be passed to subprocess.Popen. Please note that + some of the valid kwargs are already set by this method, the ones you + specify may not be the same ones. + + :return: + * str(output) if extended_output = False (Default) + * tuple(int(status), str(stdout), str(stderr)) if extended_output = True + + if ouput_stream is True, the stdout value will be your output stream: + * output_stream if extended_output = False + * tuple(int(status), output_stream, str(stderr)) if extended_output = True + + :raise GitCommandError: + + :note: + If you add additional keyword arguments to the signature of this method, + you must update the execute_kwargs tuple housed in this module.""" + if GIT_PYTHON_TRACE and not GIT_PYTHON_TRACE == 'full': + print ' '.join(command) + + # Allow the user to have the command executed in their working dir. + if with_keep_cwd or self._working_dir is None: + cwd = os.getcwd() + else: + cwd=self._working_dir + + # Start the process + proc = Popen(command, + cwd=cwd, + stdin=istream, + stderr=PIPE, + stdout=PIPE, + close_fds=(os.name=='posix'),# unsupported on linux + **subprocess_kwargs + ) + if as_process: + return self.AutoInterrupt(proc, command) + + # Wait for the process to return + status = 0 + stdout_value = '' + stderr_value = '' + try: + if output_stream is None: + stdout_value, stderr_value = proc.communicate() + # strip trailing "\n" + if stdout_value.endswith("\n"): + stdout_value = stdout_value[:-1] + if stderr_value.endswith("\n"): + stderr_value = stderr_value[:-1] + status = proc.returncode + else: + stream_copy(proc.stdout, output_stream, self.max_chunk_size) + stdout_value = output_stream + stderr_value = proc.stderr.read() + # strip trailing "\n" + if stderr_value.endswith("\n"): + stderr_value = stderr_value[:-1] + status = proc.wait() + # END stdout handling + finally: + proc.stdout.close() + proc.stderr.close() + + if GIT_PYTHON_TRACE == 'full': + cmdstr = " ".join(command) + if stderr_value: + print "%s -> %d; stdout: '%s'; stderr: '%s'" % (cmdstr, status, stdout_value, stderr_value) + elif stdout_value: + print "%s -> %d; stdout: '%s'" % (cmdstr, status, stdout_value) + else: + print "%s -> %d" % (cmdstr, status) + # END handle debug printing + + if with_exceptions and status != 0: + raise GitCommandError(command, status, stderr_value) + + # Allow access to the command's status code + if with_extended_output: + return (status, stdout_value, stderr_value) + else: + return stdout_value + + def transform_kwargs(self, **kwargs): + """Transforms Python style kwargs into git command line options.""" + args = list() + for k, v in kwargs.items(): + if len(k) == 1: + if v is True: + args.append("-%s" % k) + elif type(v) is not bool: + args.append("-%s%s" % (k, v)) + else: + if v is True: + args.append("--%s" % dashify(k)) + elif type(v) is not bool: + args.append("--%s=%s" % (dashify(k), v)) + return args + + @classmethod + def __unpack_args(cls, arg_list): + if not isinstance(arg_list, (list,tuple)): + return [ str(arg_list) ] + + outlist = list() + for arg in arg_list: + if isinstance(arg_list, (list, tuple)): + outlist.extend(cls.__unpack_args( arg )) + # END recursion + else: + outlist.append(str(arg)) + # END for each arg + return outlist + + def _call_process(self, method, *args, **kwargs): + """Run the given git command with the specified arguments and return + the result as a String + + :param method: + is the command. Contained "_" characters will be converted to dashes, + such as in 'ls_files' to call 'ls-files'. + + :param args: + is the list of arguments. If None is included, it will be pruned. + This allows your commands to call git more conveniently as None + is realized as non-existent + + :param kwargs: + is a dict of keyword arguments. + This function accepts the same optional keyword arguments + as execute(). + + ``Examples``:: + git.rev_list('master', max_count=10, header=True) + + :return: Same as ``execute``""" + # Handle optional arguments prior to calling transform_kwargs + # otherwise these'll end up in args, which is bad. + _kwargs = dict() + for kwarg in execute_kwargs: + try: + _kwargs[kwarg] = kwargs.pop(kwarg) + except KeyError: + pass + + # Prepare the argument list + opt_args = self.transform_kwargs(**kwargs) + + ext_args = self.__unpack_args([a for a in args if a is not None]) + args = opt_args + ext_args + + call = ["git", dashify(method)] + call.extend(args) + + return self.execute(call, **_kwargs) + + def _parse_object_header(self, header_line): + """ + :param header_line: + type_string size_as_int + + :return: (hex_sha, type_string, size_as_int) + + :raise ValueError: if the header contains indication for an error due to + incorrect input sha""" + tokens = header_line.split() + if len(tokens) != 3: + if not tokens: + raise ValueError("SHA could not be resolved, git returned: %r" % (header_line.strip())) + else: + raise ValueError("SHA %s could not be resolved, git returned: %r" % (tokens[0], header_line.strip())) + # END handle actual return value + # END error handling + + if len(tokens[0]) != 40: + raise ValueError("Failed to parse header: %r" % header_line) + return (tokens[0], tokens[1], int(tokens[2])) + + def __prepare_ref(self, ref): + # required for command to separate refs on stdin + refstr = str(ref) # could be ref-object + if refstr.endswith("\n"): + return refstr + return refstr + "\n" + + def __get_persistent_cmd(self, attr_name, cmd_name, *args,**kwargs): + cur_val = getattr(self, attr_name) + if cur_val is not None: + return cur_val + + options = { "istream" : PIPE, "as_process" : True } + options.update( kwargs ) + + cmd = self._call_process( cmd_name, *args, **options ) + setattr(self, attr_name, cmd ) + return cmd + + def __get_object_header(self, cmd, ref): + cmd.stdin.write(self.__prepare_ref(ref)) + cmd.stdin.flush() + return self._parse_object_header(cmd.stdout.readline()) + + def get_object_header(self, ref): + """ Use this method to quickly examine the type and size of the object behind + the given ref. + + :note: The method will only suffer from the costs of command invocation + once and reuses the command in subsequent calls. + + :return: (hexsha, type_string, size_as_int)""" + cmd = self.__get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) + return self.__get_object_header(cmd, ref) + + def get_object_data(self, ref): + """ As get_object_header, but returns object data as well + :return: (hexsha, type_string, size_as_int,data_string) + :note: not threadsafe""" + hexsha, typename, size, stream = self.stream_object_data(ref) + data = stream.read(size) + del(stream) + return (hexsha, typename, size, data) + + def stream_object_data(self, ref): + """As get_object_header, but returns the data as a stream + :return: (hexsha, type_string, size_as_int, stream) + :note: This method is not threadsafe, you need one independent Command instance + per thread to be safe !""" + cmd = self.__get_persistent_cmd("cat_file_all", "cat_file", batch=True) + hexsha, typename, size = self.__get_object_header(cmd, ref) + return (hexsha, typename, size, self.CatFileContentStream(size, cmd.stdout)) + + def clear_cache(self): + """Clear all kinds of internal caches to release resources. + + Currently persistent commands will be interrupted. + + :return: self""" + self.cat_file_all = None + self.cat_file_header = None + return self diff --git a/git/config.py b/git/config.py new file mode 100644 index 00000000..f1a8832e --- /dev/null +++ b/git/config.py @@ -0,0 +1,420 @@ +# 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 +import os +import ConfigParser as cp +import inspect +import cStringIO + +from git.odict import OrderedDict +from git.util import LockFile + +__all__ = ('GitConfigParser', 'SectionConstraint') + +class MetaParserBuilder(type): + """Utlity class wrapping base-class methods into decorators that assure read-only properties""" + def __new__(metacls, name, bases, clsdict): + """ + Equip all base-class methods with a needs_values decorator, and all non-const methods + with a set_dirty_and_flush_changes decorator in addition to that.""" + kmm = '_mutating_methods_' + if kmm in clsdict: + mutating_methods = clsdict[kmm] + for base in bases: + methods = ( t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_") ) + for name, method in methods: + if name in clsdict: + continue + method_with_values = needs_values(method) + if name in mutating_methods: + method_with_values = set_dirty_and_flush_changes(method_with_values) + # END mutating methods handling + + clsdict[name] = method_with_values + # END for each name/method pair + # END for each base + # END if mutating methods configuration is set + + new_type = super(MetaParserBuilder, metacls).__new__(metacls, name, bases, clsdict) + return new_type + + + +def needs_values(func): + """Returns method assuring we read values (on demand) before we try to access them""" + def assure_data_present(self, *args, **kwargs): + self.read() + return func(self, *args, **kwargs) + # END wrapper method + assure_data_present.__name__ = func.__name__ + return assure_data_present + +def set_dirty_and_flush_changes(non_const_func): + """Return method that checks whether given non constant function may be called. + If so, the instance will be set dirty. + Additionally, we flush the changes right to disk""" + def flush_changes(self, *args, **kwargs): + rval = non_const_func(self, *args, **kwargs) + self.write() + return rval + # END wrapper method + flush_changes.__name__ = non_const_func.__name__ + return flush_changes + + +class SectionConstraint(object): + """Constrains a ConfigParser to only option commands which are constrained to + always use the section we have been initialized with. + + It supports all ConfigParser methods that operate on an option""" + __slots__ = ("_config", "_section_name") + _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option", + "remove_section", "remove_option", "options") + + def __init__(self, config, section): + self._config = config + self._section_name = section + + def __getattr__(self, attr): + if attr in self._valid_attrs_: + return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs) + return super(SectionConstraint,self).__getattribute__(attr) + + def _call_config(self, method, *args, **kwargs): + """Call the configuration at the given method which must take a section name + as first argument""" + return getattr(self._config, method)(self._section_name, *args, **kwargs) + + @property + def config(self): + """return: Configparser instance we constrain""" + return self._config + + +class GitConfigParser(cp.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. + + :note: + The config is case-sensitive even when queried, hence section and option names + must match perfectly.""" + __metaclass__ = MetaParserBuilder + + + #{ Configuration + # The lock type determines the type of lock to use in new configuration readers. + # They must be compatible to the LockFile interface. + # A suitable alternative would be the BlockingLockFile + t_lock = LockFile + + #} END configuration + + OPTCRE = re.compile( + r'\s?(?P