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.

2voto

SamGoody Points 4406

Le projet officiel git recommande désormais d'utiliser git-filter-repo

# install git-filter-repo, see [1] for install via pip, or other OS's.
sudo apt-get install git-filter-repo 

# copy your repo; everything EXCEPT the subdir will be deleted, and the subdir will become root.
# --no-local is required to prevent git from hard linking to files in the original, and is checked by `filter-branch`
git clone working-dir/.git working-dir-copy --no-local
cd working-dir-copy

# extract the desired subdirectory and its history.
git filter-repo --subdirectory-filter foodir

# foodir is now its own directory. Push it to github/gitlab etc
git remote add origin user@hosting/project.git
git push -u origin --all
git push -u origin --tags

Merci à ce gist también.

EDIT : Pour les utilisateurs de LFS (les pauvres), git clone ne récupère PAS l'historique lfs complet d'une image, ce qui fait échouer git push.

// Original branch needs to get history of all images
git lfs fetch --all

// clone needs to copy the history
git lfs install --skip-smudge
git lfs pull working-dir --all

https://github.com/newren/git-filter-repo/blob/main/INSTALL.md

1voto

twalberg Points 19804

C'est possible, mais ce n'est pas simple. Si vous cherchez git filter-branch , subdirectory y submodule Il existe des articles intéressants sur le processus. Il s'agit essentiellement de créer deux clones de votre projet, en utilisant la technologie git filter-branch pour supprimer tout sauf le sous-répertoire dans l'un, et pour ne supprimer que ce sous-répertoire dans l'autre. Ensuite, vous pouvez établir le second dépôt comme un sous-module du premier.

0voto

jthill Points 10384

Cela fait la conversion sur place, vous pouvez revenir en arrière comme vous le feriez avec n'importe quel filtre-branche (j'utilise git fetch . +refs/original/*:* ).

J'ai un projet avec un utils qui a commencé à être utile dans d'autres projets, et qui a voulu diviser son histoire en sous-modules. Je n'ai pas pensé à regarder sur SO d'abord donc j'ai écrit le mien, il construit l'historique localement donc c'est un peu plus rapide, après quoi si vous voulez vous pouvez configurer la commande d'aide de .gitmodules et ainsi de suite, et pousser les historiques des sous-modules eux-mêmes où vous le souhaitez.

La commande dépouillée est ici, la documentation dans les commentaires, dans la commande non dépouillée qui suit. Exécutez-la comme sa propre commande, avec subdir comme subdir=utils git split-submodule si vous divisez le utils répertoire. C'est un peu bricolé parce que c'est un cas unique, mais je l'ai testé sur le sous-répertoire Documentation dans l'historique Git.

#!/bin/bash
# put this or the commented version below in e.g. ~/bin/git-split-submodule
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}
${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
    | git cat-file --batch-check='%(objectname)' | uniq`)
[[ $pathcheck = *:* ]] || {
    subfam=($( set -- ${fam[@]}; shift;
        for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
            git rev-parse -q --verify $tpar:"$subdir"
        done
    ))
    git rm -rq --cached --ignore-unmatch  "$subdir"
    if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
        git update-index --add --cacheinfo 160000,$subfam,"$subdir"
    else
        subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
            | git commit-tree $GIT_COMMIT:"$subdir" $(
                ${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
            ` &&
        git update-index --add --cacheinfo 160000,$subnew,"$subdir"
    fi
}
${debug+set +x}

#!/bin/bash
# Git filter-branch to split a subdirectory into a submodule history.

# In each commit, the subdirectory tree is replaced in the index with an
# appropriate submodule commit.
# * If the subdirectory tree has changed from any parent, or there are
#   no parents, a new submodule commit is made for the subdirectory (with
#   the current commit's message, which should presumably say something
#   about the change). The new submodule commit's parents are the
#   submodule commits in any rewrites of the current commit's parents.
# * Otherwise, the submodule commit is copied from a parent.

# Since the new history includes references to the new submodule
# history, the new submodule history isn't dangling, it's incorporated.
# Branches for any part of it can be made casually and pushed into any
# other repo as desired, so hooking up the `git submodule` helper
# command's conveniences is easy, e.g.
#     subdir=utils git split-submodule master
#     git branch utils $(git rev-parse master:utils)
#     git clone -sb utils . ../utilsrepo
# and you can then submodule add from there in other repos, but really,
# for small utility libraries and such, just fetching the submodule
# histories into your own repo is easiest. Setup on cloning a
# project using "incorporated" submodules like this is:
#   setup:  utils/.git
#
#   utils/.git:
#       @if _=`git rev-parse -q --verify utils`; then \
#           git config submodule.utils.active true \
#           && git config submodule.utils.url "`pwd -P`" \
#           && git clone -s . utils -nb utils \
#           && git submodule absorbgitdirs utils \
#           && git -C utils checkout $$(git rev-parse :utils); \
#       fi
# with `git config -f .gitmodules submodule.utils.path utils` and
# `git config -f .gitmodules submodule.utils.url ./`; cloners don't
# have to do anything but `make setup`, and `setup` should be a prereq
# on most things anyway.

# You can test that a commit and its rewrite put the same tree in the
# same place with this function:
# testit ()
# {
#     tree=($(git rev-parse `git rev-parse $1`: refs/original/refs/heads/$1));
#     echo $tree `test $tree != ${tree[1]} && echo ${tree[1]}`
# }
# so e.g. `testit make~95^2:t` will print the `t` tree there and if
# the `t` tree at ~95^2 from the original differs it'll print that too.

# To run it, say `subdir=path/to/it git split-submodule` with whatever
# filter-branch args you want.

# $GIT_COMMIT is set if we're already in filter-branch, if not, get there:
${GIT_COMMIT-exec git filter-branch --index-filter "subdir=$subdir; ${debug+debug=$debug;} $(sed 1,/SNIP/d "$0")" "$@"}

${debug+set -x}
fam=(`git rev-list --no-walk --parents $GIT_COMMIT`)
pathcheck=(`printf "%s:$subdir\\n" ${fam[@]} \
    | git cat-file --batch-check='%(objectname)' | uniq`)

[[ $pathcheck = *:* ]] || {
    subfam=($( set -- ${fam[@]}; shift;
        for par; do tpar=`map $par`; [[ $tpar != $par ]] &&
            git rev-parse -q --verify $tpar:"$subdir"
        done
    ))

    git rm -rq --cached --ignore-unmatch  "$subdir"
    if (( ${#pathcheck[@]} == 1 && ${#fam[@]} > 1 && ${#subfam[@]} > 0)); then
        # one id same for all entries, copy mapped mom's submod commit
        git update-index --add --cacheinfo 160000,$subfam,"$subdir"
    else
        # no mapped parents or something changed somewhere, make new
        # submod commit for current subdir content.  The new submod
        # commit has all mapped parents' submodule commits as parents:
        subnew=`git cat-file -p $GIT_COMMIT | sed 1,/^$/d \
            | git commit-tree $GIT_COMMIT:"$subdir" $(
                ${subfam:+printf ' -p %s' ${subfam[@]}}) 2>&-
            ` &&
        git update-index --add --cacheinfo 160000,$subnew,"$subdir"
    fi
}
${debug+set +x}

0voto

Nagev Points 658

S'il est acceptable de conserver l'historique antérieur dans le fichier dossier parent uniquement Une solution simple consiste à supprimer le du sous-dossier de l'index et de démarrer un nouveau dépôt ou un nouveau sous-module dans le même chemin. Par exemple :

  1. Ajouter subdir a .gitignore
  2. rm -r --cached subdir
  3. git add .gitignore && git commit
  4. cd subdir && git init && git add .
  5. Engager les fichiers initiaux dans le nouveau subdir dépôt

De git help rm :

-mise en cache : Utilisez cette option pour dépiler et supprimer les chemins uniquement de l'index. Les fichiers de l'arborescence de travail, qu'ils aient été modifiés ou non, ne seront pas touchés.

Ayant utilisé sous-modules dans le code de production, je peux dire que c'est une bonne solution, d'autant plus qu'elle documente les dépendances du projet.

Pour un projet simple, ou s'il n'y a pas d'autres développeurs, ou s'il n'y a pas de dépendance forte et que la structure des dossiers est plus une commodité, les sous-modules peuvent être un peu trop nombreux. Si vous choisissez de suivre cette voie, sautez l'étape 1 et procédez de la même manière.

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