Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add opt-in support for the commit-msg hook #82

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/man.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,13 @@ Configuration is managed by :manpage:`git-config(1)`.
is specified. Overridden by :option:`--no-autosquash`. Defaults to false. If
not set, the value of ``rebase.autoSquash`` is used instead.

.. gitconfig:: revise.run-hooks.commit-msg

If set to true the **commit-msg** hook will be run after exiting the
editor. Defaults to false, because (unlike with :manpage:`git-rebase(1)`)
the worktree state might not reflect the commit state. If the hook takes
the worktree state into account, it might behave differently.


CONFLICT RESOLUTION
===================
Expand Down
10 changes: 9 additions & 1 deletion git-revise.1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "GIT-REVISE" "1" "Jun 07, 2020" "0.6.0" "git-revise"
.TH "GIT-REVISE" "1" "May 04, 2021" "0.6.0" "git-revise"
.SH NAME
git-revise \- Efficiently update, split, and rearrange git commits
.
Expand Down Expand Up @@ -147,6 +147,14 @@ If set to true, imply \fI\%\-\-autosquash\fP whenever \fI\%\-\-interactive\fP
is specified. Overridden by \fI\%\-\-no\-autosquash\fP\&. Defaults to false. If
not set, the value of \fBrebase.autoSquash\fP is used instead.
.UNINDENT
.INDENT 0.0
.TP
.B revise.run\-hooks.commit\-msg
If set to true the \fBcommit\-msg\fP hook will be run after exiting the
editor. Defaults to false, because (unlike with \fBgit\-rebase(1)\fP)
the worktree state might not reflect the commit state. If the hook takes
the worktree state into account, it might behave differently.
.UNINDENT
.SH CONFLICT RESOLUTION
.sp
When a conflict is encountered, \fBgit revise\fP will attempt to resolve
Expand Down
9 changes: 8 additions & 1 deletion gitrevise/tui.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .odb import Repository, Commit, Reference
from .utils import (
EditorError,
HookError,
commit_range,
edit_commit_message,
update_head,
Expand Down Expand Up @@ -217,14 +218,20 @@ def main(argv: Optional[List[str]] = None) -> None:
with Repository() as repo:
inner_main(args, repo)
except CalledProcessError as err:
print(f"subprocess exited with non-zero status: {err.returncode}")
if err.returncode != 0:
print(f"subprocess exited with non-zero status: {err.returncode}")
else:
print(f"subprocess error: {err}")
sys.exit(1)
except EditorError as err:
print(f"editor error: {err}")
sys.exit(1)
except MergeConflict as err:
print(f"merge conflict: {err}")
sys.exit(1)
except HookError as err:
print(f"{err} hook declined")
sys.exit(1)
except ValueError as err:
print(f"invalid value: {err}")
sys.exit(1)
58 changes: 54 additions & 4 deletions gitrevise/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional, Tuple
from typing import Callable, List, Optional, Tuple
from subprocess import run, CalledProcessError
from pathlib import Path
import textwrap
Expand All @@ -14,6 +14,10 @@ class EditorError(Exception):
pass


class HookError(Exception):
pass


def commit_range(base: Commit, tip: Commit) -> List[Commit]:
"""Oldest-first iterator over the given commit range,
not including the commit ``base``"""
Expand Down Expand Up @@ -57,7 +61,9 @@ def local_commits(repo: Repository, tip: Commit) -> Tuple[Commit, List[Commit]]:
return base, commits


def edit_file_with_editor(editor: str, path: Path) -> bytes:
def edit_file_with_editor(
editor: str, path: Path, post_fn: Optional[Callable[[Path], None]] = None
) -> bytes:
try:
if os.name == "nt":
# The popular "Git for Windows" distribution uses a bundled msys
Expand All @@ -71,6 +77,10 @@ def edit_file_with_editor(editor: str, path: Path) -> bytes:
run(cmd, check=True, cwd=path.parent)
except CalledProcessError as err:
raise EditorError(f"Editor exited with status {err}") from err

if post_fn:
post_fn(path)

return path.read_bytes()


Expand Down Expand Up @@ -127,6 +137,7 @@ def run_specific_editor(
comments: Optional[str] = None,
allow_empty: bool = False,
allow_whitespace_before_comments: bool = False,
post_fn: Optional[Callable[[Path], None]] = None,
) -> bytes:
"""Run the editor configured for git to edit the given text"""
path = repo.get_tempdir() / filename
Expand All @@ -144,7 +155,7 @@ def run_specific_editor(
handle.write(b"\n")

# Invoke the editor
data = edit_file_with_editor(editor, path)
data = edit_file_with_editor(editor, path, post_fn)
if comments:
data = strip_comments(
data,
Expand Down Expand Up @@ -172,6 +183,7 @@ def run_editor(
text: bytes,
comments: Optional[str] = None,
allow_empty: bool = False,
post_fn: Optional[Callable[[Path], None]] = None,
) -> bytes:
"""Run the editor configured for git to edit the given text"""
return run_specific_editor(
Expand All @@ -181,6 +193,7 @@ def run_editor(
text=text,
comments=comments,
allow_empty=allow_empty,
post_fn=post_fn,
)


Expand Down Expand Up @@ -215,6 +228,31 @@ def run_sequence_editor(
)


def create_commit_msg_caller(repo: Repository) -> Optional[Callable[[Path], None]]:
"""Return a function which calls the "commit-msg" hook if it is
present"""

hooks_path = repo.config("core.hooksPath", b"")

if not hooks_path:
commit_msg_hook = os.path.join(
repo.git("rev-parse", "--git-common-dir"), b"hooks/commit-msg"
)
else:
commit_msg_hook = os.path.join(hooks_path, b"commit-msg")

def run_commit_msg(filepath: Path) -> None:
# If the hook script is present but not executable git would warn
# (unless `git config advice.ignoredHook false` is set), but git-revise
# silently ignores that file.
if os.access(commit_msg_hook, os.X_OK):
# stderr/stdout of the hook are passed through
if run([commit_msg_hook, filepath]).returncode != 0:
raise HookError("commit-msg")

return run_commit_msg


def edit_commit_message(commit: Commit) -> Commit:
"""Launch an editor to edit the commit message of ``commit``, returning
a modified commit"""
Expand All @@ -231,7 +269,19 @@ def edit_commit_message(commit: Commit) -> Commit:
tree_b = commit.tree().persist().hex()
comments += "\n" + repo.git("diff-tree", "--stat", tree_a, tree_b).decode()

message = run_editor(repo, "COMMIT_EDITMSG", commit.message, comments=comments)
if repo.bool_config("revise.run-hooks.commit-msg", default=False):
hook = create_commit_msg_caller(commit.repo)
else:
hook = None

message = run_editor(
repo,
"COMMIT_EDITMSG",
commit.message,
comments=comments,
post_fn=hook,
)

return commit.update(message=message)


Expand Down