Status quo
Vamos supor que temos um repositório chamado repo-old
que contém um sub diretório sub
que gostaria de converter em uma sub módulo com seu próprio repo repo-sub
.
Pretende-se ainda que o repo original repo-old
seja convertido em um repo modificado, repo-new
onde todos os commits que tocam no subdiretório existente sub
devem agora apontar para os commits correspondentes de nosso repo de submódulo extraído repo-sub
.
Vamos mudar
É possível conseguir isso com a ajuda de git filter-branch
um processo de duas etapas:
- Extração de subdiretório de
repo-old
para repo-sub
(já mencionado na resposta aceita )
- Substituição de subdiretório de
repo-old
para repo-new
(com mapeamento de confirmação adequado)
Observação : Eu sei que esta pergunta é antiga e já foi mencionado que git filter-branch
está meio obsoleta e pode ser perigosa. Mas, por outro lado, pode ajudar outras pessoas com repositórios pessoais que são fáceis de validar após a conversão. Portanto, esteja avisado ! E, por favor, deixe-me saber se há alguma outra ferramenta que faz a mesma coisa sem ser descontinuada e é segura de usar!
Explicarei como realizei as duas etapas no linux com git versão 2.26.2 abaixo. Versões mais antigas podem funcionar até certo ponto, mas isso precisa ser testado.
Para simplificar, vou me restringir ao caso em que há apenas uma master
filial e um origin
controle remoto no repo original repo-old
. Também esteja avisado que eu confio em tags git temporárias com o prefixo temp_
que serão removidas no processo. Portanto, se já houver tags com nomes semelhantes, você pode querer ajustar o prefixo abaixo. E, finalmente, esteja ciente de que não testei extensivamente isso e pode haver casos em que a receita falha. Portanto, faça backup de tudo antes de continuar !
Os seguintes fragmentos de bash podem ser concatenados em um grande script que deve ser executado na mesma pasta onde repo-org
reside o repositório . Não é recomendado copiar e colar tudo diretamente em uma janela de comando (embora eu tenha testado isso com sucesso)!
0. Preparação
Variáveis
# Root directory where repo-org lives
# and a temporary location for git filter-branch
root="$PWD"
temp='/dev/shm/tmp'
# The old repository and the subdirectory we'd like to extract
repo_old="$root/repo-old"
repo_old_directory='sub'
# The new submodule repository, its url
# and a hash map folder which will be populated
# and later used in the filter script below
repo_sub="$root/repo-sub"
repo_sub_url='https://github.com/somewhere/repo-sub.git'
repo_sub_hashmap="$root/repo-sub.map"
# The new modified repository, its url
# and a filter script which is created as heredoc below
repo_new="$root/repo-new"
repo_new_url='https://github.com/somewhere/repo-new.git'
repo_new_filter="$root/repo-new.sh"
Script de filtro
# The index filter script which converts our subdirectory into a submodule
cat << EOF > "$repo_new_filter"
#!/bin/bash
# Submodule hash map function
sub ()
{
local old_commit=\$(git rev-list -1 \$1 -- '$repo_old_directory')
if [ ! -z "\$old_commit" ]
then
echo \$(cat "$repo_sub_hashmap/\$old_commit")
fi
}
# Submodule config
SUB_COMMIT=\$(sub \$GIT_COMMIT)
SUB_DIR='$repo_old_directory'
SUB_URL='$repo_sub_url'
# Submodule replacement
if [ ! -z "\$SUB_COMMIT" ]
then
touch '.gitmodules'
git config --file='.gitmodules' "submodule.\$SUB_DIR.path" "\$SUB_DIR"
git config --file='.gitmodules' "submodule.\$SUB_DIR.url" "\$SUB_URL"
git config --file='.gitmodules' "submodule.\$SUB_DIR.branch" 'master'
git add '.gitmodules'
git rm --cached -qrf "\$SUB_DIR"
git update-index --add --cacheinfo 160000 \$SUB_COMMIT "\$SUB_DIR"
fi
EOF
chmod +x "$repo_new_filter"
1. Extração de subdiretório
cd "$root"
# Create a new clone for our new submodule repo
git clone "$repo_old" "$repo_sub"
# Enter the new submodule repo
cd "$repo_sub"
# Remove the old origin remote
git remote remove origin
# Loop over all commits and create temporary tags
for commit in $(git rev-list --all)
do
git tag "temp_$commit" $commit
done
# Extract the subdirectory and slice commits
mkdir -p "$temp"
git filter-branch --subdirectory-filter "$repo_old_directory" \
--tag-name-filter 'cat' \
--prune-empty --force -d "$temp" -- --all
# Populate hash map folder from our previously created tag names
mkdir -p "$repo_sub_hashmap"
for tag in $(git tag | grep "^temp_")
do
old_commit=${tag#'temp_'}
sub_commit=$(git rev-list -1 $tag)
echo $sub_commit > "$repo_sub_hashmap/$old_commit"
done
git tag | grep "^temp_" | xargs -d '\n' git tag -d 2>&1 > /dev/null
# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_sub_url"
# git push -u origin master
2. Substituição de subdiretório
cd "$root"
# Create a clone for our modified repo
git clone "$repo_old" "$repo_new"
# Enter the new modified repo
cd "$repo_new"
# Remove the old origin remote
git remote remove origin
# Replace the subdirectory and map all sliced submodule commits using
# the filter script from above
mkdir -p "$temp"
git filter-branch --index-filter "$repo_new_filter" \
--tag-name-filter 'cat' --force -d "$temp" -- --all
# Add the new url for this repository (and e.g. push)
git remote add origin "$repo_new_url"
# git push -u origin master
# Cleanup (commented for safety reasons)
# rm -rf "$repo_sub_hashmap"
# rm -f "$repo_new_filter"
Observação: se o repo recém-criado repo-new
travar durante git submodule update --init
, tente clonar novamente o repositório recursivamente uma vez:
cd "$root"
# Clone the new modified repo recursively
git clone --recursive "$repo_new" "$repo_new-tmp"
# Now use the newly cloned one
mv "$repo_new" "$repo_new-bak"
mv "$repo_new-tmp" "$repo_new"
# Cleanup (commented for safety reasons)
# rm -rf "$repo_new-bak"