Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#!/bin/bash
# We need the TAB character for SED (Mac OS X sed does not understand \t)
TAB="$(printf '\t')"
function abort {
echo "$(tput setaf 1)$1$(tput sgr0)"
exit 1
}
function request_input {
read -p "$(tput setaf 4)$1 $(tput sgr0)"
}
function request_confirmation {
read -p "$(tput setaf 4)$1 (y/n) $(tput sgr0)"
[ "$REPLY" == "y" ] || abort "Aborted!"
}
cat << "EOF"
This script rewrites your entire history, moving the current repository root
into a subdirectory. This can be useful if you want to merge a submodule into
its parent repository.
For example, your main repository might contain a submodule at the path src/lib/,
containing a file called "test.c".
If you would merge the submodule into the parent repository without further
modification, all the commits to "test.c" will have the path "/test.c", whereas
the file now actually lives in "src/lib/test.c".
If you rewrite your history using this script, adding "src/lib/" to the path
and the merging into the parent repository, all paths will be correct.
NOTE: This script might complete garble your repository, so PLEASE apply this
only to a clone of the repository where it does not matter if the repo is destroyed.
EOF
request_confirmation "Do you want to proceed?"
cat << "EOF"
Please provide the path which should be prepended to the current root. In the
above example, that would be "src/lib". Please note that the path MUST NOT contain
a trailing slash.
EOF
request_input "Please provide the desired path (e.g. 'src/lib'):"
# Escape input for SED, taken from http://stackoverflow.com/a/2705678/124257
TARGET_PATH=$(echo -n "$REPLY" | sed -e 's/[\/&]/\\&/g')
# Last confirmation
git ls-files -s | sed "s/${TAB}/${TAB}$TARGET_PATH\//"
request_confirmation "Please take a look at the printed file list. Does it look correct?"
# The actual processing happens here
CMD="git ls-files -s | sed \"s/${TAB}/${TAB}$TARGET_PATH\//\" | GIT_INDEX_FILE=\${GIT_INDEX_FILE}.new git update-index --index-info && mv \${GIT_INDEX_FILE}.new \${GIT_INDEX_FILE}"
git filter-branch \
--index-filter "$CMD" \
HEAD
@jeremysears

This comment has been minimized.

Copy link

jeremysears commented Apr 20, 2017

This is awesome. Thank you! Also note that in your main article related to this, the syntax for the following command has changed:

$ git merge -s ours --no-commit sub/master

To:

$ git merge -s ours --no-commit --allow-unrelated-histories sub/master
@SuperSandro2000

This comment has been minimized.

Copy link

SuperSandro2000 commented Mar 5, 2018

I wanted to say thank you for this script. It saved my life.

@Bjornelmers

This comment has been minimized.

Copy link

Bjornelmers commented Feb 1, 2019

Love the script! However there seems to be a problem with it if the history contains empty commits. I managed to solve the problem by modifying line 60 to:

CMD="git ls-files -s | sed "s/${TAB}/${TAB}$TARGET_PATH//" | GIT_INDEX_FILE=${GIT_INDEX_FILE}.new git update-index --index-info && [ ! -f ${GIT_INDEX_FILE}.new ] || mv ${GIT_INDEX_FILE}.new ${GIT_INDEX_FILE}"

@San1986

This comment has been minimized.

Copy link

San1986 commented Mar 6, 2019

thank you for this script...

@christowiz

This comment has been minimized.

Copy link

christowiz commented Mar 6, 2019

Great article. Thanks for the script and thanks to @jeremysears for the updated command.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.