Status Quo
Nehmen wir an , wir haben ein Repository, genannt , repo-old
die eine Unter enthält Verzeichnis sub
, dass wir in eine Unter umwandeln möchte Modul mit einem eigenen Repo repo-sub
.
Es ist ferner beabsichtigt, das ursprüngliche Repo repo-old
in ein modifiziertes Repo umzuwandeln, repo-new
wobei alle Commits, die das zuvor vorhandene Unterverzeichnis berühren sub
, nun auf die entsprechenden Commits unseres extrahierten Submodul-Repos verweisenrepo-sub
.
Lass uns ändern
Dies kann mit Hilfe git filter-branch
eines zweistufigen Prozesses erreicht werden:
- Unterverzeichnis-Extraktion von
repo-old
bis repo-sub
(bereits in der akzeptierten Antwort erwähnt )
- Ersetzen des Unterverzeichnisses von
repo-old
bis repo-new
(mit ordnungsgemäßer Festschreibungszuordnung)
Bemerkung : Ich weiß, dass diese Frage alt ist und bereits erwähnt wurde, dass sie git filter-branch
veraltet ist und gefährlich sein könnte. Auf der anderen Seite kann es anderen helfen, persönliche Repositorys zu erstellen, die nach der Konvertierung leicht zu validieren sind. Also sei gewarnt ! Und bitte lassen Sie mich wissen, ob es ein anderes Tool gibt, das dasselbe tut, ohne veraltet zu sein und sicher zu verwenden ist!
Ich werde erklären, wie ich beide Schritte unter Linux mit der Git-Version 2.26.2 realisiert habe. Ältere Versionen funktionieren möglicherweise bis zu einem gewissen Grad, dies muss jedoch getestet werden.
Der Einfachheit halber beschränke ich mich auf den Fall, dass das ursprüngliche Repo nur einen master
Zweig und eine origin
Fernbedienung enthält repo-old
. Seien Sie auch gewarnt, dass ich mich auf temporäre Git-Tags mit dem Präfix verlasse, temp_
die dabei entfernt werden. Wenn also bereits Tags mit ähnlichen Namen vorhanden sind, können Sie das folgende Präfix anpassen. Und zum Schluss beachten Sie bitte, dass ich dies nicht ausführlich getestet habe und es möglicherweise Eckfälle gibt, in denen das Rezept fehlschlägt. Bitte sichern Sie alles, bevor Sie fortfahren !
Die folgenden Bash-Snippets können zu einem großen Skript verkettet werden, das dann in demselben Ordner ausgeführt werden soll, in dem sich das Repo befindet repo-org
. Es wird nicht empfohlen, alles direkt in ein Befehlsfenster zu kopieren und einzufügen (obwohl ich dies erfolgreich getestet habe)!
0. Vorbereitung
Variablen
# 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"
Filterskript
# 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. Extraktion des Unterverzeichnisses
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. Austausch des Unterverzeichnisses
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"
Anmerkung: Wenn das neu erstellte Repo repo-new
währenddessen hängt, git submodule update --init
versuchen Sie stattdessen, das Repository einmal rekursiv neu zu klonen:
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"