Skip to content

Commit

Permalink
Add support for git config commit.cleanup
Browse files Browse the repository at this point in the history
See `--cleanup` in `man git commit`.

Fix mystor#108

Co-authored-by: ruro <[email protected]>
  • Loading branch information
nikitabobko and RuRo committed Jul 3, 2022
1 parent 2c376b5 commit dbe0ae4
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 16 deletions.
79 changes: 72 additions & 7 deletions gitrevise/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

from typing import Any, List, Optional, Sequence, Tuple, TYPE_CHECKING
from enum import Enum, auto
from subprocess import run, CalledProcessError
from pathlib import Path
import textwrap
Expand All @@ -11,6 +12,47 @@
from .odb import Repository, Commit, Tree, Oid, Reference


GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR = "------------------------ >8 ------------------------\n"


class EditorCleanupMode(Enum):
"""git config commit.cleanup representation"""
STRIP = auto()
WHITESPACE = auto()
VERBATIM = auto()
SCISSORS = auto()
DEFAULT = STRIP

@property
def comment(self) -> str:
return {
EditorCleanupMode.STRIP: (
"Please enter the commit message for your changes. Lines starting\n"
"with '#' will be ignored, and an empty message aborts the commit.\n"
),
EditorCleanupMode.SCISSORS: (
f"{GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR}"
"Do not modify or remove the line above.\n"
"Everything below it will be ignored.\n"
),
}.get(
self,
(
"Please enter the commit message for your changes. Lines starting\n"
"with '#' will be kept; you may remove them yourself if you want to.\n"
"An empty message aborts the commit.\n"
)
)

@classmethod
def from_repository(cls, repo: Repository) -> EditorCleanupMode:
cleanup_str = repo.config("commit.cleanup", default=b"default").decode()
value = EditorCleanupMode.__members__.get(cleanup_str.upper())
if value is None:
raise ValueError(f"Invalid cleanup mode {cleanup_str}")
return value


if TYPE_CHECKING:
from subprocess import CompletedProcess

Expand Down Expand Up @@ -93,6 +135,14 @@ def get_commentchar(repo: Repository, text: bytes) -> bytes:
return commentchar


def cut_after_scissors(lines: list[bytes], commentchar: bytes) -> list[bytes]:
try:
scissors = lines.index(commentchar + b" " + GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR.encode())
except ValueError:
scissors = None
return lines[:scissors]


def strip_comments(lines: list[bytes], commentchar: bytes, allow_preceding_whitespace: bool):
if allow_preceding_whitespace:
pat_is_comment_line = re.compile(rb"^\s*" + re.escape(commentchar))
Expand All @@ -108,10 +158,21 @@ def is_comment_line(line: bytes) -> bool:


def cleanup_editor_content(
data: bytes, commentchar: bytes, allow_preceding_whitespace: bool
data: bytes,
commentchar: bytes,
cleanup_mode: EditorCleanupMode,
allow_preceding_whitespace: bool = False,
) -> bytes:
if cleanup_mode == EditorCleanupMode.VERBATIM:
return data

lines_list = data.splitlines(keepends=True)
lines_list = strip_comments(lines_list, commentchar, allow_preceding_whitespace)

if cleanup_mode == EditorCleanupMode.SCISSORS:
lines_list = cut_after_scissors(lines_list, commentchar)

if cleanup_mode == EditorCleanupMode.STRIP:
lines_list = strip_comments(lines_list, commentchar, allow_preceding_whitespace)

# Remove trailing whitespace in each line
lines_list = [line.rstrip() for line in lines_list]
Expand Down Expand Up @@ -148,6 +209,7 @@ def run_specific_editor(
repo: Repository,
filename: str,
text: bytes,
cleanup_mode: EditorCleanupMode,
comments: Optional[str] = None,
allow_empty: bool = False,
allow_whitespace_before_comments: bool = False,
Expand All @@ -172,6 +234,7 @@ def run_specific_editor(
data = cleanup_editor_content(
data,
commentchar,
cleanup_mode,
allow_preceding_whitespace=allow_whitespace_before_comments,
)

Expand All @@ -193,6 +256,7 @@ def run_editor(
repo: Repository,
filename: str,
text: bytes,
cleanup_mode: EditorCleanupMode = EditorCleanupMode.DEFAULT,
comments: Optional[str] = None,
allow_empty: bool = False,
) -> bytes:
Expand All @@ -202,6 +266,7 @@ def run_editor(
repo=repo,
filename=filename,
text=text,
cleanup_mode=cleanup_mode,
comments=comments,
allow_empty=allow_empty,
)
Expand Down Expand Up @@ -232,6 +297,7 @@ def run_sequence_editor(
repo=repo,
filename=filename,
text=text,
cleanup_mode=EditorCleanupMode.DEFAULT,
comments=comments,
allow_empty=allow_empty,
allow_whitespace_before_comments=True,
Expand All @@ -242,10 +308,9 @@ def edit_commit_message(commit: Commit) -> Commit:
"""Launch an editor to edit the commit message of ``commit``, returning
a modified commit"""
repo = commit.repo
comments = (
"Please enter the commit message for your changes. Lines starting\n"
"with '#' will be ignored, and an empty message aborts the commit.\n"
)

cleanup_mode = EditorCleanupMode.from_repository(repo)
comments = cleanup_mode.comment

# If the target commit is not a merge commit, produce a diff --stat to
# include in the commit message comments.
Expand All @@ -254,7 +319,7 @@ 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)
message = run_editor(repo, "COMMIT_EDITMSG", commit.message, cleanup_mode, comments=comments)
return commit.update(message=message)


Expand Down
64 changes: 55 additions & 9 deletions tests/test_cleanup_editor_content.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from gitrevise.utils import cleanup_editor_content
from typing import Optional
from gitrevise.utils import cleanup_editor_content, EditorCleanupMode, GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR


def test_strip_comments() -> None:
Expand All @@ -7,7 +8,11 @@ def test_strip_comments() -> None:
b"foo\n"
b"# bar\n"
),
expected=b"foo\n",
expected_strip=b"foo\n",
expected_whitespace=(
b"foo\n"
b"# bar\n"
)
)


Expand All @@ -19,9 +24,13 @@ def test_leading_empty_lines() -> None:
b"foo\n"
b"# bar\n"
),
expected=(
expected_strip=(
b"foo\n"
),
expected_whitespace=(
b"foo\n"
b"# bar\n"
)
)


Expand All @@ -33,7 +42,11 @@ def test_trailing_empty_lines() -> None:
b"\n"
b"\n"
),
expected=b"foo\n"
expected_strip=b"foo\n",
expected_whitespace=(
b"foo\n"
b"# bar\n"
)
)


Expand All @@ -44,9 +57,14 @@ def test_trailing_whitespaces() -> None:
b"foo \n"
b"# bar \n"
),
expected=(
expected_strip=(
b"foo\n"
b"foo\n"
),
expected_whitespace=(
b"foo\n"
b"foo\n"
b"# bar\n"
)
)

Expand All @@ -59,14 +77,42 @@ def test_consecutive_emtpy_lines() -> None:
b""
b"bar\n"
),
expected=(
expected_strip=(
b"foo\n"
b""
b"bar\n"
)
)


def _do_test(data: bytes, expected: bytes):
actual = cleanup_editor_content(data, b"#", allow_preceding_whitespace=False)
assert actual == expected
def test_scissors() -> None:
original = ("foo\n"
f"# {GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR}"
"bar\n").encode()
_do_test(
original,
expected_strip=(
b"foo\n"
b"bar\n"
),
expected_whitespace=original,
expected_scissors=b"foo\n"
)


def _do_test(data: bytes, expected_strip: bytes, expected_whitespace: Optional[bytes] = None,
expected_scissors: Optional[bytes] = None):
if expected_whitespace is None:
expected_whitespace = expected_strip
if expected_scissors is None:
expected_scissors = expected_whitespace

actual_strip = cleanup_editor_content(data, b"#", EditorCleanupMode.STRIP)
actual_verbatim = cleanup_editor_content(data, b"#", EditorCleanupMode.VERBATIM)
actual_scissors = cleanup_editor_content(data, b"#", EditorCleanupMode.SCISSORS)
actual_whitespace = cleanup_editor_content(data, b"#", EditorCleanupMode.WHITESPACE)

assert actual_strip == expected_strip, "default"
assert actual_verbatim == data, "verbatim"
assert actual_scissors == expected_scissors, "scissors"
assert actual_whitespace == expected_whitespace, "whitespace"

0 comments on commit dbe0ae4

Please sign in to comment.