mirror of https://git.sr.ht/~sircmpwn/hub.sr.ht
84 lines
2.7 KiB
Python
84 lines
2.7 KiB
Python
|
import re
|
||
|
from typing import List, Tuple
|
||
|
|
||
|
_git_generated_prefixes = (
|
||
|
"Signed-off-by: ",
|
||
|
"(cherry picked from commit ",
|
||
|
)
|
||
|
|
||
|
def commit_trailers(message: str) -> List[Tuple[str, str]]:
|
||
|
"""
|
||
|
Extract the trailers from a commit message. Return a list of pairs of
|
||
|
(name, value).
|
||
|
|
||
|
This borrows a large amount of logic from git core (trailer.c).
|
||
|
"""
|
||
|
lines = message.strip().splitlines()
|
||
|
|
||
|
# The first paragraph is the title and cannot be trailers
|
||
|
while lines and lines[0] != '':
|
||
|
del lines[0]
|
||
|
|
||
|
recognized_prefix = False
|
||
|
only_spaces = True
|
||
|
trailer_lines = non_trailer_lines = 0
|
||
|
possible_continuation_lines = 0
|
||
|
|
||
|
# Get the start of the trailers by looking starting from the end for a
|
||
|
# blank line before a set of non-blank lines that (i) are all trailers, or
|
||
|
# (ii) contains at least one Git-generated trailer and consists of at least
|
||
|
# 25% trailers.
|
||
|
i = len(lines) - 1
|
||
|
while i >= 0:
|
||
|
line = lines[i]
|
||
|
|
||
|
if not line.strip():
|
||
|
# blank line
|
||
|
if only_spaces:
|
||
|
i -= 1
|
||
|
continue
|
||
|
if recognized_prefix and trailer_lines * 3 >= non_trailer_lines:
|
||
|
i += 1
|
||
|
break
|
||
|
if trailer_lines > 0 and non_trailer_lines == 0:
|
||
|
i += 1
|
||
|
break
|
||
|
return []
|
||
|
|
||
|
only_spaces = False
|
||
|
|
||
|
if any(line.startswith(p) for p in _git_generated_prefixes):
|
||
|
trailer_lines += 1
|
||
|
possible_continuation_lines = 0
|
||
|
recognized_prefix = True
|
||
|
elif re.search(r"^[A-Za-z\d][A-Za-z\d-]*\s*:", line):
|
||
|
trailer_lines += 1
|
||
|
possible_continuation_lines = 0
|
||
|
elif line[0] in (" ", "\t"):
|
||
|
possible_continuation_lines += 1
|
||
|
else:
|
||
|
non_trailer_lines += 1 + possible_continuation_lines
|
||
|
possible_continuation_lines = 0
|
||
|
i -= 1
|
||
|
|
||
|
# Iterate over all remaining lines and collect trailer names and values.
|
||
|
# If a line does not match a trailer and starts with a space or tab, its
|
||
|
# contents are appended to the current trailer value.
|
||
|
trailers = []
|
||
|
name = value = None
|
||
|
|
||
|
for line in lines[i:]:
|
||
|
match = re.match(r"^([A-Za-z\d][A-Za-z\d-]*)\s*:\s*(.*)$", line)
|
||
|
if match:
|
||
|
if name is not None and value is not None:
|
||
|
trailers.append((name, value))
|
||
|
name = match[1]
|
||
|
value = match[2]
|
||
|
elif name is not None and value is not None and line[0] in (" ", "\t"):
|
||
|
# continuation line
|
||
|
value += "\n" + line
|
||
|
if name is not None and value is not None:
|
||
|
trailers.append((name, value))
|
||
|
|
||
|
return trailers
|