From f1a82e45fc177cec8cffcfe3ff970560d272d0bf Mon Sep 17 00:00:00 2001 From: Kostis Anagnostopoulos Date: Fri, 28 Oct 2016 12:21:43 +0200 Subject: [PATCH 1/4] fix(leaks): repo context-man to cleanup global mman on repo-delete Improve API for problems like #553. --- git/repo/base.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/git/repo/base.py b/git/repo/base.py index 0f85e3d96..a9f90a175 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -33,6 +33,8 @@ import os.path as osp from .fun import rev_parse, is_git_dir, find_submodule_git_dir, touch +import gc +import gitdb log = logging.getLogger(__name__) @@ -177,9 +179,21 @@ def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=Fals args.append(self.git) self.odb = odbt(*args) + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.close() + def __del__(self): + self.close() + + def close(self): if self.git: self.git.clear_cache() + gc.collect() + gitdb.util.mman.collect() + gc.collect() def __eq__(self, rhs): if isinstance(rhs, Repo): From bdb4c7cb5b3dec9e4020aac864958dd16623de77 Mon Sep 17 00:00:00 2001 From: Kostis Anagnostopoulos Date: Fri, 28 Oct 2016 12:22:40 +0200 Subject: [PATCH 2/4] fix(leaks, TCs): attempt to cleanup mman before deleting tmp-dirs --- git/test/lib/helper.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index 743f720c8..c3960c248 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -7,18 +7,22 @@ import contextlib from functools import wraps -import sys +import gc import io import logging import os +import sys import tempfile import textwrap import time from git.compat import string_types, is_win from git.util import rmtree, cwd +import gitdb import os.path as osp + + if sys.version_info[0:2] == (2, 6): import unittest2 as unittest else: @@ -96,7 +100,6 @@ def wrapper(self): # a windows-only issue. In fact things should be deleted, as well as # memory maps closed, once objects go out of scope. For some reason # though this is not the case here unless we collect explicitly. - import gc gc.collect() if not keep: rmtree(path) @@ -144,9 +147,10 @@ def repo_creator(self): os.chdir(prev_cwd) rw_repo.git.clear_cache() rw_repo = None - import gc - gc.collect() if repo_dir is not None: + gc.collect() + gitdb.util.mman.collect() + gc.collect() rmtree(repo_dir) # END rm test repo if possible # END cleanup @@ -303,7 +307,8 @@ def remote_repo_creator(self): rw_daemon_repo.git.clear_cache() del rw_repo del rw_daemon_repo - import gc + gc.collect() + gitdb.util.mman.collect() gc.collect() if rw_repo_dir: rmtree(rw_repo_dir) @@ -357,7 +362,6 @@ def setUpClass(cls): each test type has its own repository """ from git import Repo - import gc gc.collect() cls.rorepo = Repo(GIT_REPO) From 66306f1582754ca4527b76f09924820dc9c85875 Mon Sep 17 00:00:00 2001 From: Kostis Anagnostopoulos Date: Thu, 8 Dec 2016 14:35:15 +0100 Subject: [PATCH 3/4] feat(util): export rmtree() to facilitate del of tmp-repos on Windows Usecase described in #553 --- git/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git/__init__.py b/git/__init__.py index 0514d545b..8c31e3094 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -49,7 +49,8 @@ def _init_externals(): LockFile, BlockingLockFile, Stats, - Actor + Actor, + rmtree, ) #} END imports From f858c449a993124939e9082dcea796c5a13d0a74 Mon Sep 17 00:00:00 2001 From: Kostis Anagnostopoulos Date: Thu, 8 Dec 2016 14:19:37 +0100 Subject: [PATCH 4/4] style(cmd): pythonize cmd-args filtering for PY26, improve docstring Apply codereview comments of #541. --- git/cmd.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 245a7f609..112075057 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -822,27 +822,30 @@ def _call_process(self, method, *args, **kwargs): is realized as non-existent :param kwargs: - is a dict of keyword arguments. - This function accepts the same optional keyword arguments - as execute(). - - ``Examples``:: + It contains key-values for the following: + - the :meth:`execute()` kwds, as listed in :var:`execute_kwargs`; + - "command options" to be converted by :meth:`transform_kwargs()`; + - the `'insert_kwargs_after'` key which its value must match one of ``*args``, + and any cmd-options will be appended after the matched arg. + + Examples:: + git.rev_list('master', max_count=10, header=True) + + turns into:: + + git rev-list max-count 10 --header master :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 + exec_kwargs = dict((k, v) for k, v in kwargs.items() if k in execute_kwargs) + opts_kwargs = dict((k, v) for k, v in kwargs.items() if k not in execute_kwargs) - insert_after_this_arg = kwargs.pop('insert_kwargs_after', None) + insert_after_this_arg = opts_kwargs.pop('insert_kwargs_after', None) # Prepare the argument list - opt_args = self.transform_kwargs(**kwargs) + opt_args = self.transform_kwargs(**opts_kwargs) ext_args = self.__unpack_args([a for a in args if a is not None]) if insert_after_this_arg is None: @@ -851,11 +854,11 @@ def _call_process(self, method, *args, **kwargs): try: index = ext_args.index(insert_after_this_arg) except ValueError: - raise ValueError("Couldn't find argument '%s' in args %s to insert kwargs after" + raise ValueError("Couldn't find argument '%s' in args %s to insert cmd options after" % (insert_after_this_arg, str(ext_args))) # end handle error args = ext_args[:index + 1] + opt_args + ext_args[index + 1:] - # end handle kwargs + # end handle opts_kwargs call = [self.GIT_PYTHON_GIT_EXECUTABLE] @@ -870,7 +873,7 @@ def _call_process(self, method, *args, **kwargs): call.append(dashify(method)) call.extend(args) - return self.execute(call, **_kwargs) + return self.execute(call, **exec_kwargs) def _parse_object_header(self, header_line): """