X Tutup
Skip to content

Commit 7c5fbc6

Browse files
Add trailer support for commit creation
Add a `trailers` parameter to `Commit.create_from_tree()` and `IndexFile.commit()` that allows appending trailer key-value pairs (e.g. Signed-off-by, Issue) to the commit message at creation time. Trailers can be passed as either a dict or a list of (key, value) tuples, the latter being useful when duplicate keys are needed. The implementation uses `git interpret-trailers` for proper formatting, consistent with the existing trailer parsing in `Commit.trailers_list`. Closes #1998
1 parent d0318a6 commit 7c5fbc6

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

git/index/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,7 @@ def commit(
11331133
author_date: Union[datetime.datetime, str, None] = None,
11341134
commit_date: Union[datetime.datetime, str, None] = None,
11351135
skip_hooks: bool = False,
1136+
trailers: Union[None, "Dict[str, str]", "List[Tuple[str, str]]"] = None,
11361137
) -> Commit:
11371138
"""Commit the current default index file, creating a
11381139
:class:`~git.objects.commit.Commit` object.
@@ -1169,6 +1170,7 @@ def commit(
11691170
committer=committer,
11701171
author_date=author_date,
11711172
commit_date=commit_date,
1173+
trailers=trailers,
11721174
)
11731175
if not skip_hooks:
11741176
run_commit_hook("post-commit", self)

git/objects/commit.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ def create_from_tree(
570570
committer: Union[None, Actor] = None,
571571
author_date: Union[None, str, datetime.datetime] = None,
572572
commit_date: Union[None, str, datetime.datetime] = None,
573+
trailers: Union[None, Dict[str, str], List[Tuple[str, str]]] = None,
573574
) -> "Commit":
574575
"""Commit the given tree, creating a :class:`Commit` object.
575576
@@ -609,6 +610,14 @@ def create_from_tree(
609610
:param commit_date:
610611
The timestamp for the committer field.
611612
613+
:param trailers:
614+
Optional trailer key-value pairs to append to the commit message.
615+
Can be a dictionary mapping trailer keys to values, or a list of
616+
``(key, value)`` tuples (useful when the same key appears multiple
617+
times, e.g. multiple ``Signed-off-by`` trailers). Trailers are
618+
appended using ``git interpret-trailers``.
619+
See :manpage:`git-interpret-trailers(1)`.
620+
612621
:return:
613622
:class:`Commit` object representing the new commit.
614623
@@ -678,6 +687,27 @@ def create_from_tree(
678687
tree = repo.tree(tree)
679688
# END tree conversion
680689

690+
# APPLY TRAILERS
691+
if trailers:
692+
trailer_args: List[str] = []
693+
if isinstance(trailers, dict):
694+
for key, val in trailers.items():
695+
trailer_args.append("--trailer")
696+
trailer_args.append(f"{key}: {val}")
697+
else:
698+
for key, val in trailers:
699+
trailer_args.append("--trailer")
700+
trailer_args.append(f"{key}: {val}")
701+
702+
cmd = ["git", "interpret-trailers"] + trailer_args
703+
proc: Git.AutoInterrupt = repo.git.execute( # type: ignore[call-overload]
704+
cmd,
705+
as_process=True,
706+
istream=PIPE,
707+
)
708+
message = proc.communicate(str(message).encode())[0].decode("utf8")
709+
# END apply trailers
710+
681711
# CREATE NEW COMMIT
682712
new_commit = cls(
683713
repo,

test/test_commit.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,3 +566,77 @@ def test_commit_co_authors(self):
566566
Actor("test_user_2", "another_user-email@github.com"),
567567
Actor("test_user_3", "test_user_3@github.com"),
568568
]
569+
570+
@with_rw_directory
571+
def test_create_from_tree_with_trailers_dict(self, rw_dir):
572+
"""Test that create_from_tree supports adding trailers via a dict."""
573+
rw_repo = Repo.init(osp.join(rw_dir, "test_trailers_dict"))
574+
path = osp.join(str(rw_repo.working_tree_dir), "hello.txt")
575+
touch(path)
576+
rw_repo.index.add([path])
577+
tree = rw_repo.index.write_tree()
578+
579+
trailers = {"Issue": "123", "Signed-off-by": "Test User <test@test.com>"}
580+
commit = Commit.create_from_tree(
581+
rw_repo,
582+
tree,
583+
"Test commit with trailers",
584+
head=True,
585+
trailers=trailers,
586+
)
587+
588+
assert "Issue: 123" in commit.message
589+
assert "Signed-off-by: Test User <test@test.com>" in commit.message
590+
assert commit.trailers_dict == {
591+
"Issue": ["123"],
592+
"Signed-off-by": ["Test User <test@test.com>"],
593+
}
594+
595+
@with_rw_directory
596+
def test_create_from_tree_with_trailers_list(self, rw_dir):
597+
"""Test that create_from_tree supports adding trailers via a list of tuples."""
598+
rw_repo = Repo.init(osp.join(rw_dir, "test_trailers_list"))
599+
path = osp.join(str(rw_repo.working_tree_dir), "hello.txt")
600+
touch(path)
601+
rw_repo.index.add([path])
602+
tree = rw_repo.index.write_tree()
603+
604+
trailers = [
605+
("Signed-off-by", "Alice <alice@example.com>"),
606+
("Signed-off-by", "Bob <bob@example.com>"),
607+
("Issue", "456"),
608+
]
609+
commit = Commit.create_from_tree(
610+
rw_repo,
611+
tree,
612+
"Test commit with multiple trailers",
613+
head=True,
614+
trailers=trailers,
615+
)
616+
617+
assert "Signed-off-by: Alice <alice@example.com>" in commit.message
618+
assert "Signed-off-by: Bob <bob@example.com>" in commit.message
619+
assert "Issue: 456" in commit.message
620+
assert commit.trailers_dict == {
621+
"Signed-off-by": ["Alice <alice@example.com>", "Bob <bob@example.com>"],
622+
"Issue": ["456"],
623+
}
624+
625+
@with_rw_directory
626+
def test_index_commit_with_trailers(self, rw_dir):
627+
"""Test that IndexFile.commit() supports adding trailers."""
628+
rw_repo = Repo.init(osp.join(rw_dir, "test_index_trailers"))
629+
path = osp.join(str(rw_repo.working_tree_dir), "hello.txt")
630+
touch(path)
631+
rw_repo.index.add([path])
632+
633+
trailers = {"Reviewed-by": "Reviewer <reviewer@example.com>"}
634+
commit = rw_repo.index.commit(
635+
"Test index commit with trailers",
636+
trailers=trailers,
637+
)
638+
639+
assert "Reviewed-by: Reviewer <reviewer@example.com>" in commit.message
640+
assert commit.trailers_dict == {
641+
"Reviewed-by": ["Reviewer <reviewer@example.com>"],
642+
}

0 commit comments

Comments
 (0)
X Tutup