140 votes

Convertir rétrospectivement un dossier Git en sous-module ?

Il arrive souvent que vous écriviez un projet quelconque et qu'au bout d'un certain temps, il devienne évident qu'un composant du projet est en fait utile en tant que composant autonome (une bibliothèque, par exemple). Si vous avez eu cette idée dès le début, il y a de fortes chances que la majeure partie de ce code se trouve dans son propre dossier.

Existe-t-il un moyen de convertir l'un des sous-répertoires d'un projet Git en un sous-module ?

L'idéal serait que tout le code de ce répertoire soit supprimé du projet parent, et que le projet de sous-module soit ajouté à sa place, avec tout l'historique approprié, et que tous les commits du projet parent pointent vers les commits corrects du sous-module.

0 votes

stackoverflow.com/questions/1365541/ peut en aider certains :)

2 votes

Cela ne fait pas partie de la question initiale, mais ce qui serait encore plus cool serait un moyen de conserver l'historique des fichiers qui ont commencé en dehors du dossier et qui ont été déplacés dans celui-ci. Pour l'instant, toutes les réponses perdent l'historique antérieur au déplacement.

2 votes

Le lien de @ggll est en panne. Voici une copie archivée.

99voto

knittl Points 64110

Pour isoler un sous-répertoire dans son propre référentiel, utilisez l'option filter-branch sur un clone du dépôt original :

git clone <your_project> <your_submodule>
cd <your_submodule>
git filter-branch --subdirectory-filter 'path/to/your/submodule' --prune-empty -- --all

Il ne s'agit alors que de supprimer votre répertoire d'origine et d'ajouter le sous-module à votre projet parent.

19 votes

Vous voudrez probablement aussi git remote rm <name> après la branche du filtre, puis éventuellement ajouter une nouvelle télécommande. De même, s'il y a des fichiers ignorés, un fichier git clean -xd -f peut être utile

0 votes

-- --all peut être remplacé par le nom d'une branche si le sous-module ne doit être extrait que de cette branche.

0 votes

Fait git clone <your_project> <your_submodule> ne télécharger que les fichiers de votre sous-module ?

44voto

user141279 Points 1

Commencez par changer le répertoire en dossier qui sera un sous-module. Ensuite :

git init
git remote add origin <repourl>
git add .
git commit -am 'first commit in submodule'
git push -u origin master
cd ..
rm -rf <folder> # the folder which will be a submodule
git commit -am 'deleting folder'
git submodule add <repourl> <folder> # add the submodule
git commit -am 'adding submodule'

16voto

oodavid Points 378

Je sais que c'est un vieux fil, mais les réponses ici écrasent tous les commits liés dans d'autres branches.

Un moyen simple de cloner et de conserver toutes ces branches et commits supplémentaires :

1 - Assurez-vous d'avoir cet alias git

git config --global alias.clone-branches '! git branch -a | sed -n "/\/HEAD /d; /\/master$/d; /remotes/p;" | xargs -L1 git checkout -t'

2 - Cloner le remote, tirer toutes les branches, changer le remote, filtrer votre répertoire, pousser

git clone git@github.com:user/existing-repo.git new-repo
cd new-repo
git clone-branches
git remote rm origin
git remote add origin git@github.com:user/new-repo.git
git remote -v
git filter-branch --subdirectory-filter my_directory/ -- --all
git push --all
git push --tags

5voto

P. B. Points 176

Statu quo

Supposons que nous ayons un référentiel appelé repo-old qui contient un sous répertoire sub que nous aimerions convertir en un sous module avec son propre repo repo-sub .

Il est également prévu que le repo original repo-old devrait être converti en un repo modifié repo-new où toutes les modifications touchent le sous-répertoire existant précédemment sub vont maintenant pointer vers les commits correspondants de notre repo de sous-module extrait repo-sub .

Changeons

Il est possible d'y parvenir avec l'aide de git filter-branch en deux étapes :

  1. Extraction de sous-répertoires à partir de repo-old a repo-sub (déjà mentionnée dans le document accepté responder )
  2. Remplacement du sous-répertoire de repo-old a repo-new (avec un mappage correct des livraisons)

Remarque : Je sais que cette question est ancienne et qu'il a déjà été mentionné que git filter-branch est en quelque sorte dépréciée et peut être dangereuse. Mais d'un autre côté, cela pourrait aider d'autres personnes avec des dépôts personnels qui sont faciles à valider après la conversion. Donc, soyez averti ! Et merci de me faire savoir s'il existe un autre outil qui fait la même chose sans être déprécié et qui peut être utilisé en toute sécurité !

Je vais expliquer comment j'ai réalisé ces deux étapes sous linux avec git version 2.26.2 ci-dessous. Les versions plus anciennes peuvent fonctionner dans une certaine mesure, mais cela doit être testé.

Par souci de simplicité, je me limiterai au cas où il n'y a qu'un master et une branche origin dans le repo original repo-old . Sachez également que je m'appuie sur des balises git temporaires avec le préfixe temp_ qui seront supprimées au cours du processus. Donc, s'il y a déjà des balises nommées de façon similaire, vous pourriez vouloir ajuster le préfixe ci-dessous. Enfin, sachez que je n'ai pas testé cette méthode de manière approfondie et qu'il peut y avoir des cas où la recette échoue. Alors, s'il vous plaît sauvegarder tout avant de procéder !

Les snippets bash suivants peuvent être concaténés en un seul gros script qui doit ensuite être exécuté dans le même dossier que celui où se trouve le repo. repo-org vies. Il n'est pas recommandé de tout copier et coller directement dans une fenêtre de commande (même si j'ai testé cela avec succès) !

0. Préparation

Variables

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

Filtre 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. Extraction de sous-répertoire

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. Remplacement des sous-répertoires

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"

Remarque : Si le repo nouvellement créé repo-new se bloque pendant git submodule update --init alors essayez de re-cloner le dépôt récursivement une fois à la place :

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"

2voto

LemmeTestThat Points 198

La réponse actuelle de @knittl qui utilise filter-branch nous rapproche de l'effet désiré, mais lorsque je l'ai essayé, Git m'a lancé un avertissement :

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.

Neuf ans après que cette question a été posée pour la première fois et qu'une réponse y a été apportée, filter-branch est obsolète en faveur de git filter-repo . En effet, lorsque j'ai regardé mon historique git en utilisant git log --all --oneline --graph Il était rempli d'engagements non pertinents.

Comment l'utiliser git filter-repo alors ? Github propose un article assez intéressant sur ce sujet. aquí . (Notez que vous devrez l'installer indépendamment de git. J'ai utilisé la version python avec pip3 install git-filter-repo )

Au cas où ils décideraient de déplacer/supprimer l'article, je résumerai et généraliserai leur procédure ci-dessous :

git clone <your_old_project_remote> <your_submodule>
cd <your_submodule>
git filter-repo --path path/to/your/submodule
git remote set-url origin <your_new_submodule_remote>
git push -u origin <branch_name>

A partir de là, il suffit d'enregistrer le nouveau dépôt en tant que sous-module à l'endroit voulu :

cd <path/to/your/parent/module>
git submodule add <your_new_submodule_remote>
git submodule update
git commit

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X