Dividi un grande repository Git in molti più piccoli


86

Dopo aver convertito con successo un repository SVN in Git, ora ho un repository Git molto grande che voglio suddividere in più repository più piccoli e mantenere la cronologia.

Quindi, qualcuno può aiutare a rompere un repo che potrebbe assomigliare a questo:

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

In due repository che assomigliano a questo:

MyABRepo/
   .git
   DIR_A/
   DIR_B/

My12Repo/
   .git
   DIR_1/
   DIR_2/

Ho provato a seguire le indicazioni in questa domanda precedente, ma non si adatta perfettamente quando si tenta di inserire più directory in un repository separato ( sottodirectory Detach (spostare) in repository Git separato ).


11
Quando sei soddisfatto di una risposta, contrassegnala come accettata.
Ben Fowler

1
Per chiunque cerchi di suddividere più directory (nidificate) in un nuovo repository (invece di cercare di rimuovere più directory, che potrebbe essere più difficile su alcuni progetti), questa risposta è stata utile per me: stackoverflow.com/a/19957874/164439
thaddeusmt

Risposte:


80

Questo configurerà MyABRepo; puoi fare My12Repo allo stesso modo ovviamente.

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 

Rimane un riferimento a .git / refs / original / refs / heads / master. Puoi rimuoverlo con:

cd ..
git clone MyABRepo.tmp MyABRepo

Se tutto è andato bene, puoi rimuovere MyABRepo.tmp.


Se per qualche motivo ricevi un errore relativo a .git-rewrite, puoi provare questo:

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

Questo creerà e utilizzerà /tmp/git-rewrite.tmp come directory temporanea, invece di .git-rewrite. Naturalmente, puoi sostituire qualsiasi percorso desideri invece di /tmp/git-rewrite.tmp, purché tu abbia il permesso di scrittura e la directory non esista già.


La manpage 'git filter-branch' consiglia di creare un nuovo clone del repository riscritto invece dell'ultimo passaggio menzionato sopra.
Jakub Narębski

Ho provato questo e ho ricevuto un errore durante il tentativo di eliminare la cartella .git-rewrite alla fine.
MikeM

-d <path-on-another-physical-disk> ha funzionato per me ed ha eliminato strani errori 'mv' all'interno di --tree-filter.
Vertigo

Hai un'idea di come ottenere il primo commit, se è correlato a un percorso escluso (come DIR_A, ad esempio)?
maschera di bit

1
Non mi ero reso conto delle implicazioni complete di filter-branch. Per coloro che non lo sanno, riscrive la cronologia, quindi se prevedi di eseguire il push del repository dopo averlo fatto, gli hash di commit saranno diversi ora e non funzionerà.
thaddeusmt

10

È possibile utilizzare git filter-branch --index-filtercon git rm --cachedper eliminare le directory indesiderate da cloni / copie del repository originale.

Per esempio:

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

Sarà necessario eliminare manualmente i rami o i tag non necessari di ciascun repository (ad esempio, se si dispone di un ramo feature-x-for-AB , probabilmente si desidera eliminarlo dal repository "12").


1
:non è un carattere di commento in bash. Dovresti usare #invece.
Daenyth

4
@Daenyth, :è un tradizionale comando integrato ( specificato anche in POSIX ). È incluso in bash , ma non è un commento. L'ho usato specificatamente in preferenza #perché non tutte le shell prendono #come introduttore di commenti in tutti i contesti (ad esempio zsh interattivo senza l'opzione INTERACTIVE_COMMENTS abilitata). L'utilizzo :rende l'intero testo adatto per essere incollato in qualsiasi shell interattiva e per salvarlo in un file di script.
Chris Johnsen,

1
Brillante! L'unica soluzione che ho trovato che mantiene intatti tutti i rami
pheelicks

Strano, per me si ferma con git remote rm origin, che sembra sempre tornare 1. Quindi ho sostituito il &&con ;per questa linea.
kynan

Bello, $ @ funziona per più di due directory quando necessario. Quando ho finito chiamo git remote add origin $TARGET; git push origin master.
Walter A

6

Il progetto git_split è un semplice script che fa esattamente quello che stai cercando. https://github.com/vangorra/git_split

Trasforma le directory git nei loro repository nella loro posizione. Nessuna attività divertente da sottoalbero. Questo script prenderà una directory esistente nel tuo repository git e trasformerà quella directory in un proprio repository indipendente. Lungo il percorso, copierà l'intera cronologia delle modifiche per la directory che hai fornito.

./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.


1

Grazie per le tue risposte, ma ho finito per copiare il repository due volte e poi eliminare i file che non volevo da ciascuno. Userò il ramo del filtro in un secondo momento per rimuovere tutti i commit per i file eliminati poiché sono già controllati dalla versione altrove.

cp -R MyHugeRepo MyABRepo
cp -R MyHugeRepo My12Repo

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

Questo ha funzionato per quello di cui avevo bisogno.

EDIT: Ovviamente, la stessa cosa è stata fatta in My12Repo contro la directory A e B. Questo mi ha dato due repository con cronologia identica fino al punto in cui ho cancellato le directory indesiderate.


1
Ciò non preserva la cronologia dei commit.
Daenyth

come mai? Ho ancora tutta la cronologia, anche per i file eliminati.
MikeM

1
Poiché il tuo requisito non era che il repo A deve fingere che il repo B non sia mai esistito, penso che questa (lasciando un record di commit che hanno interessato solo B) sia una soluzione appropriata. Meglio duplicare un po 'di storia che manipolarla.
Steve Clay
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.