No meu caso, eu tinha um my-plugin
repositório e um main-project
repositório e queria fingir que my-plugin
sempre havia sido desenvolvido no plugins
subdiretório de main-project
.
Basicamente, reescrevi o histórico do my-plugin
repositório para que parecesse que todo o desenvolvimento ocorreu no plugins/my-plugin
subdiretório. Em seguida, adicionei o histórico de desenvolvimento my-plugin
ao main-project
histórico e mesclei as duas árvores. Como não havia plugins/my-plugin
diretório já presente no main-project
repositório, essa foi uma mesclagem trivial sem conflitos. O repositório resultante continha todo o histórico dos dois projetos originais e tinha duas raízes.
TL; DR
$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty
Versão longa
Primeiro, crie uma cópia do my-plugin
repositório, porque vamos reescrever o histórico desse repositório.
Agora, navegue até a raiz do my-plugin
repositório, verifique sua ramificação principal (provavelmente master
) e execute o seguinte comando. Obviamente, você deve substituir my-plugin
e plugins
quaisquer que sejam seus nomes reais.
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
Agora, para uma explicação. git filter-branch --tree-filter (...) HEAD
executa o (...)
comando em todas as confirmações acessíveis HEAD
. Observe que isso opera diretamente nos dados armazenados para cada confirmação, portanto, não precisamos nos preocupar com noções de "diretório de trabalho", "índice", "preparo" e assim por diante.
Se você executar um filter-branch
comando que falhar, ele deixará para trás alguns arquivos no .git
diretório e, na próxima vez em que tentar filter-branch
, reclamará disso, a menos que você forneça a -f
opção filter-branch
.
Quanto ao comando real, não tive muita sorte bash
em fazer o que queria, então, em vez disso, uso zsh -c
para zsh
executar um comando. Primeiro, defino a extended_glob
opção, que é o que habilita a ^(...)
sintaxe no mv
comando, bem como a glob_dots
opção, que permite selecionar arquivos de ponto (como .gitignore
) com um globo ( ^(...)
).
Em seguida, uso o mkdir -p
comando para criar os dois plugins
e plugins/my-plugin
ao mesmo tempo.
Por fim, uso o zsh
recurso "glob negativo" ^(.git|plugins)
para corresponder a todos os arquivos no diretório raiz do repositório, exceto .git
a my-plugin
pasta recém-criada . (A exclusão .git
pode não ser necessária aqui, mas tentar mover um diretório para si mesmo é um erro.)
No meu repositório, o commit inicial não incluía nenhum arquivo; portanto, o mv
comando retornou um erro no commit inicial (já que nada estava disponível para mover). Portanto, adicionei um || true
para que git filter-branch
não abortasse.
A --all
opção diz filter-branch
para reescrever o histórico de todas as ramificações no repositório, e o extra --
é necessário dizer git
para interpretá-la como parte da lista de opções para reescrever ramificações, em vez de ser uma opção para filter-branch
si mesma.
Agora, navegue até o seu main-project
repositório e verifique em qual filial você deseja mesclar. Adicione sua cópia local do my-plugin
repositório (com seu histórico modificado) como um controle remoto de main-project
com:
$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY
Agora você terá duas árvores não relacionadas em seu histórico de consolidação, que podem ser visualizadas com bom uso:
$ git log --color --graph --decorate --all
Para mesclá-los, use:
$ git merge my-plugin/master --allow-unrelated-histories
Observe que no Git anterior à 2.9.0, a --allow-unrelated-histories
opção não existe. Se você estiver usando uma dessas versões, apenas omita a opção: a mensagem de erro que --allow-unrelated-histories
impede também foi adicionada na 2.9.0.
Você não deve ter nenhum conflito de mesclagem. Se o fizer, provavelmente significa que o filter-branch
comando não funcionou corretamente ou já havia um plugins/my-plugin
diretório main-project
.
Certifique-se de inserir uma mensagem de confirmação explicativa para qualquer colaborador futuro, imaginando que hackery estava acontecendo para criar um repositório com duas raízes.
Você pode visualizar o novo gráfico de confirmação, que deve ter duas confirmações raiz, usando o git log
comando acima . Observe que apenas a master
ramificação será mesclada . Isso significa que, se você tiver um trabalho importante em outros my-plugin
ramos que deseja mesclar na main-project
árvore, evite excluir o my-plugin
controle remoto até fazer essas mesclagens. Caso contrário, as confirmações dessas ramificações ainda estarão no main-project
repositório, mas algumas estarão inacessíveis e suscetíveis a eventual coleta de lixo. (Além disso, você precisará consultá-los pelo SHA, porque a exclusão de um controle remoto remove suas ramificações de rastreamento remoto.)
Opcionalmente, depois de mesclar tudo o que você deseja impedir my-plugin
, você pode remover o my-plugin
controle remoto usando:
$ git remote remove my-plugin
Agora você pode excluir com segurança a cópia do my-plugin
repositório cujo histórico foi alterado. No meu caso, também adicionei um aviso de descontinuação ao my-plugin
repositório real depois que a mesclagem foi concluída e enviada por push.
Testado no Mac OS X El Capitan com git --version 2.9.0
e zsh --version 5.2
. Sua milhagem pode variar.
Referências: