From 5991698ee2b3046bbc9cfc3bd2abd3a881f514dd Mon Sep 17 00:00:00 2001 From: "Marcus R. Brown" Date: Fri, 11 Jan 2013 13:43:49 -0700 Subject: [PATCH 0001/2943] Support repos that use the .git-file mechanism. --- git/repo/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/git/repo/base.py b/git/repo/base.py index 20c96b228..df52137eb 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -71,6 +71,7 @@ class Repo(object): re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{4,40}$') re_author_committer_start = re.compile(r'^(author|committer)') re_tab_full_line = re.compile(r'^\t(.*)$') + re_git_file_gitdir = re.compile('gitdir: (.*)') # invariants # represents the configuration level of a configuration file @@ -113,6 +114,17 @@ def __init__(self, path=None, odbt = DefaultDBType): self.git_dir = gitpath self._working_tree_dir = curpath break + if isfile(gitpath): + line = open(gitpath, 'r').readline().strip() + match = self.re_git_file_gitdir.match(line) + if match: + gitpath = match.group(1) + if not os.path.isabs(gitpath): + gitpath = os.path.normpath(join(curpath, gitpath)) + if is_git_dir(gitpath): + self.git_dir = gitpath + self._working_tree_dir = curpath + break curpath, dummy = os.path.split(curpath) if not dummy: break From 3621c06c3173bff395645bd416f0efafa20a1da6 Mon Sep 17 00:00:00 2001 From: "Marcus R. Brown" Date: Fri, 11 Jan 2013 13:47:06 -0700 Subject: [PATCH 0002/2943] Add tests for .git-file. --- git/test/fixtures/git_file | 1 + git/test/test_repo.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 git/test/fixtures/git_file diff --git a/git/test/fixtures/git_file b/git/test/fixtures/git_file new file mode 100644 index 000000000..2efda9f50 --- /dev/null +++ b/git/test/fixtures/git_file @@ -0,0 +1 @@ +gitdir: ./.real diff --git a/git/test/test_repo.py b/git/test/test_repo.py index 18d5c1b84..a4d148d18 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -594,6 +594,23 @@ def test_repo_odbtype(self): target_type = GitCmdObjectDB assert isinstance(self.rorepo.odb, target_type) + @with_rw_repo('HEAD') + def test_git_file(self, rwrepo): + # Move the .git directory to another location and create the .git file. + real_path_abs = os.path.abspath(join_path_native(rwrepo.working_tree_dir, '.real')) + os.rename(rwrepo.git_dir, real_path_abs) + git_file_path = join_path_native(rwrepo.working_tree_dir, '.git') + open(git_file_path, 'wb').write(fixture('git_file')) + + # Create a repo and make sure it's pointing to the relocated .git directory. + git_file_repo = Repo(rwrepo.working_tree_dir) + assert os.path.abspath(git_file_repo.git_dir) == real_path_abs + + # Test using an absolute gitdir path in the .git file. + open(git_file_path, 'wb').write('gitdir: %s\n' % real_path_abs) + git_file_repo = Repo(rwrepo.working_tree_dir) + assert os.path.abspath(git_file_repo.git_dir) == real_path_abs + def test_submodules(self): assert len(self.rorepo.submodules) == 1 # non-recursive assert len(list(self.rorepo.iter_submodules())) >= 2 From 53b65e074e4d62ea5d0251b37c35fd055e403110 Mon Sep 17 00:00:00 2001 From: niyaton Date: Mon, 25 Feb 2013 01:22:30 +0900 Subject: [PATCH 0003/2943] Added support for separeted git dir. --- git/repo/base.py | 6 ++++++ git/repo/fun.py | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/git/repo/base.py b/git/repo/base.py index 20c96b228..7dcf409dc 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -32,6 +32,7 @@ from fun import ( rev_parse, is_git_dir, + read_gitfile, touch ) @@ -113,6 +114,11 @@ def __init__(self, path=None, odbt = DefaultDBType): self.git_dir = gitpath self._working_tree_dir = curpath break + gitpath = read_gitfile(gitpath) + if gitpath: + self.git_dir = gitpath + self._working_tree_dir = curpath + break curpath, dummy = os.path.split(curpath) if not dummy: break diff --git a/git/repo/fun.py b/git/repo/fun.py index 03d557164..86d3c6a99 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -30,6 +30,17 @@ def is_git_dir(d): os.readlink(headref).startswith('refs')) return False +def read_gitfile(f): + """ This is taken from the git setup.c:read_gitfile function. + :return gitdir path or None if gitfile is invalid.""" + + if not isfile(f): + return None + line = open(f, 'r').readline().rstrip() + if line[0:8] != 'gitdir: ': + return None + path = os.path.realpath(line[8:]) + return path if is_git_dir(path) else None def short_to_long(odb, hexsha): """:return: long hexadecimal sha1 from the given less-than-40 byte hexsha From 3f277ba01f9a93fb040a365eef80f46ce6a9de85 Mon Sep 17 00:00:00 2001 From: BoppreH Date: Thu, 17 Oct 2013 21:47:55 -0300 Subject: [PATCH 0004/2943] Avoid spawning console windows when running from .pyw By adding `shell=True,` to the list of Popen parameters, we avoid spawning console windows when scripts call this method from a windowless (.pyw) Python script. --- git/cmd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/git/cmd.py b/git/cmd.py index 576a5300a..579fbc83a 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -336,6 +336,7 @@ def execute(self, command, stderr=PIPE, stdout=PIPE, close_fds=(os.name=='posix'),# unsupported on linux + shell=True, **subprocess_kwargs ) if as_process: From 9d0473c1d1e6cadd986102712fff9196fff96212 Mon Sep 17 00:00:00 2001 From: firm1 Date: Mon, 24 Mar 2014 14:49:33 +0100 Subject: [PATCH 0005/2943] update commit function --- git/index/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/index/base.py b/git/index/base.py index 3bd8634c7..0c1b68d94 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -873,7 +873,7 @@ def move(self, items, skip_errors=False, **kwargs): return out - def commit(self, message, parent_commits=None, head=True): + def def commit(self, message, parent_commits=None, head=True, author=None, committer=None): """Commit the current default index file, creating a commit object. For more information on the arguments, see tree.commit. @@ -884,7 +884,7 @@ def commit(self, message, parent_commits=None, head=True): :return: Commit object representing the new commit""" tree = self.write_tree() - return Commit.create_from_tree(self.repo, tree, message, parent_commits, head) + return Commit.create_from_tree(self.repo, tree, message, parent_commits, head, author=author, committer=committer) @classmethod def _flush_stdin_and_wait(cls, proc, ignore_stdout = False): From 5d602f267c32e1e917599d9bcdcfec4eef05d477 Mon Sep 17 00:00:00 2001 From: firm1 Date: Mon, 24 Mar 2014 14:52:44 +0100 Subject: [PATCH 0006/2943] add param to create_from_tree --- git/objects/commit.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/git/objects/commit.py b/git/objects/commit.py index cbfd5097b..f1c2a23d1 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -254,7 +254,7 @@ def _iter_from_process_or_stream(cls, repo, proc_or_stream): @classmethod - def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False): + def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, author=None, committer=None): """Commit the given tree, creating a commit object. :param repo: Repo object the commit should be part of @@ -299,8 +299,13 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False): cr = repo.config_reader() env = os.environ - committer = Actor.committer(cr) - author = Actor.author(cr) + if author is None and committer is None: + committer = Actor.committer(cr) + author = Actor.author(cr) + elif author is None: + author = Actor.author(cr) + elif committer is None: + committer = Actor.committer(cr) # PARSE THE DATES unix_time = int(time()) From 28fdf05b1d7827744b7b70eeb1cc66d3afd38c82 Mon Sep 17 00:00:00 2001 From: firm1 Date: Mon, 24 Mar 2014 14:54:23 +0100 Subject: [PATCH 0007/2943] correct log reference --- git/refs/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/refs/log.py b/git/refs/log.py index 9a719ec06..560ffd3e1 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -247,7 +247,7 @@ def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message): raise ValueError("Shas need to be given in binary format") #END handle sha type assure_directory_exists(filepath, is_file=True) - entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(config_reader), (int(time.time()), time.altzone), message)) + entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), config_reader, (int(time.time()), time.altzone), message)) lf = LockFile(filepath) lf._obtain_lock_or_raise() From 4a7e7a769087b1790a18d6645740b5b670f5086b Mon Sep 17 00:00:00 2001 From: firm1 Date: Mon, 24 Mar 2014 14:56:02 +0100 Subject: [PATCH 0008/2943] Update symbolic.py --- git/refs/symbolic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index ef21950fa..5374285e0 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -355,7 +355,7 @@ def log_append(self, oldbinsha, message, newbinsha=None): :param newbinsha: The sha the ref points to now. If None, our current commit sha will be used :return: added RefLogEntry instance""" - return RefLog.append_entry(self.repo.config_reader(), RefLog.path(self), oldbinsha, + return RefLog.append_entry(self.commit.committer, RefLog.path(self), oldbinsha, (newbinsha is None and self.commit.binsha) or newbinsha, message) From 3a1e0d7117b9e4ea4be3ef4895e8b2b4937ff98a Mon Sep 17 00:00:00 2001 From: firm1 Date: Wed, 9 Apr 2014 16:03:21 +0200 Subject: [PATCH 0009/2943] fix syntax error --- git/index/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/index/base.py b/git/index/base.py index 0c1b68d94..160d21bf3 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -873,7 +873,7 @@ def move(self, items, skip_errors=False, **kwargs): return out - def def commit(self, message, parent_commits=None, head=True, author=None, committer=None): + def commit(self, message, parent_commits=None, head=True, author=None, committer=None): """Commit the current default index file, creating a commit object. For more information on the arguments, see tree.commit. From f5d11b750ecc982541d1f936488248f0b42d75d3 Mon Sep 17 00:00:00 2001 From: Antoine Musso Date: Sun, 16 Nov 2014 20:15:50 +0100 Subject: [PATCH 0010/2943] pep8 linting (whitespaces) W191 indentation contains tabs E221 multiple spaces before operator E222 multiple spaces after operator E225 missing whitespace around operator E271 multiple spaces after keyword W292 no newline at end of file W293 blank line contains whitespace W391 blank line at end of file --- git/__init__.py | 5 +- git/cmd.py | 142 ++++++++--------- git/config.py | 126 ++++++++-------- git/db.py | 14 +- git/diff.py | 99 ++++++------ git/exc.py | 6 +- git/index/__init__.py | 2 +- git/index/base.py | 63 ++++---- git/index/fun.py | 44 +++--- git/index/typ.py | 14 +- git/index/util.py | 2 +- git/objects/__init__.py | 2 +- git/objects/base.py | 47 +++--- git/objects/blob.py | 2 +- git/objects/commit.py | 98 ++++++------ git/objects/fun.py | 44 +++--- git/objects/submodule/base.py | 213 +++++++++++++------------- git/objects/submodule/root.py | 74 ++++----- git/objects/submodule/util.py | 18 +-- git/objects/tag.py | 17 +-- git/objects/tree.py | 66 ++++---- git/objects/util.py | 86 +++++------ git/odict.py | 115 +++++++------- git/refs/head.py | 90 ++++++----- git/refs/log.py | 80 +++++----- git/refs/reference.py | 26 ++-- git/refs/remote.py | 10 +- git/refs/symbolic.py | 178 +++++++++++----------- git/refs/tag.py | 40 ++--- git/remote.py | 178 +++++++++++----------- git/repo/__init__.py | 2 +- git/repo/base.py | 142 ++++++++--------- git/repo/fun.py | 56 +++---- git/test/lib/asserts.py | 6 +- git/test/lib/helper.py | 64 ++++---- git/test/performance/lib.py | 20 +-- git/test/performance/test_commit.py | 22 +-- git/test/performance/test_odb.py | 14 +- git/test/performance/test_streams.py | 44 +++--- git/test/performance/test_utils.py | 40 ++--- git/test/test_actor.py | 4 +- git/test/test_base.py | 24 +-- git/test/test_blob.py | 7 +- git/test/test_commit.py | 86 +++++------ git/test/test_config.py | 30 ++-- git/test/test_db.py | 8 +- git/test/test_diff.py | 24 ++- git/test/test_fun.py | 80 +++++----- git/test/test_git.py | 6 +- git/test/test_index.py | 218 +++++++++++++-------------- git/test/test_reflog.py | 36 ++--- git/test/test_refs.py | 157 ++++++++++--------- git/test/test_remote.py | 164 ++++++++++---------- git/test/test_repo.py | 188 ++++++++++++----------- git/test/test_stats.py | 8 +- git/test/test_submodule.py | 201 ++++++++++++------------ git/test/test_tree.py | 49 +++--- git/test/test_util.py | 57 ++++--- git/util.py | 204 ++++++++++++------------- 59 files changed, 1917 insertions(+), 1945 deletions(-) diff --git a/git/__init__.py b/git/__init__.py index 9ea811123..6ccafcbba 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -15,13 +15,13 @@ def _init_externals(): """Initialize external projects by putting them into the path""" sys.path.append(os.path.join(os.path.dirname(__file__), 'ext', 'gitdb')) - + try: import gitdb except ImportError: raise ImportError("'gitdb' could not be found in your PYTHONPATH") #END verify import - + #} END initialization ################# @@ -51,4 +51,3 @@ def _init_externals(): __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 index a1780de7e..c655cdc80 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -30,13 +30,13 @@ def dashify(string): class Git(LazyMixin): """ 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. @@ -44,35 +44,35 @@ class Git(LazyMixin): """ __slots__ = ("_working_dir", "cat_file_all", "cat_file_header", "_version_info", "_git_options") - + # CONFIGURATION # The size in bytes read from stdout when copying git's output to another stream max_chunk_size = 1024*64 - + git_exec_name = "git" # default that should work on linux and windows git_exec_name_win = "git.cmd" # alternate command name, windows only - + # Enables debugging of GitPython's git commands GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False) - + # Provide the full path to the git executable. Otherwise it assumes git is in the path _git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE" GIT_PYTHON_GIT_EXECUTABLE = os.environ.get(_git_exec_env_var, git_exec_name) - - + + 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") - + __slots__ = ("proc", "args") + def __init__(self, proc, args ): self.proc = proc self.args = args - + def __del__(self): self.proc.stdout.close() self.proc.stderr.close() @@ -80,11 +80,11 @@ 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 @@ -98,13 +98,13 @@ def __del__(self): # 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: @@ -112,7 +112,7 @@ def wait(self): # END status handling return status # END auto interrupt - + class CatFileContentStream(object): """Object representing a sized read-only stream returning the contents of an object. @@ -120,20 +120,20 @@ class CatFileContentStream(object): 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: @@ -147,17 +147,17 @@ def read(self, size=-1): # 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: @@ -165,21 +165,21 @@ def readline(self, size=-1): 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 @@ -195,16 +195,16 @@ def readlines(self, size=-1): # 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: @@ -212,11 +212,11 @@ def __del__(self): # 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(). @@ -246,13 +246,13 @@ def _set_cache_(self, attr): else: super(Git, self)._set_cache_(attr) #END handle version info - + @property def working_dir(self): """:return: Git directory we are working on""" return self._working_dir - + @property def version_info(self): """ @@ -301,7 +301,7 @@ def execute(self, command, 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. @@ -309,25 +309,25 @@ def execute(self, command, 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 Note git is executed with LC_MESSAGES="C" to ensure consitent output regardless of system language. - + :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.""" @@ -338,8 +338,8 @@ def execute(self, command, if with_keep_cwd or self._working_dir is None: cwd = os.getcwd() else: - cwd=self._working_dir - + cwd = self._working_dir + # Start the process proc = Popen(command, env={"LC_MESSAGES": "C"}, @@ -347,12 +347,12 @@ def execute(self, command, stdin=istream, stderr=PIPE, stdout=PIPE, - close_fds=(os.name=='posix'),# unsupported on linux + 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 = '' @@ -426,7 +426,7 @@ def __unpack_args(cls, arg_list): if isinstance(arg_list, unicode): return [arg_list.encode('utf-8')] return [ str(arg_list) ] - + outlist = list() for arg in arg_list: if isinstance(arg_list, (list, tuple)): @@ -488,10 +488,10 @@ def _call_process(self, method, *args, **kwargs): # 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 - + def make_call(): call = [self.GIT_PYTHON_GIT_EXECUTABLE] @@ -504,7 +504,7 @@ def make_call(): call.extend(args) return call #END utility to recreate call after changes - + if sys.platform == 'win32': try: try: @@ -516,7 +516,7 @@ def make_call(): #END handle overridden variable type(self).GIT_PYTHON_GIT_EXECUTABLE = self.git_exec_name_win call = [self.GIT_PYTHON_GIT_EXECUTABLE] + list(args) - + try: return self.execute(make_call(), **_kwargs) finally: @@ -532,14 +532,14 @@ def make_call(): else: return self.execute(make_call(), **_kwargs) #END handle windows default installation - + 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() @@ -550,46 +550,46 @@ def _parse_object_header(self, header_line): 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) @@ -598,7 +598,7 @@ def get_object_data(self, 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) @@ -607,12 +607,12 @@ def stream_object_data(self, ref): 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 diff --git a/git/config.py b/git/config.py index 285ade6b7..5ad69c6ac 100644 --- a/git/config.py +++ b/git/config.py @@ -35,16 +35,16 @@ def __new__(metacls, name, bases, clsdict): 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""" @@ -54,7 +54,7 @@ def assure_data_present(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. @@ -66,64 +66,64 @@ def flush_changes(self, *args, **kwargs): # 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 re_comment = re.compile('^\s*[#;]') - + #} END configuration - + OPTCRE = re.compile( r'\s*(?P