11from __future__ import annotations
22
3+ from enum import Enum , auto
34import os
45import re
56import sys
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+
1759class 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+
95145def 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
109159def 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
0 commit comments