Como posso mover um único diretório de um repositório git para um novo repositório, mantendo o histórico?

110

Eu herdei um repositório git contendo vários projetos em diretórios separados. Gostaria de dividir o repositório em novos repositórios individuais, um para cada projeto e, em seguida, fazer com que o repositório principal contenha os projetos como submódulos. Eu gostaria de fazer tudo isso enquanto mantenho o histórico de revisão dos projetos individuais, se possível.

Eu poderia clonar o repositório para cada projeto e remover todos os outros projetos a cada vez, mas existe uma maneira melhor de evitar ter o histórico clonado em cada novo repositório de projeto?

cidra
fonte
2
Adicionada a tag git-submodules, já que é muito útil para converter parte de um repo em um submódulo.
idbrii 01 de
Possível duplicata da subárvore Exportar no Git com histórico
usuário

Respostas:

99

Você pode usar git filter-branchpara reescrever a história de um projeto. Da documentação:

Para reescrever o repositório para parecer como se foodir / tivesse sido a raiz do projeto e descartar todas as outras histórias:

git filter-branch --subdirectory-filter foodir -- --all

Faça várias cópias de seu repo, faça isso para cada subdiretório que deseja dividir e você deve terminar com o que está procurando.

Brian Campbell
fonte
1
E se eu quisesse excluir foodir e remover todo o seu histórico?
saeedgnu
1
Observação: se você quiser vários diretórios, precisará especificar --subdirectory-filtervárias vezes. EG git filter-branch --subdirectory-filter foodir --subdirectory-filter bardir, etc. --subdirectorynão leva vários diretórios, mas pode ser especificado várias vezes.
EnabrenTane
3
@Adam Se você quiser manter a história de foodir projeto original, não reescrever seu histórico, apenas git rm -r foodirdeve ser suficiente (isso também excluirá a cópia da sua árvore de trabalho; se você não quiser, use --cached). Se você quiser removê-lo inteiramente do histórico (respondendo à pergunta de @ilius também), você quer algo comogit filter-branch --index-filter 'git rm -r --cached --ignore-unmatched foodir' -- --all
Brian Campbell
2
@ilius Desculpe, não percebi sua pergunta antes, veja minha resposta acima para saber como remover um diretório e seu histórico.
Brian Campbell
2
@ilius Sim, isso também funciona. Para um projeto grande, a --index-filtersolução é mais rápida do que o --tree-filter, como não precisa realmente fazer o check-out dos arquivos, ele pode apenas manipular o índice diretamente. No --tree-filterentanto, isso pode ser um pouco mais fácil de usar, já que você pode usar operações comuns do sistema de arquivos em vez de ter que trabalhar com operações de manipulação de índice.
Brian Campbell
4

Para exportar uma pasta como um novo repositório, você precisa:

  1. Para clonar o repositório onde está a pasta que você deseja exportar.
  2. Para criar um repositório vazio em seu provedor de hospedagem como GitHub, para armazenar a pasta exportada.
  3. Abra a pasta do repositório clonado e execute este comando:

    git subtree push --prefix=YourFolderNameToExport https://github.com/YourUserName/YourNewCleanRepoName master
    
do utilizador
fonte
1
git subtreenão está disponível como pacote Cygwin. Se você precisar: stackoverflow.com/a/27116828/2484903
Jack Miller
2

O ponto do git é que o histórico é incorporado em cada commit por hash no commit pai. Você pode "repetir" os commits (é essencialmente assim que o svn-importer funciona) em um novo repositório e apenas mantendo cada subprojeto. Isso, no entanto, destruiria o significado dos hashes de confirmação. Se você não tem nenhum problema com isso, então que seja.

No passado, eu apenas clonava e seguia em frente. Isso torna as coisas maiores, mas o espaço em disco é barato; meu tempo é caro.

Também não conheço nenhuma ferramenta para dividir um diretório. Suponho que você poderia fazer o git-log no diretório para encontrar todos os commits nele e depois reproduzir os commits com algo como git-fast-export?

Colin Burnett
fonte
Votei positivamente porque não merecia ser negativo - certamente não seguiria essa abordagem, mas posso ver que ele estava tentando
Alvin