Skip to content

Instantly share code, notes, and snippets.

Last active April 17, 2024 21:06
Show Gist options
  • Save emiller/6769886 to your computer and use it in GitHub Desktop.
Save emiller/6769886 to your computer and use it in GitHub Desktop.
git utility to move/rename file or folder and retain history with it.
# git-mv-with-history -- move/rename file or folder, with history.
# Moving a file in git doesn't track history, so the purpose of this
# utility is best explained from the kernel wiki:
# Git has a rename command git mv, but that is just for convenience.
# The effect is indistinguishable from removing the file and adding another
# with different name and the same content.
# While the above sucks, git has the ability to let you rewrite history
# of anything via `filter-branch`. This utility just wraps that functionality,
# but also allows you to easily specify more than one rename/move at a
# time (since the `filter-branch` can be slow on big repos).
# Usage:
# git-rewrite-history [-d/--dry-run] [-v/--verbose] <srcname>=<destname> <...> <...>
# After the repsitory is re-written, eyeball it, commit and push up.
# Given this example repository structure:
# src/makefile
# src/test.cpp
# src/test.h
# src/help.txt
# README.txt
# The command:
# git-rewrite-history \ <-- rename to markdpown
# src/help.txt=docs/ \ <-- move help.txt into docs
# src/makefile=src/Makefile <-- capitalize makefile
# Would restructure and retain history, resulting in the new structure:
# docs/help.txt
# src/Makefile
# src/test.cpp
# src/test.h
# @author emiller
# @date 2013-09-29
function usage() {
echo "usage: `basename $0` [-d/--dry-run] [-v/--verbose] <srcname>=<destname> <...> <...>"
[ -z "$1" ] || echo $1
exit 1
[ ! -d .git ] && usage "error: must be ran from within the root of the repository"
repo=$(basename `git rev-parse --show-toplevel`)
while [[ $1 =~ ^\- ]]; do
case $1 in
usage "invalid argument: $1"
for arg in $@; do
val=`echo $arg | grep -q '=' && echo 1 || echo 0`
src=`echo $arg | sed 's/\(.*\)=\(.*\)/\1/'`
dst=`echo $arg | sed 's/\(.*\)=\(.*\)/\2/'`
dir=`echo $dst | grep -q '/$' && echo $dst || dirname $dst`
[ "$val" -ne 1 ] && usage
[ ! -e "$src" ] && usage "error: $src does not exist"
filter="$filter \n\
if [ -e \"$src\" ]; then \n\
echo \n\
if [ ! -e \"$dir\" ]; then \n\
mkdir -p ${verbose} \"$dir\" && echo \n\
fi \n\
mv $verbose \"$src\" \"$dst\" \n\
fi \n\
[ -z "$filter" ] && usage
if [[ $dryrun -eq 1 || ! -z $verbose ]]; then
echo "tree-filter to execute against $repo:"
echo -e "$filter"
[ $dryrun -eq 0 ] && git filter-branch -f --tree-filter "`echo -e $filter`"
Copy link

I have 70600+ commit logs, when i run 'bash git-mv-with-history old_file_name=new_file_name', it tasks too much hours to overwrite all the logs.Do you have some ideas to descrease time?Thank you

Copy link

Works like magic. I used this script to rename my entire src directory under my project tree. Thanks!

Copy link

mahAnuj commented Apr 1, 2021

Great script!! I have used it to move folders and files both.
I was getting the same error as mentioned by @Yashrajsingh but it was fixed by the solution provided by him -

Copy link

SQA777 commented Jun 14, 2021

I find this script very useful. Moved some tracked files to different directories w/o losing the files' Git logs.

However, when I tried to move a file from one directory to another, it didn't quite complete the move.

I get this message: "WARNING: Ref 'refs/heads/master' is unchanged"

Then Git shows the file has been moved to the new directory but Git status shows a pending "Deleted" status of the file in the old directory.

BTW, the Git repo had no pending commits for any of the tracked files when I invoked this script.

If I commit the changes that this script made to Git, the file in the new directory has no logs anymore for some reason. So I backed out the change by doing a git reset --hard <prev. commit hash>

Has anyone else encountered this problem?

Copy link

ivayloc commented May 1, 2022

From what I understand this script should also move folders with files, when I run it bash git-mv-with-history src=apps\product-catalog\ this error is displayed:

git-mv-with-history: line 63: git: command not found
BusyBox v1.29.3 (2019-01-24 07:45:07 UTC) multi-call binary.

Usage: basename FILE [SUFFIX]

Strip directory path and .SUFFIX from FILE
git-mv-with-history: line 110: git: command not found

Copy link

DryreL commented Aug 4, 2022

git: 'rewrite-history' is not a git command. See 'git --help'.

Copy link

This script knows its job. Thank you!

Copy link

I have been looking for this kind of script. We have some SQL scripts stored as ".txt" files in many directories. I am new to bash script. Is there any way I can rename all files with '.txt' extension to '.sql' in batch?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment