O git-svn dcommit após a fusão no git é perigoso?

133

Minha motivação para experimentar o git-svn é a fusão e ramificação sem esforço. Então notei que o homem git-svn (1) diz:

Executar git-merge ou git-pull NÃO é recomendado em uma ramificação da qual você planeja se comprometer. O Subversion não representa fusões de maneira razoável ou útil; então os usuários que usam o Subversion não podem ver nenhuma fusão que você fez. Além disso, se você mesclar ou extrair de uma ramificação git que é um espelho de uma ramificação SVN, o dcommit poderá confirmar a ramificação errada.

Isso significa que não posso criar um ramo local a partir do svn / trunk (ou um ramo), cortar, mesclar novamente para o svn / trunk e, em seguida, dcommit? Eu entendo que os usuários do svn verão a mesma bagunça que se mescla no svn pré 1.5.x sempre foram, mas existem outras desvantagens? Essa última frase também me preocupa. As pessoas rotineiramente fazem esse tipo de coisa?

Knut Eldhuset
fonte
14
Isso está errado: o Subversion não representa fusões de maneira razoável ou útil; então os usuários que usam o Subversion não podem ver nenhuma fusão que você fez. Além disso, se você mesclar ou extrair de uma ramificação git que é um espelho de uma ramificação SVN, o dcommit poderá confirmar a ramificação errada. O Subversion pode representar não apenas informações de rastreamento de mesclagem do git, mas também muito mais informações detalhadas. É o git-svn que falha ao compor svn: mergeinfo adequado ou ao fazer o dcommit em um ramo apropriado.
Alexander Kitaev 02/09/11
2
@AlexanderKitaev: os documentos do git-svn mudaram desde então e, além disso, há uma --mergeinfo=<mergeinfo>opção que pode passar informações de mesclagem ao SVN. Não tenho certeza de como deve ser usado.
Mr_and_Mrs_D

Respostas:

174

Na verdade, encontrei uma maneira ainda melhor com a --no-ffopção git merge. Toda essa técnica de squash que usei antes não é mais necessária.

Meu novo fluxo de trabalho agora é o seguinte:

  • Eu tenho um ramo "mestre", que é o único ramo que eu dcommit partir e que clone do repositório SVN ( -sassumir que tem um layout SVN padrão no repositório trunk/, branches/e tags/):

    git svn clone [-s] <svn-url>
    
  • Eu trabalho em uma filial local "work" ( -bcria a filial "work")

    git checkout -b work
    
  • confirmar localmente na ramificação "trabalho" ( -spara assinar sua mensagem de confirmação). Na sequela, suponho que você fez três confirmações locais

    ...
    (work)$> git commit -s -m "msg 1"
    ...
    (work)$> git commit -s -m "msg 2"
    ...
    (work)$> git commit -s -m "msg 3"
    

Agora você deseja confirmar no servidor SVN

  • [Eventualmente] esconde as modificações que você não deseja que sejam confirmadas no servidor SVN (geralmente você comenta algum código no arquivo principal apenas porque deseja acelerar a compilação e se concentrar em um determinado recurso)

    (work)$> git stash
    
  • rebase a ramificação principal com o repositório SVN (para atualizar a partir do servidor SVN)

    (work)$> git checkout master
    (master)$> git svn rebase
    
  • volte para a ramificação do trabalho e faça uma nova refazer com o mestre

    (master)$> git checkout work
    (work)$> git rebase master
    
  • Verifique se está tudo bem usando, por exemplo:

    (work)$> git log --graph --oneline --decorate
    
  • Agora é hora de mesclar todos os três commits do ramo "work" no "master" usando esta --no-ffopção maravilhosa

    (work)$> git checkout master
    (master)$> git merge --no-ff work
    
  • Você pode observar o status dos logs:

    (master)$> git log --graph --oneline --decorate
    * 56a779b (work, master) Merge branch 'work'
    |\  
    | * af6f7ae msg 3
    | * 8750643 msg 2
    | * 08464ae msg 1
    |/  
    * 21e20fa (git-svn) last svn commit
    
  • Agora você provavelmente deseja editar ( amend) o último commit dos seus caras do SVN (caso contrário, eles verão apenas um único commit com a mensagem "Merge branch 'work'"

    (master)$> git commit --amend
    
  • Finalmente confirme no servidor SVN

    (master)$> git svn dcommit
    
  • Volte ao trabalho e, eventualmente, recupere seus arquivos escondidos:

    (master)$> git checkout work
    (work)$> git stash pop
    
Sebastien Varrette
fonte
4
Este é EXATAMENTE o fluxo de trabalho que este git n00b estava procurando. Crie uma ramificação local para corrigir um erro, faça o que for e envie-o para o svn com uma confirmação ÚNICA. Usar a resposta mais alta classificada aqui atrapalhou o que eu estava fazendo - não podia git svn rebase sem erros.
João Bragança
19
Não é exatamente isso que os documentos do git-svn citados na operação alertam contra? Ao executar a mesclagem com a --no-ffopção, você está criando explicitamente uma consolidação de mesclagem (uma consolidação com dois pais) em vez de um avanço rápido. Para garantir que todas as confirmações no ramo svn-tracking sejam confirmadas por pais solteiros, todas as fusões devem ser encaminhadas rapidamente ( --ff-onlypodem ajudar com isso) ou, se o tronco tiver sido alterado nas suas costas --squash, certo?
jemmons
4
Isso funciona para um ramo único que você exclui imediatamente, mas git svn dcommitreescreve o commit do git que você fornece. Isso significa que você perde o outro pai, e agora seu repositório Git não tem registro em que você mesclou esse ramo master. Isso também pode deixar seu diretório de trabalho em um estado inconsistente.
rescdsk
9
usar git merge --no-ff work -m "commit message"em vez de ter um extra git commit --amendpasso
tekumara
3
para mim, eu prefiro usar "merge git --ff somente trabalho" desde que eu quero preservar todos os meus commits e não apenas o último
Moataz Elmasry
49

Criar ramificações locais é definitivamente possível com o git-svn. Contanto que você esteja usando ramos locais por si mesmo e não tentando usar o git para mesclar entre os ramos svn upstream, você deve estar bem.

Eu tenho um ramo "master" que eu uso para rastrear o servidor svn. Este é o único ramo do qual eu comprometo. Se estou fazendo algum trabalho, crio um ramo de tópico e trabalho nele. Quando eu quero confirmar, eu faço o seguinte:

  1. Confirme tudo na ramificação do tópico
  2. git svn rebase (resolva quaisquer conflitos entre seu trabalho e svn)
  3. mestre de verificação geral do git
  4. git svn rebase (isso torna a próxima etapa uma mesclagem de avanço rápido, veja os comentários de Aaron abaixo)
  5. git mesclar topic_branch
  6. resolver quaisquer conflitos de mesclagem (não deve haver nenhum neste momento)
  7. git svn dcommit

Também tenho outra situação em que preciso manter algumas alterações locais (para depuração) que nunca devem ser enviadas para o svn. Para isso, tenho o ramo principal acima, mas também um ramo chamado "trabalho", onde normalmente trabalho. As ramificações de tópicos são ramificadas do trabalho. Quando quero confirmar um trabalho lá, faço checkout master e uso cherry-pick para selecionar os commit no ramo de trabalho que eu quero confirmar no svn. Isso ocorre porque eu quero evitar confirmar os três commits de alteração local. Então, eu saio do ramo principal e refiz tudo.

Vale a pena correr git svn dcommit -nprimeiro para garantir que você esteja prestes a confirmar exatamente o que pretende confirmar. Diferente do git, reescrever o histórico no svn é difícil!

Eu sinto que deve haver uma maneira melhor de mesclar a mudança em uma ramificação de tópico, ignorando esses commits de mudança local do que usar cherry-pick, portanto, se alguém tiver alguma idéia, será bem-vindo.

Greg Hewgill
fonte
Se eu entendi isso corretamente, no git, mesclar git com svn está OK, mas não svn com svn? Então não consigo mesclar meu svn / branch com svn / trunk no git?
Knut Eldhuset 10/10/08
1
Reescrever o histórico no SVN é difícil se você quiser fazer algo trivial. Em casos não triviais, é praticamente impossível.
Aristóteles Pagaltzis
19
A resposta de Greg Hewgill tem muitos votos, mas acredito que está errado. A fusão do topic_branch no master é segura apenas se for apenas uma mesclagem de avanço rápido. Se for uma mesclagem real exigindo uma consolidação de mesclagem, a consolidação de mesclagem será perdida quando você confirmar. Na próxima vez que você tentar mesclar topic_branch no master, o git acha que a primeira mesclagem nunca aconteceu e o inferno se abre. Veja a pergunta Por que o git svn dcommit perde o histórico de confirmações de mesclagem para ramificações locais?
Aaron
1
@ Greg Hewgill, a edição não está clara. Você escreveu que o rebase "faz do próximo commit uma mesclagem de avanço rápido". Não sei o que isso significa, porque uma mesclagem de avanço rápido não envolve uma confirmação, mas talvez você quisesse dizer "torna a próxima mesclagem uma mesclagem de avanço rápido". Mas se é isso que você quis dizer, não está correto. Se uma mesclagem de topic_branch no master é uma mesclagem de avanço rápido ou não, depende se alguma confirmação foi feita no master desde o ponto de ramificação. Quando você refaz o mestre, isso cria novas confirmações no mestre, portanto, uma mesclagem subsequente não é necessariamente uma mesclagem de avanço rápido.
Aaron
1
@ Aaron: Sim, eu não sei o que estava pensando lá. Corrigi-o novamente, espero que isso faça sentido. Um dos problemas é que existem tantas maneiras de fazer as coisas no Git, que são necessárias algumas explicações para descrever uma sequência específica de ações.
Greg Hewgill
33

Solução simples: remova a ramificação 'trabalho' após a mesclagem

Resposta curta: Você pode usar o git da maneira que desejar (veja abaixo um fluxo de trabalho simples), incluindo mesclagem. Apenas certifique-se de seguir cada ' trabalho de mesclagem do git ' com ' git branch -d work ' para excluir o ramo de trabalho temporário.

Explicação em segundo plano: O problema de mesclagem / dcommit é que, sempre que você 'git svn dcommit' uma ramificação, o histórico de mesclagem dessa ramificação é 'achatado': o git esquece todas as operações de mesclagem que entraram nessa ramificação: apenas o conteúdo do arquivo é preservado, mas o fato de que esse conteúdo (parcialmente) veio de um outro ramo específico está perdido. Veja: Por que o git svn dcommit perde o histórico de confirmações de mesclagem para ramificações locais?

(Nota: Não há muito o que o git-svn possa fazer a respeito: o svn simplesmente não entende as combinações muito mais poderosas do git. Portanto, dentro do repositório svn, essas informações de mesclagem não podem ser representadas de forma alguma.)

Mas este é o problema todo . Se você excluir o ramo 'work' depois que ele foi mesclado no 'master branch', seu repositório git estará 100% limpo e se parecerá exatamente com o seu repositório svn.

Meu fluxo de trabalho: Claro, primeiro clonei o repositório svn remoto em um repositório git local (isso pode levar algum tempo):

$> git svn clone <svn-repository-url> <local-directory>

Todo o trabalho acontece dentro do "diretório local". Sempre que preciso obter atualizações do servidor (como 'svn update'), eu faço:

$> git checkout master
$> git svn rebase

Eu faço todo o meu trabalho de desenvolvimento em uma ramificação separada 'work', criada assim:

$> git checkout -b work

Obviamente, você pode criar quantas ramificações para o seu trabalho quiser e mesclar e refazer entre elas quantas quiser (basta excluí-las quando terminar com elas - como discutido abaixo). No meu trabalho normal, comprometo-me com muita frequência:

$> git commit -am '-- finished a little piece of work'

O próximo passo (git rebase -i) é opcional --- está apenas limpando a história antes de arquivá-la no svn: Quando atingi uma milha estável que quero compartilhar com outras pessoas, reescrevi a história desse 'trabalho' ramifique e limpe as mensagens de confirmação (outros desenvolvedores não precisam ver todos os pequenos passos e erros que cometi no caminho - apenas o resultado). Para isso, eu faço

$> git log

e copie o hash sha-1 do último commit que está ativo no repositório svn (como indicado por um git-svn-id). Então eu ligo

$> git rebase -i 74e4068360e34b2ccf0c5869703af458cde0cdcb

Basta colar o sha-1 hash do nosso último svn commit ao invés do meu. Você pode ler a documentação com 'git help rebase' para obter detalhes. Resumindo: este comando primeiro abre um editor apresentando suas confirmações ---- basta alterar 'pick' para 'squash' para todas as confirmações que você deseja esmagar com as confirmações anteriores. Obviamente, a primeira linha deve permanecer como uma 'escolha'. Dessa forma, você pode condensar seus muitos pequenos commits em uma ou mais unidades significativas. Salve e saia do editor. Você receberá outro editor solicitando que você reescreva as mensagens de log de confirmação.

Resumindo: depois de terminar o 'hacker de código', massageio minha ramificação 'trabalho' até que pareça como quero apresentá-lo aos outros programadores (ou como quero ver o trabalho em algumas semanas quando navego pelo histórico) .

Para enviar as alterações ao repositório svn, eu faço:

$> git checkout master
$> git svn rebase

Agora estamos de volta ao antigo ramo 'mestre' atualizado com todas as alterações que ocorreram nesse período no repositório svn (suas novas alterações estão ocultas no ramo 'trabalho').

Se houver alterações que possam colidir com as novas alterações de 'trabalho', você precisará resolvê-las localmente antes de poder enviar seu novo trabalho (veja detalhes mais abaixo). Em seguida, podemos enviar nossas alterações para svn:

$> git checkout master
$> git merge work        # (1) merge your 'work' into 'master'
$> git branch -d work    # (2) remove the work branch immediately after merging
$> git svn dcommit       # (3) push your changes to the svn repository

Nota 1: O comando 'git branch -d work' é bastante seguro: permite excluir apenas os ramos que você não precisa mais (porque eles já estão mesclados no seu ramo atual). Se você executar este comando por engano antes de mesclar seu trabalho com a ramificação 'master', você receberá uma mensagem de erro.

Nota 2: Certifique-se de excluir sua ramificação com 'git branch -d work' entre mesclagem e dcommit: Se você tentar excluir a ramificação após dcommit, você receberá uma mensagem de erro: Ao fazer 'git svn dcommit', o git esquece que sua filial foi mesclada com 'master'. Você deve removê-lo com 'git branch -D work', que não faz a verificação de segurança.

Agora, crio imediatamente um novo ramo 'trabalho' para evitar hackers acidentalmente no ramo 'mestre':

$> git checkout -b work
$> git branch            # show my branches:
  master
* work

Integrando seu 'trabalho' com alterações no svn: Aqui está o que eu faço quando o 'git svn rebase' revela que outras pessoas mudaram o repositório svn enquanto eu trabalhava no meu ramo 'trabalho':

$> git checkout master
$> git svn rebase              # 'svn pull' changes
$> git checkout work           # go to my work
$> git checkout -b integration # make a copy of the branch
$> git merge master            # integrate my changes with theirs
$> ... check/fix/debug ...
$> ... rewrite history with rebase -i if needed

$> git checkout master         # try again to push my changes
$> git svn rebase              # hopefully no further changes to merge
$> git merge integration       # (1) merge your work with theirs
$> git branch -d work          # (2) remove branches that are merged
$> git branch -d integration   # (2) remove branches that are merged
$> git svn dcommit             # (3) push your changes to the svn repository

Existem soluções mais poderosas: O fluxo de trabalho apresentado é simplista: ele usa os poderes do git apenas dentro de cada rodada de 'update / hack / dcommit' --- mas deixa o histórico do projeto a longo prazo tão linear quanto o repositório svn. Tudo bem se você deseja apenas começar a usar o git mesclado em pequenos primeiros passos em um projeto svn legado.

Quando você se familiarizar com a fusão do git, sinta-se à vontade para explorar outros fluxos de trabalho: se você sabe o que está fazendo, pode misturar o git merges com o svn merges ( Usando o git-svn (ou similar) apenas para ajudar no svn merge? )

Yaakov Belch
fonte
Isso parece desnecessariamente complexo. Já ouvi falar de pessoas fazendo git merge --squash work, por que não fazer isso? Eu posso ver fazendo confirmações de squash na ramificação antes da mesclagem, se você estiver criando mais de uma 'escolha' (digamos que você tenha 8 confirmações e esteja transformando cada uma das 4 confirmações em 1 e mesclando 2 confirmações para master). Além disso, quando atualizando meu ramo 'trabalho', eu faço rebase, é mais simples do que criar um outro ramo para o meu ramo e fazer fusões ...
void.pointer
8

A resposta de Greg Hewgill no topo não é segura! Se algum novo commit aparecer no tronco entre os dois "git svn rebase", a mesclagem não será rápida.

Isso pode ser garantido usando o sinalizador "--ff-only" no git-merge, mas geralmente não executo o "git svn rebase" no ramo, apenas o "git rebase master" nele (supondo que seja apenas um local ramo). Depois, é garantido que um "git merge thebranch" avança rapidamente.

Marius K
fonte
6

Uma maneira segura de mesclar ramos svn no git é usar o git merge --squash. Isso criará uma única confirmação e parará para você adicionar uma mensagem.

Digamos que você tenha um tópico svn branch, chamado svn-branch.

git svn fetch
git checkout remotes/trunk -b big-merge
git merge --squash svn-branch

Neste ponto, você tem todas as alterações do svn-branch compactadas em um commit aguardando no índice

git commit
luntain
fonte
Como outros já apontaram, isso perde a granularidade dos commits.
Kzqai
@Tchalvak: Às vezes você quer exatamente isso. Tenho muitas vezes compromete em um ramo de funcionalidade ala "bagunça fixo de cometer anterior" e esses becos sem saída que gosto de esconder por causa de uma boa reputação :-)
schoetbi
1
Sim, embora eu considere isso uma caminhada na corda bamba, pois sempre que você esmaga, você também perde a capacidade de separar os compromissos individuais mais tarde. Apenas uma questão de diferentes opções de cumprimento de padrões.
Kzqai
1
@schoetbi sim, às vezes você precisa limpar o histórico bagunçado antes de publicar. É aqui que 'git rebase -i <trunk>' é uma grande ajuda: você pode ingressar / reordenar / remover e até dividir (!) Confirmações, corrigindo a mensagem seletivamente.
16402 inger
5

Rebase o ramo git local para o ramo mestre git e, em seguida, dcommit e, dessa forma, parece que você fez todos esses commits em sequência para que as pessoas svn possam vê-lo linearmente como estão acostumados. Supondo que você tenha um ramo local chamado topic, você poderia fazer

git rebase master topic

que executará seus commits sobre o ramo mestre, prontos para você confirmar

JoeyJ
fonte