Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions bin/git-cp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
20 changes: 18 additions & 2 deletions man/git-cp.1
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
.\" 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 <current_filename> <destination_filename>
\fBgit\-cp\fR [\-\-commit\-template <template>] <current_filename> <destination_filename>
.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
.nf
$ 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"
Expand Down
16 changes: 14 additions & 2 deletions man/git-cp.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion man/git-cp.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,28 @@ git-cp(1) -- Copy a file keeping its history

## SYNOPSIS

`git-cp` &lt;current_filename&gt; &lt;destination_filename&gt;
`git-cp` [--commit-template &lt;template&gt;] &lt;current_filename&gt; &lt;destination_filename&gt;

## 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 &lt;<hubscher.remy@gmail.com>&gt;
Expand Down
55 changes: 55 additions & 0 deletions tests/git-cp.bats
Original file line number Diff line number Diff line change
@@ -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'
}
Loading