Skip to content

Commit f0dfa38

Browse files
nikitabobkoruro
authored andcommitted
Add support for git config commit.cleanup
See `--cleanup` in `man git commit`. Fix #108 Co-authored-by: ruro <[email protected]>
1 parent 595b770 commit f0dfa38

File tree

2 files changed

+127
-16
lines changed

2 files changed

+127
-16
lines changed

gitrevise/utils.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from enum import Enum, auto
34
import os
45
import re
56
import sys
@@ -14,6 +15,47 @@
1415
from subprocess import CompletedProcess
1516

1617

18+
GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR = "------------------------ >8 ------------------------\n"
19+
20+
21+
class EditorCleanupMode(Enum):
22+
"""git config commit.cleanup representation"""
23+
STRIP = auto()
24+
WHITESPACE = auto()
25+
VERBATIM = auto()
26+
SCISSORS = auto()
27+
DEFAULT = STRIP
28+
29+
@property
30+
def comment(self) -> str:
31+
return {
32+
EditorCleanupMode.STRIP: (
33+
"Please enter the commit message for your changes. Lines starting\n"
34+
"with '#' will be ignored, and an empty message aborts the commit.\n"
35+
),
36+
EditorCleanupMode.SCISSORS: (
37+
f"{GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR}"
38+
"Do not modify or remove the line above.\n"
39+
"Everything below it will be ignored.\n"
40+
),
41+
}.get(
42+
self,
43+
(
44+
"Please enter the commit message for your changes. Lines starting\n"
45+
"with '#' will be kept; you may remove them yourself if you want to.\n"
46+
"An empty message aborts the commit.\n"
47+
)
48+
)
49+
50+
@classmethod
51+
def from_repository(cls, repo: Repository) -> EditorCleanupMode:
52+
cleanup_str = repo.config("commit.cleanup", default=b"default").decode()
53+
value = cls.__members__.get(cleanup_str.upper())
54+
if value is None:
55+
raise ValueError(f"Invalid cleanup mode {cleanup_str}")
56+
return value
57+
58+
1759
class EditorError(Exception):
1860
pass
1961

@@ -92,6 +134,14 @@ def get_commentchar(repo: Repository, text: bytes) -> bytes:
92134
return commentchar
93135

94136

137+
def cut_after_scissors(lines: list[bytes], commentchar: bytes) -> list[bytes]:
138+
try:
139+
scissors = lines.index(commentchar + b" " + GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR.encode())
140+
except ValueError:
141+
scissors = None
142+
return lines[:scissors]
143+
144+
95145
def strip_comments(lines: list[bytes], commentchar: bytes, allow_preceding_whitespace: bool):
96146
if allow_preceding_whitespace:
97147
pat_is_comment_line = re.compile(rb"^\s*" + re.escape(commentchar))
@@ -107,10 +157,21 @@ def is_comment_line(line: bytes) -> bool:
107157

108158

109159
def cleanup_editor_content(
110-
data: bytes, commentchar: bytes, allow_preceding_whitespace: bool
160+
data: bytes,
161+
commentchar: bytes,
162+
cleanup_mode: EditorCleanupMode,
163+
allow_preceding_whitespace: bool = False,
111164
) -> bytes:
165+
if cleanup_mode == EditorCleanupMode.VERBATIM:
166+
return data
167+
112168
lines_list = data.splitlines(keepends=True)
113-
lines_list = strip_comments(lines_list, commentchar, allow_preceding_whitespace)
169+
170+
if cleanup_mode == EditorCleanupMode.SCISSORS:
171+
lines_list = cut_after_scissors(lines_list, commentchar)
172+
173+
if cleanup_mode == EditorCleanupMode.STRIP:
174+
lines_list = strip_comments(lines_list, commentchar, allow_preceding_whitespace)
114175

115176
# Remove trailing whitespace in each line
116177
lines_list = [line.rstrip() for line in lines_list]
@@ -147,6 +208,7 @@ def run_specific_editor(
147208
repo: Repository,
148209
filename: str,
149210
text: bytes,
211+
cleanup_mode: EditorCleanupMode,
150212
comments: Optional[str] = None,
151213
allow_empty: bool = False,
152214
allow_whitespace_before_comments: bool = False,
@@ -171,6 +233,7 @@ def run_specific_editor(
171233
data = cleanup_editor_content(
172234
data,
173235
commentchar,
236+
cleanup_mode,
174237
allow_preceding_whitespace=allow_whitespace_before_comments,
175238
)
176239

@@ -192,6 +255,7 @@ def run_editor(
192255
repo: Repository,
193256
filename: str,
194257
text: bytes,
258+
cleanup_mode: EditorCleanupMode = EditorCleanupMode.DEFAULT,
195259
comments: Optional[str] = None,
196260
allow_empty: bool = False,
197261
) -> bytes:
@@ -201,6 +265,7 @@ def run_editor(
201265
repo=repo,
202266
filename=filename,
203267
text=text,
268+
cleanup_mode=cleanup_mode,
204269
comments=comments,
205270
allow_empty=allow_empty,
206271
)
@@ -231,6 +296,7 @@ def run_sequence_editor(
231296
repo=repo,
232297
filename=filename,
233298
text=text,
299+
cleanup_mode=EditorCleanupMode.DEFAULT,
234300
comments=comments,
235301
allow_empty=allow_empty,
236302
allow_whitespace_before_comments=True,
@@ -241,10 +307,9 @@ def edit_commit_message(commit: Commit) -> Commit:
241307
"""Launch an editor to edit the commit message of ``commit``, returning
242308
a modified commit"""
243309
repo = commit.repo
244-
comments = (
245-
"Please enter the commit message for your changes. Lines starting\n"
246-
"with '#' will be ignored, and an empty message aborts the commit.\n"
247-
)
310+
311+
cleanup_mode = EditorCleanupMode.from_repository(repo)
312+
comments = cleanup_mode.comment
248313

249314
# If the target commit is not a merge commit, produce a diff --stat to
250315
# include in the commit message comments.
@@ -253,7 +318,7 @@ def edit_commit_message(commit: Commit) -> Commit:
253318
tree_b = commit.tree().persist().hex()
254319
comments += "\n" + repo.git("diff-tree", "--stat", tree_a, tree_b).decode()
255320

256-
message = run_editor(repo, "COMMIT_EDITMSG", commit.message, comments=comments)
321+
message = run_editor(repo, "COMMIT_EDITMSG", commit.message, cleanup_mode, comments=comments)
257322
return commit.update(message=message)
258323

259324

tests/test_cleanup_editor_content.py

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from gitrevise.utils import cleanup_editor_content
1+
from typing import Optional
2+
from gitrevise.utils import cleanup_editor_content, EditorCleanupMode, GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR
23

34

45
def test_strip_comments() -> None:
@@ -7,7 +8,11 @@ def test_strip_comments() -> None:
78
b"foo\n"
89
b"# bar\n"
910
),
10-
expected=b"foo\n",
11+
expected_strip=b"foo\n",
12+
expected_whitespace=(
13+
b"foo\n"
14+
b"# bar\n"
15+
)
1116
)
1217

1318

@@ -19,9 +24,13 @@ def test_leading_empty_lines() -> None:
1924
b"foo\n"
2025
b"# bar\n"
2126
),
22-
expected=(
27+
expected_strip=(
2328
b"foo\n"
2429
),
30+
expected_whitespace=(
31+
b"foo\n"
32+
b"# bar\n"
33+
)
2534
)
2635

2736

@@ -33,7 +42,11 @@ def test_trailing_empty_lines() -> None:
3342
b"\n"
3443
b"\n"
3544
),
36-
expected=b"foo\n"
45+
expected_strip=b"foo\n",
46+
expected_whitespace=(
47+
b"foo\n"
48+
b"# bar\n"
49+
)
3750
)
3851

3952

@@ -44,9 +57,14 @@ def test_trailing_whitespaces() -> None:
4457
b"foo \n"
4558
b"# bar \n"
4659
),
47-
expected=(
60+
expected_strip=(
61+
b"foo\n"
62+
b"foo\n"
63+
),
64+
expected_whitespace=(
4865
b"foo\n"
4966
b"foo\n"
67+
b"# bar\n"
5068
)
5169
)
5270

@@ -59,14 +77,42 @@ def test_consecutive_emtpy_lines() -> None:
5977
b""
6078
b"bar\n"
6179
),
62-
expected=(
80+
expected_strip=(
6381
b"foo\n"
6482
b""
6583
b"bar\n"
6684
)
6785
)
6886

6987

70-
def _do_test(data: bytes, expected: bytes):
71-
actual = cleanup_editor_content(data, b"#", allow_preceding_whitespace=False)
72-
assert actual == expected
88+
def test_scissors() -> None:
89+
original = ("foo\n"
90+
f"# {GIT_SCISSOR_LINE_WITHOUT_COMMENT_CHAR}"
91+
"bar\n").encode()
92+
_do_test(
93+
original,
94+
expected_strip=(
95+
b"foo\n"
96+
b"bar\n"
97+
),
98+
expected_whitespace=original,
99+
expected_scissors=b"foo\n"
100+
)
101+
102+
103+
def _do_test(data: bytes, expected_strip: bytes, expected_whitespace: Optional[bytes] = None,
104+
expected_scissors: Optional[bytes] = None):
105+
if expected_whitespace is None:
106+
expected_whitespace = expected_strip
107+
if expected_scissors is None:
108+
expected_scissors = expected_whitespace
109+
110+
actual_strip = cleanup_editor_content(data, b"#", EditorCleanupMode.STRIP)
111+
actual_verbatim = cleanup_editor_content(data, b"#", EditorCleanupMode.VERBATIM)
112+
actual_scissors = cleanup_editor_content(data, b"#", EditorCleanupMode.SCISSORS)
113+
actual_whitespace = cleanup_editor_content(data, b"#", EditorCleanupMode.WHITESPACE)
114+
115+
assert actual_strip == expected_strip, "default"
116+
assert actual_verbatim == data, "verbatim"
117+
assert actual_scissors == expected_scissors, "scissors"
118+
assert actual_whitespace == expected_whitespace, "whitespace"

0 commit comments

Comments
 (0)