Skip to content

Instantly share code, notes, and snippets.

Created August 15, 2013 15:57
Show Gist options
  • Save x3ro/6242017 to your computer and use it in GitHub Desktop.
Save x3ro/6242017 to your computer and use it in GitHub Desktop.
# 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.
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.
request_input "Please provide the desired path (e.g. 'src/lib'):"
# Escape input for SED, taken from
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" \
Copy link

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


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

Copy link

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

Copy link

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}"

Copy link

thank you for this script...

Copy link

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

Copy link

Saved my day. Thx :)

Copy link

thanks for the script!

Copy link

misolo commented Mar 31, 2020

Great! Thank you

Copy link

Thank you!

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