Skip to content

Instantly share code, notes, and snippets.

@x3ro
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.
#!/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
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

To:

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

@SuperSandro2000
Copy link

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

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

@spatil-git
Copy link

thank you for this script...

@christowiz
Copy link

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

@brandesign
Copy link

Saved my day. Thx :)

@oksmelnik
Copy link

thanks for the script!

@misolo
Copy link

misolo commented Mar 31, 2020

Great! Thank you

@rdvencioneck
Copy link

Thank you!

@realyukii
Copy link

realyukii commented Nov 22, 2024

While reading the article's example, I feel confuse; why is the submodule not inside the parent directory?

@realyukii
Copy link

realyukii commented Nov 22, 2024

after reading the script, I think the script should be executed on the root/parent directory? but the article instruct to change to the directory of your submodule first and then execute the script? am I misunderstand something important?

@realyukii
Copy link

I'm a bit confused when tried to integrate my submodule into the root/parent repository without losing its history, also the use case is unique since some submodule have another submodule (yes, I regret creating a bunch of submodule that is not necessary in my case)

the repository I talking about is this

@realyukii
Copy link

I will assume that you’ve cloned both your repositories into the same directory, one as parent/, and one as sub/.

does this statement mean, that I need to clone two repository that are actually point to the same repository and rename it to parent and sub respectively?

@realyukii
Copy link

realyukii commented Nov 22, 2024

after following the instructions on article:

[hemlo@asus tmp]$  cd sub
[hemlo@asus sub]$  cp ../git-rewrite-to-subfolder .
[hemlo@asus sub]$  ./git-rewrite-to-subfolder
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.

Do you want to proceed? (y/n) y
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.

Please provide the desired path (e.g. 'src/lib'): nvim
100644 1293391370fa01bae5bb02f365cf74290a55a888 0       nvim/.gitmodules
100644 1e119b0170031e8f88f949542d14731c5aeb51aa 0       nvim/README.md
160000 4793864bd3e60d121874a9e6d214a9f7645e3a19 0       nvim/bash-config
100644 d9203eb1875f9ce9508d198c12e9da59e42489a2 0       nvim/common.list.pkg.txt
100644 f86195a541f13dc1112025787df0e3575208e89d 0       nvim/common.list.pkg_aur.txt
160000 dc5960ed1c4a2db3bf7b43cbdec0e0076baf8e3b 0       nvim/external-sources/paru-bin
160000 a0274bc20e11d8672bb2953fdd1d3010c0e708c5 0       nvim/external-sources/st
100644 c44b7890cda5261fcfed0582833dc42e41592aea 0       nvim/guest.list.pkg.txt
100644 c5b17856da251d0855a2ba058321b3bc796efee0 0       nvim/host.list.pkg.txt
100755 34e1030a28427323d44303d3c460835e42b11531 0       nvim/install.sh
100644 4475ba8580fab08d1ca3eb1426ac4bec47f27e63 0       nvim/managed_manually.txt
160000 a636c245b52ad19a8e55e3f225bf1aab972eea26 0       nvim/nvim
160000 4a4a04f5ed6ff1837c7193fdf78696056ba54fc8 0       nvim/scripts
160000 122e57315367b7e4ab40e9824b0f90b14bd3ba7e 0       nvim/systemd
160000 e21d726e03baacb21f55cbf9dc863eacde4a4c32 0       nvim/window-manager
Please take a look at the printed file list. Does it look correct? (y/n) y
WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.
Proceeding with filter-branch...

Rewrite adf3c6b1a3b604b9f9a801a55fe8c45fc9b27dba (49/67) (1 seconds passed, remaining 0 predicted)
Ref 'refs/heads/main' was rewritten
error: Untracked working tree file 'nvim/.gitmodules' would be overwritten by merge.
[hemlo@asus sub]$  cd ../parent/
[hemlo@asus parent]$  rm -r ./nvim
[hemlo@asus parent]$  nvim .gitmodules
[hemlo@asus parent]$  git add -A . && git commit
[hemlo@asus parent]$  git remote add sub ../sub/
[hemlo@asus parent]$  git fetch sub
remote: Enumerating objects: 249, done.
remote: Counting objects: 100% (249/249), done.
remote: Compressing objects: 100% (84/84), done.
remote: Total 249 (delta 94), reused 117 (delta 94), pack-reused 0 (from 0)
Receiving objects: 100% (249/249), 23.80 KiB | 11.90 MiB/s, done.
Resolving deltas: 100% (94/94), done.
From ../sub
 * [new branch]      main       -> sub/main
[hemlo@asus parent]$  git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean
[hemlo@asus parent]$  git merge --allow-unrelated-histories -s ours --no-commit sub/main
Automatic merge went well; stopped before committing as requested
[hemlo@asus parent]$  git clone git@gitlab.com:reyuki/nvim.git
Cloning into 'nvim'...
remote: Enumerating objects: 41, done.
remote: Counting objects: 100% (41/41), done.
remote: Compressing objects: 100% (33/33), done.
remote: Total 41 (delta 15), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (41/41), 5.48 KiB | 5.48 MiB/s, done.
Resolving deltas: 100% (15/15), done.
[hemlo@asus parent]$  rm -rf nvim/.git
[hemlo@asus parent]$  git add nvim/
[hemlo@asus parent]$  git commit -m "integrate nvim"
[main 46cc1d7] integrate nvim
[hemlo@asus parent]$  git log nvim/after/plugin/xxd.vim
commit 46cc1d78b931696a026793d776ed6f5c185f4921 (HEAD -> main)
Merge: 6808e93 640b756
Author: Your Name <user@example.com>
Date:   Fri Nov 22 15:54:01 2024 +0700

    integrate nvim

but as you can see, the history of file xxd.vim is not present, did i miss anything?

@realyukii
Copy link

problem solved, I ended up using a script that I get from stackoverflow :)

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