diff options
-rw-r--r-- | doc/source/changes.rst | 3 | ||||
-rw-r--r-- | git/cmd.py | 2 | ||||
-rw-r--r-- | git/exc.py | 20 | ||||
-rw-r--r-- | git/index/base.py | 8 | ||||
-rw-r--r-- | git/index/fun.py | 47 | ||||
-rw-r--r-- | git/test/test_index.py | 21 |
6 files changed, 89 insertions, 12 deletions
diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 67384eb9..a64143c5 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -7,8 +7,9 @@ Changelog * push/pull/fetch operations will not block anymore * diff() can now properly detect renames, both in patch and raw format. Previously it only worked when create_patch was True. * repo.odb.update_cache() is now called automatically after fetch and pull operations. In case you did that in your own code, you might want to remove your line to prevent a double-update that causes unnecessary IO. -* A list of all fixed issues can be found here: https://github.com/gitpython-developers/GitPython/issues?q=milestone%3A%22v0.3.5+-+bugfixes%22+ * `Repo(path)` will not automatically search upstream anymore and find any git directory on its way up. If you need that behaviour, you can turn it back on using the new `search_parent_directories=True` flag when constructing a `Repo` object. +* IndexFile.commit() now runs the `pre-commit` and `post-commit` hooks. Verified to be working on posix systems only. +* A list of all fixed issues can be found here: https://github.com/gitpython-developers/GitPython/issues?q=milestone%3A%22v0.3.5+-+bugfixes%22+ 0.3.4 - Python 3 Support ======================== @@ -502,7 +502,7 @@ class Git(LazyMixin): # Prevent cmd prompt popups on windows by using a shell ... . # See https://github.com/gitpython-developers/GitPython/pull/126 shell=sys.platform == 'win32', - close_fds=(os.name == 'posix'), # unsupported on linux + close_fds=(os.name == 'posix'), # unsupported on windows **subprocess_kwargs ) if as_process: @@ -11,17 +11,14 @@ from git.compat import defenc class InvalidGitRepositoryError(Exception): - """ Thrown if the given repository appears to have an invalid format. """ class NoSuchPathError(OSError): - """ Thrown if a path could not be access by the system. """ class GitCommandError(Exception): - """ Thrown if execution of the git command fails with non-zero status code. """ def __init__(self, command, status, stderr=None, stdout=None): @@ -41,7 +38,6 @@ class GitCommandError(Exception): class CheckoutError(Exception): - """Thrown if a file could not be checked out from the index as it contained changes. @@ -71,6 +67,20 @@ class CacheError(Exception): class UnmergedEntriesError(CacheError): - """Thrown if an operation cannot proceed as there are still unmerged entries in the cache""" + + +class HookExecutionError(Exception): + """Thrown if a hook exits with a non-zero exit code. It provides access to the exit code and the string returned + via standard output""" + + def __init__(self, command, status, stdout, stderr): + self.command = command + self.status = status + self.stdout = stdout + self.stderr = stderr + + def __str__(self): + return ("'%s' hook returned with exit code %i\nstdout: '%s'\nstderr: '%s'" + % (self.command, self.status, self.stdout, self.stderr)) diff --git a/git/index/base.py b/git/index/base.py index db0c3cda..7002385c 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -63,7 +63,8 @@ from .fun import ( aggressive_tree_merge, write_tree_from_cache, stat_mode_to_index_mode, - S_IFGITLINK + S_IFGITLINK, + run_commit_hook ) from gitdb.base import IStream @@ -893,9 +894,12 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): :note: If you have manually altered the .entries member of this instance, don't forget to write() your changes to disk beforehand. :return: Commit object representing the new commit""" + run_commit_hook('pre-commit', self) tree = self.write_tree() - return Commit.create_from_tree(self.repo, tree, message, parent_commits, + rval = Commit.create_from_tree(self.repo, tree, message, parent_commits, head, author=author, committer=committer) + run_commit_hook('post-commit', self) + return rval @classmethod def _flush_stdin_and_wait(cls, proc, ignore_stdout=False): diff --git a/git/index/fun.py b/git/index/fun.py index f0dee961..38ad843b 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -13,9 +13,14 @@ from stat import ( S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule from io import BytesIO +import os +import subprocess from git.util import IndexFileSHA1Writer -from git.exc import UnmergedEntriesError +from git.exc import ( + UnmergedEntriesError, + HookExecutionError +) from git.objects.fun import ( tree_to_stream, traverse_tree_recursive, @@ -37,10 +42,46 @@ from .util import ( from gitdb.base import IStream from gitdb.typ import str_tree_type -from git.compat import defenc +from git.compat import ( + defenc, + force_text +) __all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key', - 'stat_mode_to_index_mode', 'S_IFGITLINK') + 'stat_mode_to_index_mode', 'S_IFGITLINK', 'run_commit_hook', 'hook_path') + + +def hook_path(name, git_dir): + """:return: path to the given named hook in the given git repository directory""" + return os.path.join(git_dir, 'hooks', name) + + +def run_commit_hook(name, index): + """Run the commit hook of the given name. Silently ignores hooks that do not exist. + :param name: name of hook, like 'pre-commit' + :param index: IndexFile instance + :raises HookExecutionError: """ + hp = hook_path(name, index.repo.git_dir) + if not os.access(hp, os.X_OK): + return + + env = os.environ.copy() + env['GIT_INDEX_FILE'] = index.path + env['GIT_EDITOR'] = ':' + cmd = subprocess.Popen(hp, + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=(os.name == 'posix')) + stdout, stderr = cmd.communicate() + cmd.stdout.close() + cmd.stderr.close() + + if cmd.returncode != 0: + stdout = force_text(stdout, defenc) + stderr = force_text(stderr, defenc) + raise HookExecutionError(hp, cmd.returncode, stdout, stderr) + # end handle return code def stat_mode_to_index_mode(mode): diff --git a/git/test/test_index.py b/git/test/test_index.py index 021a8cd9..2be776cd 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -12,6 +12,7 @@ from git.test.lib import ( with_rw_repo ) from git.util import Actor +from git.exc import HookExecutionError from git import ( IndexFile, BlobFilter, @@ -40,6 +41,7 @@ from git.index.typ import ( BaseIndexEntry, IndexEntry ) +from git.index.fun import hook_path class TestIndex(TestBase): @@ -665,6 +667,25 @@ class TestIndex(TestBase): assert fkey not in index.entries index.add(files, write=True) + if os.name != 'nt': + hp = hook_path('pre-commit', index.repo.git_dir) + with open(hp, "wt") as fp: + fp.write("#!/usr/bin/env sh\necho stdout; echo stderr 1>&2; exit 1") + # end + os.chmod(hp, 0o544) + try: + index.commit("This should fail") + except HookExecutionError as err: + assert err.status == 1 + assert err.command == hp + assert err.stdout == 'stdout\n' + assert err.stderr == 'stderr\n' + assert str(err) + else: + raise AssertionError("Should have cought a HookExecutionError") + # end exception handling + os.remove(hp) + # end hook testing nc = index.commit("2 files committed", head=False) for fkey in keys: |