1945 votes

Détacher le sous-répertoire dans séparé dépôt Git

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

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

Je suppose que je pourrais en faire un clone et supprimer les parties non désirées de chaque clone, mais je suppose que cela me donnerait l'arborescence complète lors de la vérification d'une ancienne révision etc. Cela peut être acceptable, mais je préfère être capable de prétendre que les deux dépôts n'ont pas d'histoire commune.

Juste pour préciser, j'ai la structure suivante:

XYZ/
    .git/
    XY1/
    ABC/
    XY2/

Mais je voudrais plutôt ceci:

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

1558voto

CoolAJ86 Points 19341

Le Moyen Le Plus Facile™

Il s'avère que c'est un tel courantes et les plus utiles à la pratique que les seigneurs de git fait-il vraiment facile, mais vous devez avoir une version plus récente de git (>= 1.7.11 Mai 2012). Voir l' annexe pour savoir comment installer la dernière git. Aussi, il y a un exemple réel dans la procédure pas à pas ci-dessous.

  1. Préparer 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 tête ou en queue de caractères. Par exemple, le dossier nommé subproject DOIT être passé en subproject, PAS ./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. Lien de la nouvelle repo à Github ou partout où

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

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

    Note: Ceci laisse toutes les références historiques dans le référentiel.Voir l' Annexe ci-dessous si vous êtes réellement inquiète d'avoir commis un mot de passe ou vous avez besoin de la diminution de la taille du fichier .git le dossier.

...

Procédure pas à pas

Ce sont les mêmes étapes que ci-dessus, mais à la suite de ma étapes exactes pour mon dépôt au lieu d'utiliser <meta-named-things>.

Voici un projet que j'ai pour la mise en œuvre de JavaScript navigateur modules node:

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 dépôt git

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

J'ai maintenant une nouvelle direction, btoa-only, qui n'a que des commits 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, j'ai créer un nouveau repo Github ou bitbucket, ou que ce soit et d'ajouter que c'est l' origin (btw, "origine" n'est qu'une convention, ne fait pas partie de la commande - vous pouvez l'appeler "remote-server" ou ce que vous voulez)

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

Heureux jour!

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

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

Enfin, je vais vouloir supprimer le dossier de la plus grande repo

git rm -rf btoa

...

Annexe

Dernière git sur OS X

Pour obtenir la dernière version de git:

brew install git

Pour obtenir de l'infusion pour OS X:

http://brew.sh

Dernière git sur Ubuntu

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

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

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

Si cela ne fonctionne toujours pas, essayez de

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, les commentaires.

la suppression de votre histoire

Par défaut, la suppression de fichiers à partir de git n'a pas fait les supprimer à partir de git, c'est juste s'engage à ce que ils ne sont pas plus là. Si vous souhaitez réellement supprimer les références historiques (ex: vous avez engagé un mot de passe), vous devez faire ceci:

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

Après cela, vous pouvez vérifier que votre fichier ou de dossier ne s'affiche plus dans l'historique de git à tous

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

Cependant, vous ne pouvez pas "pousser" les suppressions à github et la comme. Si vous essayez, vous recevrez un message d'erreur et vous devrez git pull avant git push - et puis vous êtes de retour pour avoir tout dans votre histoire.

Donc, si vous voulez supprimer l'historique de "l'origine" - dans le sens de supprimer à partir de github, bitbucket, etc - vous aurez besoin de supprimer les pensions de titres et de re-pousser un taillés copie du repo. Mais attendez - il y a plus! - Si vous êtes vraiment préoccupés par se débarrasser d'un mot de passe ou quelque chose comme cela, vous aurez besoin de tailler la sauvegarde (voir ci-dessous).

rendant .git plus petit

Ladite supprimer l'historique de commande laisse toujours derrière un tas de fichiers de sauvegarde - parce que git est trop aimable à vous aider à ne pas ruiner votre repo par accident. Il sera finalement supprimé les fichiers orphelins au cours des jours et des mois, mais il les laisse là pendant un certain temps dans le cas où vous vous rendez compte que vous avez accidentellement supprimé quelque chose que vous ne voulez pas.

Donc, si vous voulez vraiment vider la corbeille pour réduire le clone de la taille d'une mise en pension immédiatement que vous avez à faire tout cela vraiment des choses bizarres:

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 d' - juste au cas où vous ne taillez le mauvais sous-répertoire, tu sais? Les fichiers de sauvegarde ne devrait pas avoir cloné lorsque vous appuyez sur le repo, il va juste être dans votre copie locale.

Crédit

1270voto

Paul Points 12977

Mise à jour: Ce processus est si commune, que le git de l'équipe, il sera beaucoup plus simple avec un nouvel outil, git subtree. Voir ici: Détacher sous-répertoire en séparer dépôt Git

Vous souhaitez cloner votre dépôt et ensuite utiliser git filter-branch de la marque de tout, mais le sous-répertoire que vous voulez dans votre nouveau repo être récupérées.

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

    git clone /XYZ /ABC
    

    (Remarque: le référentiel sera cloné à l'aide de hard-links, mais ce n'est pas un problème puisque l'unité de fichiers liés ne seront pas modifiés en eux - mêmes- les nouveaux seront créés.)

  2. Maintenant, laissez-nous préserver l'intéressant les branches que nous voulons réécrire ainsi, puis supprimer l'origine pour éviter de pousser là-bas et assurez-vous que le vieux s'engage ne seront pas référencés 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 aussi supprimer des balises qui n'ont aucun rapport avec le sous-projet; vous pouvez aussi le faire plus tard, mais vous pourriez avoir besoin de le tailler votre pension de nouveau. Je n'ai pas fait et j'ai obtenu un WARNING: Ref 'refs/tags/v0.1' is unchanged de toutes les balises (étant donné qu'ils étaient sans rapport avec le sous-projet); en outre, après le retrait de ces balises plus d'espace de remise en état. Apparemment git filter-branch devrait être en mesure de réécrire les autres balises, mais je ne pouvais pas vérifier cela. Si vous souhaitez supprimer toutes les balises, il faut utiliser git tag -l | xargs git tag -d.

  4. Ensuite, l'utilisation de filtre de direction et réinitialiser pour exclure les autres fichiers, ils peuvent être taillés. Nous allons également ajouter --tag-name-filter cat --prune-empty supprimer vide s'engage et à réécrire les balises (notez que cela aura pour les dépouiller de leur signature):

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

    ou sinon, pour que réécrire la TÊTE de la branche et d'ignorer les balises et les autres branches:

    git filter-branch --tag-name-filter cat --prune-empty --subdirectory-filter ABC HEAD
    
  5. Puis supprimer la sauvegarde reflogs afin que l'espace peut être vraiment récupéré (bien que maintenant l'opération est destructeur)

    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 de l'ABC de la sous-répertoire, avec toute son histoire préservée.

Remarque: Pour la plupart des utilisations, git filter-branch devrait en effet avoir le paramètre ajouté -- --all. Oui, c'est vraiment dash dash espace dash dash all. Ce doit être la dernière paramètres pour la commande. Comme Matli découvert, ce qui maintient le projet de branches et de tags inclus dans le nouveau repo.

Edit: diverses suggestions de commentaires ci-dessous ont été intégrés afin de s'assurer, par exemple, que le référentiel est en fait diminué (ce qui n'était pas toujours le cas avant).

140voto

pgs Points 3453

Réponse de Paul crée un nouveau référentiel contenant /ABC mais ne supprime pas /ABC d’intérieur/XYZ. La commande suivante supprime /ABC d’intérieur/XYZ :

Bien sûr, tout d’abord la tester dans un référentiel « cloner--no-liens durs » et suivez-le avec les commandes de réinitialisation, gc et prune que Paul énumère.

98voto

Josh Lee Points 53741

J'ai trouvé que pour bien supprimer l'ancien de l'histoire du nouveau référentiel, vous avez à faire un peu plus de travail après l' filter-branch étape.

  1. Faire le clone et le filtre:

    git clone --no-hardlinks foo bar; cd bar
    git filter-branch --subdirectory-filter subdir/you/want
    
  2. Supprimer toute référence à l'histoire ancienne. "origine" a été de garder une trace de votre clone, et "original" est l'endroit où filter-branch enregistre les vieux trucs:

    git remote rm origin
    git update-ref -d refs/original/refs/heads/master
    git reflog expire --expire=now --all
    
  3. Même maintenant, votre histoire pourrait être coincé dans un packfile que fsck ne touche pas. Le déchirer en lambeaux, la création d'une nouvelle packfile et de supprimer les objets inutilisés:

    git repack -ad
    

Il y a une explication à cela dans le manuel pour filter-branch.

40voto

Simon A. Eugster Points 1815

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

Avec les solutions antérieures, le référentiel comportait environ 100 Mo. Celui-ci il a ramené à 1,7 MB. Peut-être que ça aide quelqu'un  :)

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