web-dev-qa-db-fra.com

Comment extraire un sous-répertoire git et en faire un sous-module?

J'ai commencé un projet il y a quelques mois et j'ai tout stocké dans un répertoire principal. Dans mon répertoire principal "Project", il y a plusieurs sous-répertoires contenant différentes choses: Project/paper contient un document écrit dans LaTeX Project/sourcecode/RailsApp contient mon Rails app.

"Project" est GITified et il y a eu beaucoup de commits dans le répertoire "paper" et "RailsApp". Maintenant, comme j'aimerais utiliser cruisecontrol.rb pour mon "RailsApp", je me demande s'il existe un moyen de créer un sous-module à partir de "RailsApp" sans perdre l'historique.

112
patch

De nos jours, il existe un moyen beaucoup plus simple de le faire que d'utiliser manuellement git filter-branch: git subtree

Installation

[~ # ~] note [~ # ~] git-subtree fait désormais partie de git (si vous installez contrib) à partir du 1.7.11, vous pouvez donc déjà l'avoir installé. Vous pouvez vérifier en exécutant git subtree.


Pour installer git-subtree depuis la source (pour les anciennes versions de git):

git clone https://github.com/apenwarr/git-subtree.git

cd git-subtree
Sudo rsync -a ./git-subtree.sh /usr/local/bin/git-subtree

Ou si vous voulez les pages de manuel et tout

make doc
make install

Usage

Divisez un plus grand en plus petits morceaux:

# Go into the project root
cd ~/my-project

# Create a branch which only contains commits for the children of 'foo'
git subtree split --prefix=foo --branch=foo-only

# Remove 'foo' from the project
git rm -rf ./foo

# Create a git repo for 'foo' (assuming we already created it on github)
mkdir foo
pushd foo
git init
git remote add Origin [email protected]:my-user/new-project.git
git pull ../ foo-only
git Push Origin -u master
popd

# Add 'foo' as a git submodule to `my-project`
git submodule add [email protected]:my-user/new-project.git foo

Pour une documentation détaillée (page de manuel), veuillez lire git-subtree.txt.

116
apenwarr

Commander git filter-branch .

La section Examples de la page de manuel montre comment extraire un sous-répertoire dans son propre projet tout en conservant tout son historique et en supprimant l'historique des autres fichiers/répertoires (juste ce que vous ' re cherche).

Pour réécrire le référentiel pour avoir l'air comme si foodir/ avait été la racine de son projet, et rejetait toute autre histoire:

   git filter-branch --subdirectory-filter foodir -- --all

Ainsi, vous pouvez, par exemple, transformer un sous-répertoire de bibliothèque en un référentiel à part entière.
Noter la -- qui sépare filter-branch options parmi les options de révision et --all pour réécrire toutes les branches et balises.

38
Pat Notz

Une façon de procéder est l'inverse - supprimez tout sauf le fichier que vous souhaitez conserver.

Fondamentalement, faites une copie du référentiel, puis utilisez git filter-branch pour tout supprimer sauf le ou les fichiers/dossiers que vous souhaitez conserver.

Par exemple, j'ai un projet dont je souhaite extraire le fichier tvnamer.py vers un nouveau référentiel:

git filter-branch --tree-filter 'for f in *; do if [ $f != "tvnamer.py" ]; then rm -rf $f; fi; done' HEAD

Qui utilise git filter-branch --tree-filter pour parcourir chaque commit, exécutez la commande et réengagez le contenu des répertoires résultants. C'est extrêmement destructeur (vous ne devriez donc le faire que sur une copie de votre référentiel!), Et cela peut prendre un certain temps (environ 1 minute sur un référentiel avec 300 validations et environ 20 fichiers)

La commande ci-dessus exécute simplement le script Shell suivant sur chaque révision, que vous devrez bien sûr modifier (pour le faire exclure votre sous-répertoire au lieu de tvnamer.py):

for f in *; do
    if [ $f != "tvnamer.py" ]; then
        rm -rf $f;
    fi;
done

Le plus gros problème évident est qu'il laisse tous les messages de validation, même s'ils ne sont pas liés au fichier restant. Le script git-remove-empty-commits , corrige cela ..

git filter-branch --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'

Vous devez utiliser le -f forcer l'exécution de l'argument filter-branch encore une fois avec tout dans refs/original/ (qui est essentiellement une sauvegarde)

Bien sûr, cela ne sera jamais parfait, par exemple si vos messages de validation mentionnent d'autres fichiers, mais c'est à peu près aussi proche que le permet un courant git (à ma connaissance de toute façon).

Encore une fois, ne l'exécutez que sur une copie de votre référentiel! - mais en résumé, pour supprimer tous les fichiers sauf "thisismyfilename.txt":

git filter-branch --tree-filter 'for f in *; do if [ $f != "thisismyfilename.txt" ]; then rm -rf $f; fi; done' HEAD
git filter-branch -f --commit-filter 'if [ z$1 = z`git rev-parse $3^{tree}` ]; then skip_commit "$@"; else git commit-tree "$@"; fi'
13
dbr

Les réponses CoolAJ86 et apenwarr sont très similaires. J'ai fait des allers-retours entre les deux en essayant de comprendre les morceaux qui manquaient à l'un ou l'autre. Ci-dessous est une combinaison d'entre eux.

Naviguez d'abord dans Git Bash jusqu'à la racine du dépôt git à diviser. Dans mon exemple ici, c'est ~/Documents/OriginalRepo (master)

# move the folder at prefix to a new branch
git subtree split --prefix=SubFolderName/FolderToBeNewRepo --branch=to-be-new-repo

# create a new repository out of the newly made branch
mkdir ~/Documents/NewRepo
pushd ~/Documents/NewRepo
git init
git pull ~/Documents/OriginalRepo to-be-new-repo

# upload the new repository to a place that should be referenced for submodules
git remote add Origin [email protected]:myUsername/newRepo.git
git Push -u Origin master
popd

# replace the folder with a submodule
git rm -rf ./SubFolderName/FolderToBeNewRepo
git submodule add [email protected]:myUsername/newRepo.git SubFolderName/FolderToBeNewRepo
git branch --delete --force to-be-new-repo

Ci-dessous est une copie de ci-dessus avec les noms personnalisables remplacés et en utilisant https à la place. Le dossier racine est maintenant ~/Documents/_Shawn/UnityProjects/SoProject (master)

# move the folder at prefix to a new branch
git subtree split --prefix=Assets/SoArchitecture --branch=so-package

# create a new repository out of the newly made branch
mkdir ~/Documents/_Shawn/UnityProjects/SoArchitecture
pushd ~/Documents/_Shawn/UnityProjects/SoArchitecture
git init
git pull ~/Documents/_Shawn/UnityProjects/SoProject so-package

# upload the new repository to a place that should be referenced for submodules
git remote add Origin https://github.com/Feddas/SoArchitecture.git
git Push -u Origin master
popd

# replace the folder with a submodule
git rm -rf ./Assets/SoArchitecture
git submodule add https://github.com/Feddas/SoArchitecture.git
git branch --delete --force so-package
4
ShawnFeatherly

Si vous souhaitez transférer un sous-ensemble de fichiers vers un nouveau référentiel mais conserver l'historique, vous allez essentiellement vous retrouver avec un tout nouvel historique. La façon dont cela fonctionnerait est essentiellement la suivante:

  1. Créez un nouveau référentiel.
  2. Pour chaque révision de votre ancien référentiel, fusionnez les modifications apportées à votre module dans le nouveau référentiel. Cela créera une "copie" de l'historique de votre projet existant.

Il devrait être quelque peu simple d'automatiser cela si cela ne vous dérange pas d'écrire un script petit mais velu. Simple, oui, mais aussi douloureux. Les gens ont fait de la réécriture de l'histoire dans Git dans le passé, vous pouvez faire une recherche pour cela.

Alternativement: clonez le référentiel, et supprimez le papier dans le clone, supprimez l'application dans l'original. Cela prendrait une minute, il est garanti de fonctionner, et vous pouvez revenir à des choses plus importantes que d'essayer de purifier votre historique Git. Et ne vous inquiétez pas de l'espace disque occupé par les copies redondantes de l'historique.

3
Dietrich Epp