Question Détachez (déplacez) le sous-répertoire dans un référentiel Git distinct


j'ai un Git référentiel qui contient un certain nombre de sous-répertoires. Maintenant, j'ai trouvé que l'un des sous-répertoires n'est pas lié à l'autre et devrait être détaché dans un référentiel séparé.

Comment puis-je faire cela tout en gardant l'historique des fichiers dans le sous-répertoire?

Je suppose que je pourrais faire un clone et supprimer les parties indésirables de chaque clone, mais je suppose que cela me donnerait l'arbre complet lors de la vérification d'une ancienne révision, etc. Cela pourrait être acceptable, mais je préférerais pouvoir prétendre que le deux référentiels n'ont pas d'historique partagé.

Juste pour clarifier, j'ai la structure suivante:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Mais j'aimerais cela à la place:

XYZ/
    .git/
    XY1/
    XY2/
ABC/
    .git/
    ABC/

1595
2017-12-11 13:57


origine


Réponses:


Mettre à jour: Ce processus est si commun que l'équipe git l'a rendu beaucoup plus simple avec un nouvel outil, git subtree. Vois ici: Détachez (déplacez) le sous-répertoire dans un référentiel Git distinct


Vous voulez cloner votre dépôt et ensuite utiliser git filter-branch pour marquer tout sauf le sous-répertoire que vous voulez dans votre nouveau dépôt pour être ramassé.

  1. Pour cloner votre dépôt local:

    git clone /XYZ /ABC
    

    (Note: le référentiel sera cloné en utilisant des liens physiques, mais ce n'est pas un problème car les fichiers liés ne seront pas modifiés en eux-mêmes - de nouveaux seront créés.)

  2. Maintenant, conservons les branches intéressantes que nous voulons réécrire aussi, puis supprimons l'origine pour éviter d'y pousser et pour nous assurer que les anciennes validations ne seront pas référencées par l'origine:

    cd /ABC
    for i in branch1 br2 br3; do git branch -t $i origin/$i; done
    git remote rm origin
    

    ou pour toutes les branches distantes:

    cd /ABC
    for i in $(git branch -r | sed "s/.*origin\///"); do git branch -t $i origin/$i; done
    git remote rm origin
    
  3. Maintenant, vous pouvez également supprimer les balises qui n'ont aucun lien avec le sous-projet. vous pouvez également le faire plus tard, mais vous devrez peut-être tailler à nouveau votre repo. Je ne l'ai pas fait et j'ai eu WARNING: Ref 'refs/tags/v0.1' is unchanged pour toutes les balises (puisqu'elles n'étaient pas liées au sous-projet); En outre, après avoir retiré ces étiquettes, plus d'espace sera récupéré. Apparemment git filter-branch devrait pouvoir réécrire d'autres étiquettes, mais je n'ai pas pu le vérifier. Si vous voulez supprimer tous les tags, utilisez git tag -l | xargs git tag -d.

  4. Utilisez ensuite filter-branch et reset pour exclure les autres fichiers, afin qu'ils puissent être élagués. Ajoutons aussi --tag-name-filter cat --prune-empty pour supprimer les validations vides et pour réécrire les tags (notez que cela devra supprimer leur signature):

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC -- --all
    

    ou bien, pour réécrire uniquement la branche HEAD et ignorer les tags et autres branches:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Puis supprimez les reflogs de sauvegarde afin que l'espace puisse être réellement récupéré (bien que l'opération soit maintenant destructive)

    git reset --hard
    git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
    git reflog expire --expire=now --all
    git gc --aggressive --prune=now
    

    et maintenant vous avez un dépôt git local du sous-répertoire ABC avec tout son historique préservé.

Note: Pour la plupart des utilisations, git filter-branch devrait en effet avoir le paramètre ajouté -- --all. Oui c'est vraiment --espace--  all. Cela doit être le dernier paramètre de la commande. Comme Matli l'a découvert, cela garde les branches et les tags du projet inclus dans le nouveau repo.

Edit: diverses suggestions des commentaires ci-dessous ont été incorporées pour s'assurer, par exemple, que le référentiel est réellement rétréci (ce qui n'était pas toujours le cas auparavant).


1155
2017-07-25 17:10



L'Easy Way ™

Il s'avère que c'est une pratique si courante et si utile que les sujéts de git l'ont rendu vraiment facile, mais vous devez avoir une version plus récente de git (> = 1.7.11 mai 2012). Voir le annexe pour savoir comment installer le dernier git. En outre, il y a un exemple du monde réel dans le procédure pas à pas au dessous de.

  1. Préparez le vieux repo

    pushd <big-repo>
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Remarque:  <name-of-folder> ne doit PAS contenir de caractères avant ou arrière. Par exemple, le dossier nommé subproject DOIT être transmis comme subproject, NE PAS ./subproject/

    Note pour les utilisateurs de Windows: lorsque la profondeur de votre dossier est> 1, <name-of-folder> doit avoir * séparateur de dossier de style nix (/). Par exemple, le dossier nommé path1\path2\subproject DOIT être transmis comme path1/path2/subproject

  2. Créer le nouveau repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Lier le nouveau dépôt à Github ou ailleurs

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Nettoyer, si on le désire

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Remarque: Cela laisse toutes les références historiques dans le référentiel. annexe ci-dessous si vous êtes réellement préoccupé par avoir commis un mot de passe ou vous devez diminuer la taille de votre fichier .git dossier.

...

Procédure pas à pas

Voici les mêmes étapes que ci-dessus, mais en suivant mes étapes exactes pour mon référentiel au lieu d'utiliser <meta-named-things>.

Voici un projet que j'ai pour implémenter des modules de navigateur JavaScript dans le noeud:

tree ~/Code/node-browser-compat

node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator

Je veux diviser un seul dossier, btoa, dans un référentiel git séparé

pushd ~/Code/node-browser-compat/
git subtree split -P btoa -b btoa-only
popd

J'ai maintenant une nouvelle branche, btoa-only, qui a seulement pour btoa et je veux créer un nouveau référentiel.

mkdir ~/Code/btoa/
pushd ~/Code/btoa/
git init
git pull ~/Code/node-browser-compat btoa-only

Ensuite, je crée un nouveau repo sur Github ou bitbucket, ou quoi que ce soit et ajouter c'est le origin (btw, "origine" est juste une convention, pas une partie de la commande - vous pourriez l'appeler "serveur distant" ou ce que vous voulez)

git remote add origin git@github.com:node-browser-compat/btoa.git
git push origin -u master

Bonne journée!

Remarque: Si vous avez créé un repo avec un README.md, .gitignore et LICENSE, vous devrez tirer d'abord:

git pull origin -u master
git push origin -u master

Enfin, je vais vouloir retirer le dossier du plus gros repo

git rm -rf btoa

...

annexe

Dernier git sur OS X

Pour obtenir la dernière version de git:

brew install git

Pour obtenir le brassage pour OS X:

http://brew.sh

Dernier git sur Ubuntu

sudo apt-get update
sudo apt-get install git
git --version

Si cela ne fonctionne pas (vous avez une très ancienne version d'ubuntu), essayez

sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git

Si cela ne fonctionne toujours pas, essayez

sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree

Merci à rui.araujo des commentaires.

effacer votre historique

Par défaut, supprimer des fichiers de git ne les supprime pas réellement de git, cela signifie simplement qu'ils ne sont plus là. Si vous souhaitez réellement supprimer les références historiques (c'est-à-dire que vous avez un mot de passe validé), vous devez procéder comme suit:

git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD

Après cela, vous pouvez vérifier que votre fichier ou dossier n'apparaît plus dans l'historique git

git log -- <name-of-folder> # should show nothing

Cependant, vous ne peut pas "pousser" les suppressions à github etc. Si vous essayez, vous aurez une erreur et vous devrez git pull avant de pouvoir git push - et alors vous êtes de retour à avoir tout dans votre histoire.

Donc, si vous voulez supprimer l'historique de la "origine" - ce qui signifie de le supprimer de github, bitbucket, etc - vous aurez besoin de supprimer le repo et de repousser une copie élaguée du repo. Mais attendez - Il y a plus! - Si vous êtes vraiment soucieux de vous débarrasser d'un mot de passe ou quelque chose comme ça, vous devrez supprimer la sauvegarde (voir ci-dessous).

fabrication .git plus petit

La commande delete history mentionnée ci-dessus laisse encore derrière un tas de fichiers de sauvegarde - parce que git est trop gentil pour vous aider à ne pas gâcher votre repo par accident. Il finira par supprimer les fichiers orphelins au cours des jours et des mois, mais il les laisse là pendant un certain temps au cas où vous vous rendez compte que vous avez accidentellement supprimé quelque chose que vous ne vouliez pas.

Donc, si vous voulez vraiment vider la poubelle à réduire la taille du clone d'un repo immédiatement, vous devez faire tout ce truc vraiment bizarre:

rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now

git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune

Cela dit, je vous recommande de ne pas effectuer ces étapes, sauf si vous savez que vous avez besoin - juste au cas où vous avez supprimé le mauvais sous-répertoire, vous savez? Les fichiers de sauvegarde ne devraient pas être clonés lorsque vous appuierez le repo, ils seront simplement dans votre copie locale.

Crédit


1122
2018-06-05 13:15



La réponse de Paul crée un nouveau référentiel contenant / ABC, mais ne supprime pas / ABC de / XYZ. La commande suivante supprimera / ABC de / XYZ:

git filter-branch --tree-filter "rm -rf ABC" --prune-empty HEAD

Bien sûr, testez-le d'abord dans un dépôt 'clone --no-hardlinks', et suivez-le avec les commandes reset, gc et prune de Paul.


131
2017-10-19 21:10



J'ai trouvé que, afin de supprimer correctement l'ancien historique du nouveau référentiel, vous devez faire un peu plus de travail après le filter-branch étape.

  1. Faites le clone et le filtre:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Supprimez toutes les références à l'ancien historique. "Origin" faisait le suivi de votre clone, et "original" est l'endroit où filter-branch sauvegarde les anciennes:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Même maintenant, votre historique pourrait être coincé dans un fichier pack que fsck ne touchera pas. Déchirez-le en lambeaux, en créant un nouveau fichier pack et en supprimant les objets inutilisés:

    git repack -ad
    

Il y a une explication de ceci dans le manuel pour la branche de filtre.


94
2018-06-09 15:41



Editer: Script Bash ajouté.

Les réponses données ici ont fonctionné seulement partiellement pour moi; Beaucoup de gros fichiers sont restés dans le cache. Ce qui a finalement fonctionné (après des heures dans #git sur freenode):

git clone --no-hardlinks file:///SOURCE /tmp/blubb
cd blubb
git filter-branch --subdirectory-filter ./PATH_TO_EXTRACT  --prune-empty --tag-name-filter cat -- --all
git clone file:///tmp/blubb/ /tmp/blooh
cd /tmp/blooh
git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

Avec les solutions précédentes, la taille du référentiel était d'environ 100 Mo. Celui-ci l'a ramené à 1,7 MB. Peut-être que ça aide quelqu'un :)


Le script bash suivant automatise la tâche:

!/bin/bash

if (( $# < 3 ))
then
    echo "Usage:   $0 </path/to/repo/> <directory/to/extract/> <newName>"
    echo
    echo "Example: $0 /Projects/42.git first/answer/ firstAnswer"
    exit 1
fi


clone=/tmp/${3}Clone
newN=/tmp/${3}

git clone --no-hardlinks file://$1 ${clone}
cd ${clone}

git filter-branch --subdirectory-filter $2  --prune-empty --tag-name-filter cat -- --all

git clone file://${clone} ${newN}
cd ${newN}

git reflog expire --expire=now --all
git repack -ad
git gc --prune=now

38
2017-08-20 14:11



Ce n'est plus si complexe que vous pouvez simplement utiliser le git filtre-branche commande sur un clone de votre repo pour éliminer les sous-répertoires que vous ne voulez pas, puis appuyez sur la nouvelle télécommande.

git filter-branch --prune-empty --subdirectory-filter <YOUR_SUBDIR_TO_KEEP> master
git push <MY_NEW_REMOTE_URL> -f .

21
2018-03-22 20:55



Mettre à jour: Le module git-subtree était si utile que l'équipe git l'a tiré dans le noyau et l'a fait git subtree. Vois ici: Détachez (déplacez) le sous-répertoire dans un référentiel Git distinct

git-subtree peut être utile pour cela

http://github.com/apenwarr/git-subtree/blob/master/git-subtree.txt (obsolète)

http://psionides.jogger.pl/2010/02/04/sharing-code-between-projects-with-git-subtree/


19
2017-08-06 15:26



Voici une petite modification à CoolAJ86de "The Easy Way ™" réponse afin de diviser plusieurs sous-dossiers (Disons sub1et sub2) dans un nouveau dépôt git.

Easy Way ™ (plusieurs sous-dossiers)

  1. Préparez le vieux repo

    pushd <big-repo>
    git filter-branch --tree-filter "mkdir <name-of-folder>; mv <sub1> <sub2> <name-of-folder>/" HEAD
    git subtree split -P <name-of-folder> -b <name-of-new-branch>
    popd
    

    Remarque:  <name-of-folder> ne doit PAS contenir de caractères avant ou arrière. Par exemple, le dossier nommé subproject DOIT être transmis comme subproject, NE PAS ./subproject/

    Note pour les utilisateurs de Windows: lorsque la profondeur de votre dossier est> 1, <name-of-folder> doit avoir * séparateur de dossier de style nix (/). Par exemple, le dossier nommé path1\path2\subproject DOIT être transmis comme path1/path2/subproject. De plus, n'utilisez pas mvcommande mais move.

    Note finale: la différence unique et grande avec la réponse de base est la deuxième ligne du script "git filter-branch..."

  2. Créer le nouveau repo

    mkdir <new-repo>
    pushd <new-repo>
    
    git init
    git pull </path/to/big-repo> <name-of-new-branch>
    
  3. Lier le nouveau dépôt à Github ou ailleurs

    git remote add origin <git@github.com:my-user/new-repo.git>
    git push origin -u master
    
  4. Nettoyer, si on le désire

    popd # get out of <new-repo>
    pushd <big-repo>
    
    git rm -rf <name-of-folder>
    

    Remarque: Cela laisse toutes les références historiques dans le référentiel. annexe dans la réponse originale si vous êtes réellement préoccupé par avoir commis un mot de passe ou vous devez diminuer la taille de votre fichier .git dossier.


13
2018-04-17 05:12



La question originale veut que XYZ / ABC / (* fichiers) deviennent ABC / ABC / (* fichiers). Après avoir implémenté la réponse acceptée pour mon propre code, j'ai remarqué qu'il change réellement XYZ / ABC / (* fichiers) en ABC / (* fichiers). La page de manuel de filtre-branche dit même,

Le résultat contiendra ce répertoire (et seulement cela) comme sa racine de projet"

En d'autres termes, il favorise le dossier de haut niveau "up" d'un niveau. C'est une distinction importante car, par exemple, dans mon histoire, j'avais renommé un dossier de premier niveau. En faisant la promotion des dossiers "up" d'un niveau, git perd la continuité au commit où j'ai fait le renommage.

I lost contiuity after filter-branch

Ma réponse à la question est alors de faire 2 copies du référentiel et de supprimer manuellement le (s) dossier (s) que vous souhaitez conserver dans chaque répertoire. La page de manuel me soutient avec ceci:

[...] évitez d'utiliser [cette commande] si un simple commit suffit à résoudre votre problème


11
2017-07-25 10:01



Ajouter à La réponse de Paul, J'ai trouvé que pour récupérer l'espace finalement, je dois pousser HEAD vers un dépôt propre et cela réduit la taille du répertoire .git / objects / pack.

c'est à dire.

$ mkdir ... ABC.git
$ cd ... ABC.git
$ git init --bare

Après le pruneau gc, faites aussi:

$ git push ... ABC.git HEAD

Alors vous pouvez faire

$ git clone ... ABC.git

et la taille de ABC / .git est réduite

En fait, certaines des étapes consommant beaucoup de temps (par exemple git gc) ne sont pas nécessaires avec le dépôt push to clean, c'est-à-dire:

$ git clone --no-hardlinks / XYZ / ABC
$ git filtre-branche - sous-répertoire-filtre ABC HEAD
$ git reset --hard
$ git push ... ABC.git HEAD

7
2017-11-12 13:22