diff --git a/bin/git-cp b/bin/git-cp
index cabb564b..25732243 100755
--- a/bin/git-cp
+++ b/bin/git-cp
@@ -4,17 +4,32 @@ set -euo pipefail
PROGRAM=$0
CURRENT_FILENAME=""
DESTINATION_FILENAME=""
+COMMIT_TEMPLATE="{}"
+
+function commit_message()
+{
+ # Replace every {} in the template with the default commit message
+ printf '%s\n' "${COMMIT_TEMPLATE//\{\}/"$1"}"
+}
function usage()
{
- echo 1>&2 "USAGE: ${PROGRAM} CURRENT_FILENAME DESTINATION_FILENAME"
+ echo 1>&2 "USAGE: ${PROGRAM} [--commit-template TEMPLATE] CURRENT_FILENAME DESTINATION_FILENAME"
}
while [[ $# -gt 0 ]]
do
key="$1"
- if [[ "$CURRENT_FILENAME" == "" ]]; then
+ if [[ "$key" == "--commit-template" ]]; then
+ if [[ $# -lt 2 ]]; then
+ usage
+ exit 30
+ fi
+ COMMIT_TEMPLATE=$2
+ shift 2
+ continue
+ elif [[ "$CURRENT_FILENAME" == "" ]]; then
CURRENT_FILENAME=$key
elif [[ "$DESTINATION_FILENAME" == "" ]]; then
DESTINATION_FILENAME=$key
@@ -62,15 +77,15 @@ else
# Move the original file to the new destination, in this branch
git mv "${CURRENT_FILENAME}" "${DESTINATION_FILENAME}"
- git commit -nm "--Duplicate $CURRENT_FILENAME history into $DESTINATION_FILENAME"
+ git commit -nm "$(commit_message "--Duplicate $CURRENT_FILENAME history into $DESTINATION_FILENAME")"
# Restore the original file to keep it in the history
git checkout HEAD~ "${CURRENT_FILENAME}"
- git commit -nm "--Restore $CURRENT_FILENAME"
+ git commit -nm "$(commit_message "--Restore $CURRENT_FILENAME")"
# Switch to the original branch and merge this back in.
git checkout -
- git merge --no-ff "$BRANCH_NAME" -m "Copy $CURRENT_FILENAME into $DESTINATION_FILENAME"
+ git merge --no-ff "$BRANCH_NAME" -m "$(commit_message "Copy $CURRENT_FILENAME into $DESTINATION_FILENAME")"
# We're now done with the branch, so delete it.
# We shouldn't need -D here, as we've already merged it back in.
diff --git a/man/git-cp.1 b/man/git-cp.1
index d9a17093..15f52fcd 100644
--- a/man/git-cp.1
+++ b/man/git-cp.1
@@ -1,12 +1,14 @@
.\" generated with Ronn-NG/v0.9.0
.\" http://github.com/apjanke/ronn-ng/tree/0.9.0
-.TH "GIT\-CP" "1" "March 2020" "" "Git Extras"
+.TH "GIT\-CP" "1" "October 2026" "" "Git Extras"
.SH "NAME"
\fBgit\-cp\fR \- Copy a file keeping its history
.SH "SYNOPSIS"
-\fBgit\-cp\fR
+\fBgit\-cp\fR [\-\-commit\-template ]
.SH "DESCRIPTION"
Copy a file keeping its git history\. This allows merge conflict handling\.
+.P
+Use \fB\-\-commit\-template\fR to customize each of the three commits created by the copy\. Every \fB{}\fR in the template is replaced with the default commit message, so text can be added before or after it\. The default template is \fB{}\fR, which leaves commit messages unchanged\.
.SH "EXAMPLES"
Copy README into README\.txt
.IP "" 4
@@ -14,6 +16,20 @@ Copy README into README\.txt
$ git cp README README\.txt
.fi
.IP "" 0
+.P
+Associate the copy commits with issue #1252
+.IP "" 4
+.nf
+$ git cp \-\-commit\-template \'#1252 {}\' README README\.txt
+.fi
+.IP "" 0
+.P
+Append a trailer to the copy commits
+.IP "" 4
+.nf
+$ git cp \-\-commit\-template \'{} [skip ci]\' README README\.txt
+.fi
+.IP "" 0
.SH "AUTHOR"
Written by Rémy Hubscher <\fI\%mailto:hubscher\.remy@gmail\.com\fR>
.SH "REPORTING BUGS"
diff --git a/man/git-cp.html b/man/git-cp.html
index b670f7ba..ecb38fc0 100644
--- a/man/git-cp.html
+++ b/man/git-cp.html
@@ -76,12 +76,14 @@ NAME
SYNOPSIS
-git-cp <current_filename> <destination_filename>
+git-cp [--commit-template <template>] <current_filename> <destination_filename>
DESCRIPTION
Copy a file keeping its git history. This allows merge conflict handling.
+Use --commit-template to customize each of the three commits created by the copy. Every {} in the template is replaced with the default commit message, so text can be added before or after it. The default template is {}, which leaves commit messages unchanged.
+
EXAMPLES
Copy README into README.txt
@@ -89,6 +91,16 @@ EXAMPLES
$ git cp README README.txt
+Associate the copy commits with issue #1252
+
+$ git cp --commit-template '#1252 {}' README README.txt
+
+
+Append a trailer to the copy commits
+
+$ git cp --commit-template '{} [skip ci]' README README.txt
+
+
AUTHOR
Written by Rémy Hubscher <hubscher.remy@gmail.com>
@@ -105,7 +117,7 @@ SEE ALSO
diff --git a/man/git-cp.md b/man/git-cp.md
index 9e333d28..ee8b30c5 100644
--- a/man/git-cp.md
+++ b/man/git-cp.md
@@ -3,18 +3,28 @@ git-cp(1) -- Copy a file keeping its history
## SYNOPSIS
-`git-cp` <current_filename> <destination_filename>
+`git-cp` [--commit-template <template>] <current_filename> <destination_filename>
## DESCRIPTION
Copy a file keeping its git history. This allows merge conflict handling.
+Use `--commit-template` to customize each of the three commits created by the copy. Every `{}` in the template is replaced with the default commit message, so text can be added before or after it. The default template is `{}`, which leaves commit messages unchanged.
+
## EXAMPLES
Copy README into README.txt
$ git cp README README.txt
+ Associate the copy commits with issue #1252
+
+ $ git cp --commit-template '#1252 {}' README README.txt
+
+ Append a trailer to the copy commits
+
+ $ git cp --commit-template '{} [skip ci]' README README.txt
+
## AUTHOR
Written by Rémy Hubscher <>
diff --git a/tests/git-cp.bats b/tests/git-cp.bats
new file mode 100644
index 00000000..d688cf83
--- /dev/null
+++ b/tests/git-cp.bats
@@ -0,0 +1,55 @@
+# shellcheck shell=bash
+
+source "$BATS_TEST_DIRNAME/test_util.sh"
+
+setup_file() {
+ test_util.setup_file
+ test_util.install_command cp
+}
+
+setup() {
+ test_util.cd_test
+ test_util.git_init
+ printf 'original\n' > original.txt
+ git add original.txt
+ git commit -m 'initial'
+}
+
+@test "applies the commit template prefix to all three copy commits" {
+ run git cp --commit-template '#1252 {}' original.txt duplicate.txt
+ assert_success
+ run git log --format=%s
+ assert_success
+ assert_line '#1252 Copy original.txt into duplicate.txt'
+ assert_line '#1252 --Restore original.txt'
+ assert_line '#1252 --Duplicate original.txt history into duplicate.txt'
+ [ -f original.txt ]
+ [ -f duplicate.txt ]
+}
+
+@test "rejects a missing template without changing the repository" {
+ run git cp original.txt duplicate.txt --commit-template
+ assert_failure 30
+ [ ! -e duplicate.txt ]
+ [ "$(git rev-list --count HEAD)" -eq 1 ]
+}
+
+@test "applies the commit template suffix to all three copy commits" {
+ run git cp --commit-template '{} [skip ci]' original.txt duplicate.txt
+ assert_success
+ run git log --format=%s
+ assert_success
+ assert_line 'Copy original.txt into duplicate.txt [skip ci]'
+ assert_line '--Restore original.txt [skip ci]'
+ assert_line '--Duplicate original.txt history into duplicate.txt [skip ci]'
+}
+
+@test "keeps existing commit messages without the option" {
+ run git cp original.txt duplicate.txt
+ assert_success
+ run git log --format=%s
+ assert_success
+ assert_line 'Copy original.txt into duplicate.txt'
+ assert_line '--Restore original.txt'
+ assert_line '--Duplicate original.txt history into duplicate.txt'
+}