Divisez le grand dépôt Git en plusieurs plus petits

86

Après avoir réussi à convertir un référentiel SVN en Git, j'ai maintenant un très grand référentiel Git que je souhaite décomposer en plusieurs référentiels plus petits et maintenir l'historique.

Alors, quelqu'un peut-il aider à rompre un repo qui pourrait ressembler à ceci:

MyHugeRepo/
   .git/
   DIR_A/
   DIR_B/
   DIR_1/
   DIR_2/

Dans deux référentiels qui ressemblent à ceci:

MyABRepo/
   .git
   DIR_A/
   DIR_B/

My12Repo/
   .git
   DIR_1/
   DIR_2/

J'ai essayé de suivre les instructions de cette question précédente, mais cela ne convient pas vraiment lorsque vous essayez de placer plusieurs répertoires dans un dépôt séparé ( Détacher (déplacer) le sous-répertoire dans un dépôt Git séparé ).

MikeM
la source
11
Lorsque vous êtes satisfait d'une réponse, veuillez la marquer comme acceptée.
Ben Fowler
1
Pour ceux qui cherchent à diviser plusieurs répertoires (imbriqués) dans un nouveau dépôt (au lieu de chercher à supprimer plusieurs répertoires, ce qui pourrait être plus difficile sur certains projets), cette réponse m'a été utile: stackoverflow.com/a/19957874/164439
thaddeusmt

Réponses:

80

Cela configurera MyABRepo; vous pouvez bien sûr faire My12Repo de la même manière.

git clone MyHugeRepo/ MyABRepo.tmp/
cd MyABRepo.tmp
git filter-branch --prune-empty --index-filter 'git rm --cached --ignore-unmatch DIR_1/* DIR_2/*' HEAD 

Une référence à .git / refs / original / refs / heads / master reste. Vous pouvez supprimer cela avec:

cd ..
git clone MyABRepo.tmp MyABRepo

Si tout s'est bien passé, vous pouvez supprimer MyABRepo.tmp.


Si pour une raison quelconque vous obtenez une erreur concernant .git-rewrite, vous pouvez essayer ceci:

git clone MyHugeRepo/ MyABRepo.tmp/
cd MyABRepo.tmp
git filter-branch -d /tmp/git-rewrite.tmp --prune-empty --index-filter 'git rm --cached --ignore-unmatch DIR_1/* DIR_2/*' HEAD 
cd ..
git clone MyABRepo.tmp MyABRepo

Cela créera et utilisera /tmp/git-rewrite.tmp comme répertoire temporaire, au lieu de .git-rewrite. Naturellement, vous pouvez remplacer le chemin que vous souhaitez /tmp/git-rewrite.tmp, tant que vous disposez d'une autorisation d'écriture et que le répertoire n'existe pas déjà.

unutbu
la source
La page de manuel 'git filter-branch' recommande de créer un nouveau clone du référentiel réécrit au lieu de la dernière étape mentionnée ci-dessus.
Jakub Narębski
J'ai essayé cela et j'ai eu une erreur en essayant de supprimer le dossier .git-rewrite à la fin.
MikeM
-d <path-on-another-physical-disk> a fonctionné pour moi et a éliminé les échecs stange 'mv' dans --tree-filter.
Vertigo
Avez-vous une idée de la façon de sortir le tout premier commit, s'il est lié à un chemin exclu (comme DIR_A, par exemple)?
bitmask
1
Je n'avais pas réalisé toutes les ramifications de filter-branch. Pour ceux qui ne le savent pas, il réécrit l'historique, donc si vous prévoyez de pousser le dépôt après avoir fait cela, les hachages de validation seront différents maintenant et cela ne fonctionnera pas.
thaddeusmt
10

Vous pouvez utiliser git filter-branch --index-filteravec git rm --cachedpour supprimer les répertoires indésirables des clones / copies de votre référentiel d'origine.

Par exemple:

trim_repo() { : trim_repo src dst dir-to-trim-out...
  : uses printf %q: needs bash, zsh, or maybe ksh
  git clone "$1" "$2" &&
  (
    cd "$2" &&
    shift 2 &&

    : mirror original branches &&
    git checkout HEAD~0 2>/dev/null &&
    d=$(printf ' %q' "$@") &&
    git for-each-ref --shell --format='
      o=%(refname:short) b=${o#origin/} &&
      if test -n "$b" && test "$b" != HEAD; then 
        git branch --force --no-track "$b" "$o"
      fi
    ' refs/remotes/origin/ | sh -e &&
    git checkout - &&
    git remote rm origin &&

    : do the filtering &&
    git filter-branch \
      --index-filter 'git rm --ignore-unmatch --cached -r -- '"$d" \
      --tag-name-filter cat \
      --prune-empty \
      -- --all
  )
}
trim_repo MyHugeRepo MyABRepo DIR_1 DIR_2
trim_repo MyHugeRepo My12Repo DIR_A DIR_B

Vous devrez supprimer manuellement les branches ou balises inutiles de chaque référentiel (par exemple, si vous aviez une branche feature-x-for-AB , vous voudrez probablement la supprimer du référentiel «12»).

Chris Johnsen
la source
1
:n'est pas un caractère de commentaire dans bash. Vous devriez utiliser à la #place.
Daenyth
4
@Daenyth, :est une commande intégrée traditionnelle ( également spécifiée dans POSIX ). Il est inclus dans bash , mais ce n'est pas un commentaire. Je l'ai spécifiquement utilisé de préférence #parce que tous les shells ne prennent pas #comme introducteur de commentaires dans tous les contextes (par exemple, zsh interactif sans l'option INTERACTIVE_COMMENTS activée). L'utilisation :rend le texte entier approprié pour coller dans n'importe quel shell interactif ainsi que pour enregistrer dans un fichier de script.
Chris Johnsen
1
Brillant! La seule solution que j'ai trouvée qui garde toutes les branches intactes
pheelicks
Bizarre, pour moi ça s'arrête avec git remote rm origin, qui semble toujours revenir 1. J'ai donc remplacé le &&par ;pour cette ligne.
kynan
Bien, $ @ fonctionne pour plus de deux répertoires en cas de besoin. Quand j'ai fini, j'appelle git remote add origin $TARGET; git push origin master.
Walter A
6

Le projet git_split est un script simple qui fait exactement ce que vous recherchez. https://github.com/vangorra/git_split

Transformez les répertoires git en leurs propres dépôts à leur propre emplacement. Aucune entreprise drôle de sous-arbre. Ce script prendra un répertoire existant dans votre référentiel git et transformera ce répertoire en un référentiel indépendant à part entière. En cours de route, il copiera l'intégralité de l'historique des modifications pour le répertoire que vous avez fourni.

./git_split.sh <src_repo> <src_branch> <relative_dir_path> <dest_repo>
        src_repo  - The source repo to pull from.
        src_branch - The branch of the source repo to pull from. (usually master)
        relative_dir_path   - Relative path of the directory in the source repo to split.
        dest_repo - The repo to push to.
Vangorra
la source
1

Merci pour vos réponses, mais j'ai fini par copier le référentiel deux fois, puis en supprimant les fichiers que je ne voulais pas de chacun. Je vais utiliser la branche de filtre à une date ultérieure pour supprimer tous les commits pour les fichiers supprimés car ils sont déjà contrôlés en version ailleurs.

cp -R MyHugeRepo MyABRepo
cp -R MyHugeRepo My12Repo

cd MyABRepo/
rm -Rf DIR_1/ DIR_2/
git add -A
git commit -a

Cela a fonctionné pour ce dont j'avais besoin.

EDIT: Bien sûr, la même chose a été faite dans le My12Repo contre les répertoires A et B. Cela m'a donné deux dépôts avec un historique identique jusqu'au point où j'ai supprimé les répertoires indésirables.

MikeM
la source
1
Cela ne préserve pas l'historique des validations.
Daenyth
comment? J'ai toujours tout l'historique, même pour les fichiers supprimés.
MikeM
1
Puisque votre exigence n'était pas que le repo A doive prétendre que le repo B n'a jamais existé, je pense que cela (laisser un enregistrement des commits qui n'affecte que B) est une solution appropriée. Mieux vaut dupliquer un peu d'histoire que de le déformer.
Steve Clay