Skip to content

Instantly share code, notes, and snippets.

@aks
Created June 29, 2021 04:00
Show Gist options
  • Save aks/c530241eee2487a6b088ec24088ff3f7 to your computer and use it in GitHub Desktop.
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
#!/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