Status quo
Supponiamo che abbiamo un repository chiamato repo-old
che contiene una sub directory sub
che vorremmo convertire in un sub modulo con il proprio repo repo-sub
.
È inoltre inteso che il repo originale repo-old
dovrebbe essere convertito in un repo modificato in repo-new
cui tutti i commit che toccano la sottodirectory esistente in precedenza sub
devono ora puntare ai commit corrispondenti del nostro repo del sottomodulo estratto repo-sub
.
Facciamo cambio
È possibile ottenere ciò con l'aiuto di git filter-branch
un processo in due fasi:
- Estrazione di sottodirectory da
repo-old
a repo-sub
(già menzionato nella risposta accettata )
- Sostituzione di sottodirectory da
repo-old
a repo-new
(con mappatura commit corretta)
Nota : so che questa domanda è vecchia ed è già stato detto che git filter-branch
è un po 'deprecata e potrebbe essere pericolosa. Ma d'altra parte potrebbe aiutare gli altri con archivi personali che sono facili da convalidare dopo la conversione. Quindi state attenti ! E per favore fatemi sapere se c'è qualche altro strumento che fa la stessa cosa senza essere deprecato ed è sicuro da usare!
Spiegherò come ho realizzato entrambi i passaggi su Linux con git versione 2.26.2 di seguito. Le versioni precedenti potrebbero funzionare in qualche modo, ma devono essere testate.
Per semplicità mi limiterò al caso in cui nel repository originale ci siano solo un master
ramo e un origin
telecomando repo-old
. Inoltre, tieni presente che mi affido a tag git temporanei con il prefisso temp_
che verranno rimossi durante il processo. Quindi, se ci sono già tag con nomi simili, potresti voler regolare il prefisso di seguito. Infine, tieni presente che non l'ho testato ampiamente e potrebbero esserci casi d'angolo in cui la ricetta fallisce. Quindi per favore fai il backup di tutto prima di procedere !
I seguenti frammenti di bash possono essere concatenati in un unico grande script che dovrebbe quindi essere eseguito nella stessa cartella in cui repo-org
risiede il repository . Non è consigliabile copiare e incollare tutto direttamente in una finestra di comando (anche se l'ho testato con successo)!
0. Preparazione
variabili
# 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"
Filtra script
# 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. Estrazione di sottodirectory
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. Sostituzione di sottodirectory
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"
Nota: se il repository appena creato si repo-new
blocca durante, git submodule update --init
provare a ri-clonare il repository in modo ricorsivo una volta:
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"