Divida um grande repositório Git em muitos menores

86

Depois de converter com sucesso um repositório SVN em Git, agora tenho um repositório Git muito grande que quero dividir em vários repositórios menores e manter o histórico.

Então, alguém pode ajudar a quebrar um repo que pode ter a seguinte aparência:

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

Em dois repositórios semelhantes a este:

MyABRepo/
   .git
   DIR_A/
   DIR_B/

My12Repo/
   .git
   DIR_1/
   DIR_2/

Eu tentei seguir as instruções nesta pergunta anterior, mas realmente não se encaixa ao tentar colocar vários diretórios em um repositório separado (subdiretório Detach (mover) em repositório Git separado ).

MikeM
fonte
11
Quando estiver satisfeito com uma resposta, marque-a como aceita.
Ben Fowler
1
Para quem deseja dividir vários diretórios (aninhados) em um novo repo (em vez de remover vários diretórios, o que pode ser mais difícil em alguns projetos), esta resposta foi útil para mim: stackoverflow.com/a/19957874/164439
thaddeusmt

Respostas:

80

Isso configurará o MyABRepo; você pode fazer o My12Repo da mesma forma, é claro.

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 

Uma referência a .git / refs / original / refs / heads / master permanece. Você pode remover isso com:

cd ..
git clone MyABRepo.tmp MyABRepo

Se tudo correr bem, você pode remover MyABRepo.tmp.


Se, por algum motivo, você receber um erro em relação a .git-rewrite, pode tentar o seguinte:

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

Isso criará e usará /tmp/git-rewrite.tmp como um diretório temporário, em vez de .git-rewrite. Naturalmente, você pode substituir qualquer caminho que desejar /tmp/git-rewrite.tmp, desde que tenha permissão de gravação e o diretório ainda não exista.

unutbu
fonte
A página de manual 'git filter-branch' recomenda a criação de um novo clone do repositório reescrito em vez do último passo mencionado acima.
Jakub Narębski
Tentei fazer isso e recebi um erro quando estava tentando excluir a pasta .git-rewrite no final.
MikeM de
-d <path-on-another-physical-disk> funcionou para mim e eliminou falhas 'mv' estranhas em --tree-filter.
Vertigo
Você tem uma ideia de como tirar o primeiro commit, se estiver relacionado a um caminho excluído (como DIR_A, por exemplo)?
bitmask
1
Eu não percebi todas as ramificações de filter-branch. Para quem não sabe, ele reescreve o histórico, portanto, se você planeja enviar o repo após ter feito isso, os hashes de commit serão diferentes agora e não funcionará.
thaddeusmt
10

Você pode usar git filter-branch --index-filtercom git rm --cachedpara excluir os diretórios indesejados de clones / cópias de seu repositório original.

Por exemplo:

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

Você precisará deletar manualmente os branches ou tags desnecessários de cada repositório (por exemplo, se você tinha um branch feature-x-for-AB , então provavelmente deseja excluí-lo do repositório “12”).

Chris Johnsen
fonte
1
:não é um caractere de comentário no bash. Você deve usar em seu #lugar.
Daenyth de
4
@Daenyth, :é um comando embutido tradicional ( também especificado no POSIX ). Ele está incluído no bash , mas não é um comentário. Eu o usei especificamente em preferência a #porque nem todos os shells levam #como introdutor de comentário em todos os contextos (por exemplo, zsh interativo sem a opção INTERACTIVE_COMMENTS habilitada). Usar :torna todo o texto adequado para colar em qualquer shell interativo, bem como salvar em um arquivo de script.
Chris Johnsen de
1
Brilhante! A única solução que encontrei que mantém todos os ramos intactos
pheelicks
Estranho, para mim termina com git remote rm origin, que sempre parece retornar 1. Portanto, substituí &&por por ;para esta linha.
kynan
Legal, $ @ funciona por mais de dois diretórios quando necessário. Quando terminar, ligo git remote add origin $TARGET; git push origin master.
Walter A
6

O projeto git_split é um script simples que faz exatamente o que você está procurando. https://github.com/vangorra/git_split

Transforme os diretórios git em seus próprios repositórios em seu próprio local. Sem subárvore negócios engraçados. Este script pegará um diretório existente em seu repositório git e o transformará em um repositório independente próprio. Ao longo do caminho, ele copiará todo o histórico de alterações do diretório fornecido.

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

Obrigado por suas respostas, mas acabei apenas copiando o repositório duas vezes e excluindo os arquivos que não queria de cada um. Vou usar o branch-filtro em uma data posterior para retirar todos os commits para os arquivos excluídos, uma vez que eles já têm versão controlada em outro lugar.

cp -R MyHugeRepo MyABRepo
cp -R MyHugeRepo My12Repo

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

Isso funcionou para o que eu precisava.

EDIT: Claro, a mesma coisa foi feita no My12Repo contra o diretório A e B. Isso me deu dois repositórios com histórico idêntico até o ponto em que excluí os diretórios indesejados.

MikeM
fonte
1
Isso não preserva o histórico de commits.
Daenyth de
como assim? Ainda tenho todo o histórico, mesmo para os arquivos excluídos.
MikeM de
1
Já que sua exigência não era que o repo A deve fingir que o repo B nunca existiu, eu acho que isso (deixando um registro de commits que apenas afetou B) é uma solução apropriada. Melhor duplicar um pouco da história do que destruí-la.
Steve Clay