Created
June 29, 2021 04:00
-
-
Save aks/c530241eee2487a6b088ec24088ff3f7 to your computer and use it in GitHub Desktop.
Bash script to copy a file in a git repo with history preservation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# git-copy [options] SOURCEFILE NEWFILE | |
usage() { | |
cat 1>&2 <<USAGE | |
usage: git-copy [options] SOURCEFILE NEWFILE | |
Copies a SOURCEFILE within a git repo to a NEWFILE in a way | |
that preserves the change history so that changes in SOURCEFILE | |
are still viewable in NEWFILE. | |
This is done by the following steps: | |
1. create a sub-branch | |
2. with 'git mv', move the SOURCEFILE to NEWFILE | |
3. commit the change | |
4. checkout the original SOURCEFILE again | |
5. commit the change | |
6. checkout the previous branch | |
7. merge the sub-branch without fast-forwarding or editing | |
8. delete the sub-branch | |
The result is a commit on the current branch in which a new file was | |
introduced, with the same change history as the source file. The source file | |
remains unchanged in the current branch because the sub-branch both moved it | |
and then restored it within the same commit-stream. | |
git log on the new file will show only a single commit, but git blame will | |
show the history of each remaining line. | |
Options: | |
-a NAME use NAME as the author name on git commits | |
-e EMAIL use EMAIL as the author email on git commits | |
-h show this help | |
-n show the commands, but don't run them | |
-v show the commands before they're run | |
USAGE | |
exit | |
} | |
talk() { echo 1>&2 "$*" ; } | |
error() { talk "$*" ; exit 1 ; } | |
###################### | |
# branch_name_with_file PREFIX FILEPATH | |
branch_name_with_file() { | |
local file=`basename ${2%%.*}` | |
echo "$1-$file" | |
} | |
GIT() { | |
local cmd="git $@" | |
if (( norun )) ; then | |
talk "(norun) $cmd" | |
else | |
(( verbose )) && talk "--> $cmd" | |
$cmd | |
fi | |
} | |
###################### main code | |
author_name= | |
author_email= | |
git_author= | |
verbose= norun= | |
while getopts 'a:e:hnv' opt ; do | |
case "$opt" in | |
a) author_name="$OPTARG" ;; | |
e) author_email="$OPTARG" ;; | |
h) usage ;; | |
n) norun=1 ;; | |
v) verbose=1 ;; | |
esac | |
done | |
shift $(( OPTIND - 1 )) | |
(( $# > 1 )) || usage | |
src_file="${1:?'Source file missing'}" | |
new_file="${2:?'New file missing'}" | |
if [[ -n "$author_name$author_email" ]]; then | |
git_author='--author "$author_name <$author_email>"' | |
fi | |
new_branch=`branch_name_with_file 'create' $src_file` | |
GIT checkout -b $new_branch | |
GIT mv $src_file $new_file | |
GIT add $new_file | |
GIT commit $git_author -m "Creating $new_file" | |
GIT checkout HEAD~ $src_file | |
GIT add $src_file # restore the original source file | |
GIT commit $git_author -m "Restore $src_file" | |
GIT checkout - | |
GIT merge --no-ff --no-edit $new_branch | |
GIT branch -D $new_branch | |
exit |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment