Quero compartilhar receitas para resolver alguns problemas que às vezes surgem ao trabalhar com o git, e que não são "diretamente óbvios".
No começo, pensei em acumular mais receitas desse tipo, mas tudo tem seu tempo. Eu acho que se houver algum benefício, então é possível e aos poucos ...
Então ...
Mesclar galhos antigos com dor mínima
Preâmbulo. Existe uma ramificação principal ( master
), que confirma ativamente novos recursos e correções; existe um ramo paralelo de feature
, no qual os desenvolvedores navegaram por um tempo em seu próprio nirvana e, de repente, descobriram que não estavam com o mestre há um mês e a fusão "na testa" (cabeça a cabeça) já não era trivial.
(Sim, não se trata de um mundo ideal, onde tudo está certo, não há crime, as crianças são sempre obedientes e até atravessam a estrada estritamente com a mãe com a mãe, olhando atentamente.)
Objetivo: ficar com raiva. Ao mesmo tempo, para que fosse uma mesclagem "pura", sem recursos. I.e. para que no repositório público no gráfico da ramificação, dois threads sejam conectados em um único ponto com a mensagem "ramificação mesclada 'master' no recurso". E toda essa dor de cabeça "essa" sobre quanto tempo e esforço foram necessários, quantos conflitos foram resolvidos e quanto cabelo foi deixado desnecessariamente manchado.
A trama. O fato de que no git você pode editar o último commit com a chave --amend
sabe. O truque é que esse "último commit" possa ser localizado em qualquer lugar e conter qualquer coisa. Por exemplo, pode ser não apenas o "último commit no ramo linear", onde eles esqueceram de corrigir o erro de digitação, mas também o commit de mesclagem da mesclagem usual ou "polvo". --amend
apenas rolará as alterações propostas e "incorporará" o commit alterado na árvore, como se ele realmente aparecesse como resultado de uma fusão e resolução honestas de conflitos. Em essência, o git merge
e o git commit --amend
permitem separar completamente o "local de colocação" ("este commit na árvore estará AQUI") e o conteúdo do próprio commit.
A idéia principal de uma confirmação de mesclagem complexa com um histórico limpo é simples: primeiro, “crie um local” criando uma confirmação de mesclagem limpa (independentemente do conteúdo) e depois reescreva-a com --amend
, tornando o conteúdo “correto”.
"Comprima um lugar." Isso é fácil, definindo uma estratégia de fusão que não fará perguntas desnecessárias sobre a resolução de conflitos.
git checkout feature git merge master -s ours
Ah sim. Antes da mesclagem, era necessário criar uma ramificação de "backup" a partir do cabeçalho do recurso. Afinal, nada é realmente mesclado ... Mas seja o segundo parágrafo, e não o 0. Em geral, passamos para o recurso não mesclado e agora mesclamos honestamente. De qualquer maneira possível, apesar de quaisquer "hacks sujos". Minha maneira pessoal é olhar para a ramificação principal a partir do momento da última mesclagem e avaliar possíveis comprometimentos (por exemplo: corrigir um erro de digitação em um lugar - não um problema. Massivamente (muitos arquivos) eles renomearam uma entidade - um problema etc.). A partir do commit do problema, criamos novas ramificações (eu faço engenhosamente - master1, master2, master3, etc.). E depois mesclamos ramo por ramo, passando de antigos para novos e corrigindo conflitos (que geralmente são evidentes com essa abordagem). Sugira outros métodos (não sou mágico; estou apenas aprendendo; ficarei feliz em fazer comentários construtivos!). Por fim, gastando (talvez) algumas horas em operações puramente rotineiras (que podem ser confiadas ao junior, porque simplesmente não há conflitos complicados com essa abordagem), obtemos o estado final do código: todas as inovações / correções do assistente foram portadas com sucesso para o ramo de recursos, todos os testes relevantes andou neste código, etc. Um código bem-sucedido deve ser confirmado.
Reescrevendo a "história de sucesso". Sendo no commit, em que "tudo é feito", execute o seguinte:
git tag mp git checkout mp git reset feature git checkout feature git tag -d mp
(Eu decifro: usando a tag (mp - merge point), alternamos para o estado HEAD desanexado e, a partir daí, redefini-lo para o chefe de nossa filial, onde, no início, uma "piquetagem" era feita por um commit de mesclagem fraudulento. A tag não é mais necessária, então a excluímos). Agora estamos no commit de mesclagem "pura" original; ao mesmo tempo, na cópia de trabalho, temos os arquivos "corretos", onde tudo o que você precisa é encontrado. Agora você precisa adicionar todos os arquivos alterados ao índice e, especialmente, observar com cuidado os não-estágios (haverá todos os novos arquivos que apareceram na ramificação principal). Adicionamos tudo o que precisamos a partir daí também.
Finalmente, quando tudo estiver pronto, inserimos nosso commit correto no local reservado:
git commit --amend
Viva! Tudo deu certo! Você pode enviar ocasionalmente uma ramificação para um repositório público e ninguém saberá que você realmente passou meio dia de trabalho nessa mesclagem.
Upd: maneira mais concisa
Três meses após esta publicação, o artigo " Como e por que roubar árvores no git " por capslocky
Com base nos motivos dela, é possível alcançar exatamente o mesmo objetivo de maneira mais curta e sem mecanismos auxiliares: não há necessidade de "postar espaço", considere arquivos não-estágios após a redefinição e faça as alterações; Você pode criar uma consolidação direta de mesclagem com o conteúdo desejado em uma etapa.
Começamos imediatamente com a fusão usando qualquer método disponível (como no parágrafo 2 acima). A história intermediária e hacks ainda são irrelevantes. E então, em vez da reivindicação 3, com a substituição do commit de mesclagem, fazemos uma mesclagem artificial , como no artigo:
git tag mp git checkout feature git merge --ff $(git commit-tree mp^{tree} -m "merged branch 'master' into 'feature'" -p feature -p master) git tag -d mp
Toda a mágica aqui é feita em uma etapa pelo terceiro comando (git commit-tree).
Selecione parte do arquivo, mantendo o histórico
Preâmbulo: o arquivo foi codificado e finalmente codificado, de modo que até o estúdio visual começou a desacelerar, digerindo-o (para não mencionar o JetBrains). (Sim, estamos de volta ao mundo "imperfeito". Como sempre).
Os cérebros inteligentes pensaram, pensaram e destacaram várias entidades que podem ser renderizadas em um arquivo separado. Mas! Se você pegar, copie e cole um pedaço do arquivo e cole em outro - este será um arquivo completamente novo do ponto de vista do git. Em caso de problemas, uma pesquisa no histórico indicará inequivocamente apenas “onde está essa pessoa com deficiência?” Quem compartilhou o arquivo. E pode ser necessário encontrar a fonte original de modo algum “para repressões”, mas puramente construtivamente - para descobrir por que essa linha foi alterada; que bug corrigiu (ou não corrigiu nenhum). Quero que o arquivo seja novo, mas ao mesmo tempo todo o histórico de alterações ainda permanece!
A trama. Com alguns efeitos de borda levemente irritantes, isso pode ser feito. Por definição, existe um arquivo file.txt
, do qual desejo destacar uma parte em file2.txt
. (e ainda mantém a história, sim). Execute este trecho:
f=file.txt; f1=file1.txt; f2=file2.txt cp $f $f2 git add $f2 git mv $f $f1 git commit -m"split $f step 1, converted to $f1 and $f2"
Como resultado, obtemos os arquivos file1.txt
e file2.txt
. Ambos têm exatamente a mesma história (real; como o arquivo original). Sim, o file.txt
original teve que ser renomeado; esse é o efeito de borda "levemente irritante". Infelizmente, não consegui encontrar uma maneira de salvar a história, mas para NÃO renomear o arquivo de origem (se alguém puder, me diga!). No entanto, o git suportará tudo; agora ninguém se preocupa em renomear o arquivo novamente em uma confirmação separada:
git mv $f1 $f git commit -m"split finish, rename $f1 to $f"
Agora file2.txt
dourada mostrará o mesmo histórico de linha que o arquivo original. O principal é não mesclar esses dois commits juntos (caso contrário, toda a magia desaparecerá; eu tentei!). Mas, ao mesmo tempo, ninguém se preocupa em editar arquivos diretamente no processo de separação; não é necessário fazer isso posteriormente com confirmações separadas. E sim, você pode selecionar muitos arquivos de uma vez!
O ponto principal da receita: renomeie o arquivo de origem para outro no mesmo commit, onde é feita (e possivelmente editada) uma cópia (cópias). E deixe esse commit viver no futuro (nunca cometerá um erro com a renomeação reversa).
Upd: um par de receitas de Lissov
Separe a parte do repositório de histórico
Você está na versão mais recente do repositório inicial. A tarefa é separar uma pasta. Vi opções para várias pastas, mas é mais fácil e compreensível dobrar tudo em uma ou repetir as seguintes várias vezes.
Importante! Todos os movimentos devem ser feitos com o git mv
, caso contrário, o git poderá perder o histórico.
Realizamos:
git filter-branch --prune-empty --subdirectory-filter "{directory}" [branch]
{directory} é a pasta a ser separada. Como resultado, obtemos a pasta junto com o histórico completo de confirmações apenas para ela, ou seja, em cada confirmação, apenas os arquivos dessa pasta são exibidos. Naturalmente, parte dos commits estarão vazios, --prune-empty os remove.
Agora mude a origem:
git remote set-url origin {another_repository_url}` git checkout move_from_Repo_1
Se o segundo repositório estiver limpo, você poderá ir diretamente para o master. Bem, empurre:
git push -u move_from_Repo_1
Fragmento inteiro (para copiar e colar fácil):
directory="directory_to_extract"; newurl="another_repository_url" git filter-branch --prune-empty --subdirectory-filter "$directory" git remote set-url origin "$newurl" git checkout move_from_Repo_1 git push -u move_from_Repo_1
Mesclar dois repositórios juntos
Suponha que você tenha feito algo duas vezes mais alto e tenha brunches move_from_Repo_1
e move_from_Repo_2
, e em cada um você tenha transferido arquivos usando git mv
para onde eles deveriam estar após a mesclagem. Agora resta controlar:
br1="move_from_Repo_1"; br2="move_from_Repo_2" git checkout master git merge origin/$br1 --allow-unrelated-histories git merge origin/$br2 --allow-unrelated-histories git push
Todo o truque está em histórias permitidas e não relacionadas. Como resultado, obtemos um repositório com um histórico completo de todas as alterações.