O que exatamente faz a “rebase - preservar e mesclar” do git (e por quê?)

355

A documentaçãorebase do Git para o comando é bastante breve:

--preserve-merges
    Instead of ignoring merges, try to recreate them.

This uses the --interactive machinery internally, but combining it
with the --interactive option explicitly is generally not a good idea
unless you know what you are doing (see BUGS below).

Então, o que realmente acontece quando você usa --preserve-merges? Como ele difere do comportamento padrão (sem esse sinalizador)? O que significa "recriar" uma mesclagem etc.

Chris
fonte
20
Aviso: a partir do Git 2.18 (segundo trimestre de 2018, 5 anos depois), git --rebase-mergessubstituirá o antigo git --preserve-merges. Veja minha resposta abaixo
VonC

Respostas:

464

Assim como em uma rebase normal do git, o git --preserve-mergesprimeiro identifica uma lista de confirmações feitas em uma parte do gráfico de confirmação e, em seguida, as replica novamente na parte superior de outra parte. As diferenças com --preserve-mergesrelação a quais confirmações são selecionadas para reprodução e como essa reprodução funciona para mesclagem.

Para ser mais explícito sobre as principais diferenças entre o rebase normal e a preservação de mesclagem:

  • A rebase de preservação de mesclagem está disposta a reproduzir (algumas) as confirmações de mesclagem, enquanto a rebase normal ignora completamente as confirmações de mesclagem.
  • Como ele está disposto a reproduzir confirmações de mesclagem, o rebase de preservação de mesclagem precisa definir o que significa reproduzir uma confirmação de mesclagem e lidar com algumas rugas extras
    • A parte mais interessante, conceitualmente, talvez seja escolher o que os pais de mesclagem do novo commit devem ser.
    • A reprodução de confirmações de mesclagem também exige a verificação explícita de confirmações específicas ( git checkout <desired first parent>), enquanto a rebase normal não precisa se preocupar com isso.
  • A rebase de preservação de mesclagem considera um conjunto menor de confirmações para reprodução:
    • Em particular, ele considerará apenas a repetição de confirmações feitas desde a (s) base (s) mais recente (s) de mesclagem - ou seja, o momento mais recente em que as duas ramificações divergiram -, enquanto a rebase normal pode repetir as confirmações desde a primeira vez que as duas ramificações divergiram.
    • Para ser provisório e pouco claro, acredito que esse é, em última análise, um meio de filtrar a reprodução de "antigos commit" que já foram "incorporados" a um commit de mesclagem.

Primeiro, tentarei descrever "suficientemente exatamente" o que a rebase --preserve-mergesfaz e, em seguida, haverá alguns exemplos. É claro que se pode começar com os exemplos, se isso parecer mais útil.

O algoritmo em "Breve"

Se você realmente deseja conhecer as ervas daninhas, baixe a fonte git e explore o arquivo git-rebase--interactive.sh. (O rebase não faz parte do núcleo C do Git, mas é escrito no bash. E, nos bastidores, ele compartilha código com o "rebase interativo".)

Mas aqui vou esboçar o que acho que é a essência disso. Para reduzir o número de coisas em que pensar, tomei algumas liberdades. (por exemplo, não tento capturar com 100% de precisão a ordem exata em que os cálculos ocorrem e ignorar alguns tópicos menos centrais, como o que fazer com confirmações que já foram escolhidas entre ramificações).

Primeiro, observe que um rebase sem preservação de mesclagem é bastante simples. É mais ou menos:

Find all commits on B but not on A ("git log A..B")
Reset B to A ("git reset --hard A") 
Replay all those commits onto B one at a time in order.

Rebase --preserve-mergesé comparativamente complicado. Aqui está o mais simples que consegui sem perder coisas que parecem muito importantes:

Find the commits to replay:
  First find the merge-base(s) of A and B (i.e. the most recent common ancestor(s))
    This (these) merge base(s) will serve as a root/boundary for the rebase.
    In particular, we'll take its (their) descendants and replay them on top of new parents
  Now we can define C, the set of commits to replay. In particular, it's those commits:
    1) reachable from B but not A (as in a normal rebase), and ALSO
    2) descendants of the merge base(s)
  If we ignore cherry-picks and other cleverness preserve-merges does, it's more or less:
    git log A..B --not $(git merge-base --all A B)
Replay the commits:
  Create a branch B_new, on which to replay our commits.
  Switch to B_new (i.e. "git checkout B_new")
  Proceeding parents-before-children (--topo-order), replay each commit c in C on top of B_new:
    If it's a non-merge commit, cherry-pick as usual (i.e. "git cherry-pick c")
    Otherwise it's a merge commit, and we'll construct an "equivalent" merge commit c':
      To create a merge commit, its parents must exist and we must know what they are.
      So first, figure out which parents to use for c', by reference to the parents of c:
        For each parent p_i in parents_of(c):
          If p_i is one of the merge bases mentioned above:
            # p_i is one of the "boundary commits" that we no longer want to use as parents
            For the new commit's ith parent (p_i'), use the HEAD of B_new.
          Else if p_i is one of the commits being rewritten (i.e. if p_i is in R):
            # Note: Because we're moving parents-before-children, a rewritten version
            # of p_i must already exist. So reuse it:
            For the new commit's ith parent (p_i'), use the rewritten version of p_i.
          Otherwise:
            # p_i is one of the commits that's *not* slated for rewrite. So don't rewrite it
            For the new commit's ith parent (p_i'), use p_i, i.e. the old commit's ith parent.
      Second, actually create the new commit c':
        Go to p_1'. (i.e. "git checkout p_1'", p_1' being the "first parent" we want for our new commit)
        Merge in the other parent(s):
          For a typical two-parent merge, it's just "git merge p_2'".
          For an octopus merge, it's "git merge p_2' p_3' p_4' ...".
        Switch (i.e. "git reset") B_new to the current commit (i.e. HEAD), if it's not already there
  Change the label B to apply to this new branch, rather than the old one. (i.e. "git reset --hard B")

Rebase com um --onto Cargumento deve ser muito semelhante. Em vez de iniciar a reprodução confirmada no HEAD de B, você inicia a reprodução confirmada no HEAD de C. (E use C_new em vez de B_new.)

Exemplo 1

Por exemplo, considere o gráfico de confirmação

  B---C <-- master
 /                     
A-------D------E----m----H <-- topic
         \         /
          F-------G

m é um commit de mesclagem com os pais E e G.

Suponhamos que refizemos o tópico (H) sobre o mestre (C) usando uma base rebase normal, sem preservação de mesclagem. (Por exemplo, tópico de check-out; rebase master .) Nesse caso, o git selecionaria os seguintes commits para reprodução:

  • escolha D
  • escolha E
  • escolha F
  • escolha G
  • escolha H

e atualize o gráfico de confirmação da seguinte maneira:

  B---C <-- master
 /     \                
A       D'---E'---F'---G'---H' <-- topic

(D 'é o equivalente repetido de D, etc.)

Observe que merge commit m não está selecionado para reprodução.

Se, em vez disso, --preserve-mergesredefinirmos H em cima de C. (Por exemplo, tópico de checkout; rebase --preserve-mescla master .) Nesse novo caso, o git selecionaria os seguintes commits para reprodução:

  • escolha D
  • escolha E
  • escolha F (em D 'no ramo' subtópico ')
  • escolha G (em F 'no ramo' subtópico ')
  • selecione Mesclar ramo 'subtópico' no tópico
  • escolha H

Agora m foi escolhido para repetição. Observe também que os pais de mesclagem E e G foram escolhidos para inclusão antes de se comprometerem m.

Aqui está o gráfico de confirmação resultante:

 B---C <-- master
/     \                
A      D'-----E'----m'----H' <-- topic
        \          / 
         F'-------G'

Novamente, D 'é uma versão escolhida de cereja (ou seja, recriada) de D. O mesmo para E', etc. Todos os commit que não estão no master foram reproduzidos. Tanto E como G (os pais mesclados de m) foram recriados como E 'e G' para servir como pais de m '(após a reformulação, a história da árvore ainda permanece a mesma).

Exemplo 2

Ao contrário do rebase normal, o rebase de preservação de mesclagem pode criar vários filhos da cabeça upstream.

Por exemplo, considere:

  B---C <-- master
 /                     
A-------D------E---m----H <-- topic
 \                 |
  ------- F-----G--/ 

Se refazermos o H (tópico) sobre C (mestre), os commits escolhidos para o rebase serão:

  • escolha D
  • escolha E
  • escolha F
  • escolha G
  • escolha m
  • escolha H

E o resultado é assim:

  B---C  <-- master
 /    | \                
A     |  D'----E'---m'----H' <-- topic
       \            |
         F'----G'---/

Exemplo 3

Nos exemplos acima, o commit de mesclagem e seus dois pais são confirmados de repetição, em vez dos pais originais que o commit de mesclagem original possui. No entanto, em outras refazer uma consolidação de mesclagem repetida pode acabar com os pais que já estavam no gráfico de consolidação antes da mesclagem.

Por exemplo, considere:

  B--C---D <-- master
 /    \                
A---E--m------F <-- topic

Se restabelecermos o tópico no mestre (preservar mesclagens), as confirmações para reprodução serão

  • escolha mesclar commit m
  • escolha F

O gráfico de confirmação reescrito terá a seguinte aparência:

                     B--C--D <-- master
                    /       \             
                   A-----E---m'--F'; <-- topic

Aqui, o commit de mesclagem reproduzido m 'obtém os pais que já existiam no gráfico de commit, ou seja, D (a CABEÇA do mestre) e E (um dos pais do commit de mesclagem original m).

Exemplo 4

A rebase de preservação de mesclagem pode ficar confusa em certos casos de "confirmação vazia". Pelo menos isso é verdade apenas em algumas versões mais antigas do git (por exemplo, 1.7.8.)

Veja este gráfico de confirmação:

                   A--------B-----C-----m2---D <-- master
                    \        \         /
                      E--- F--\--G----/
                            \  \
                             ---m1--H <--topic

Observe que ambos os commit m1 e m2 devem ter incorporado todas as alterações de B e F.

Se tentarmos fazer git rebase --preserve-mergesde H (tópico) em D (mestre), os seguintes commits serão escolhidos para reprodução:

  • escolha m1
  • escolha H

Observe que as mudanças (B, F) unidas em m1 já devem ser incorporadas em D. (Essas mudanças já devem ser incorporadas em m2, porque m2 mescla os filhos de B e F.) Portanto, conceitualmente, reproduzindo m1 em cima de D provavelmente deve ser um no-op ou criar uma confirmação vazia (ou seja, onde a diferença entre revisões sucessivas está vazia).

Em vez disso, no entanto, o git pode rejeitar a tentativa de repetir m1 em cima de D. Você pode receber um erro como este:

error: Commit 90caf85 is a merge but no -m option was given.
fatal: cherry-pick failed

Parece que alguém esqueceu de passar uma bandeira para o git, mas o problema subjacente é que o git não gosta de criar confirmações vazias.

Chris
fonte
6
Notei que git rebase --preserve-mergesé muito mais lento que um rebasesem --preserve-merges. Esse é um efeito colateral de encontrar as confirmações corretas? Existe algo que se possa fazer para acelerar isso? (By the way ... obrigado pela resposta muito detalhada!)
David Alan Hjelle
7
Parece que você sempre deve usar --preserve-mescla. Caso contrário, há o potencial de perder histórico, ou seja, a mesclagem é confirmada.
DarVar 4/13/13
19
@DarVar Você sempre perde o histórico em uma nova base, porque alega que as alterações foram feitas em uma base de código diferente da atual.
Cronial 4/13/13
5
Ainda é uma "resposta provisória"?
Andrew Grimm
5
@ Chronial É claro que você está certo, que o rebasing sempre incorpora a história perdida, mas talvez a DarVar estivesse aludindo ao fato de que você não apenas perde a história, mas também muda a base de código. A resolução de conflitos contém informações que são perdidas de todas as maneiras possíveis em que uma nova refazer pode ser feita. Você sempre tem que refazê-lo. Realmente não há como deixar o git refazer sua resolução de conflitos? Por que o git não pode escolher o commit de mesclagem?
N15_M
94

O Git 2.18 (Q2 2018) melhorará consideravelmente a --preserve-mergeopção adicionando uma nova opção.

" git rebase" aprendi " --rebase-merges" a transplantar toda a topologia do gráfico de confirmação em outro lugar .

(Observação: o Git 2.22, segundo trimestre de 2019, na verdade é preterido --preserve-merge , e o Git 2.25, primeiro trimestre de 2020, para de anunciar o resultado " git rebase --help" )

Veja cometer 25cff9f , cometer 7543f6f , cometer 1131ec9 , cometer 7ccdf65 , cometer 537e7d6 , cometer a9be29c , cometer 8f6aed7 , cometer 1644c73 , cometer d1e8b01 , cometer 4c68e7d , cometer 9055e40 , cometer cb5206e , cometer a01c2a5 , cometer 2f6b1d1 , cometer bf5c057 (25 de abril de 2018) de Johannes Schindelin ( dscho) .
Veja commit f431d73 (25 Abr 2018) por Stefan Beller ( stefanbeller) .
Veja commit 2429335 (25 de abril de 2018) por Phillip Wood ( phillipwood) .
(Mesclado por Junio ​​C Hamano - gitster- na confirmação 2c18e6a , 23 de maio de 2018)

pull: aceite --rebase-mergesrecriar a topologia da ramificação

Semelhante ao preservemodo simplesmente passando a --preserve-merges opção para o rebasecomando, o mergesmodo simplesmente passa a --rebase-mergesopção.

Isso permitirá que os usuários reorganizem convenientemente topologias de confirmação não triviais ao obter novas confirmações, sem achatá-las.


git rebaseAgora, a página de manual tem uma seção completa dedicada a refazer o histórico com mesclagens .

Extrair:

Existem razões legítimas pelas quais um desenvolvedor pode querer recriar confirmações de mesclagem: para manter a estrutura da ramificação (ou "topologia de confirmação") ao trabalhar em várias ramificações inter-relacionadas.

No exemplo a seguir, o desenvolvedor trabalha em uma ramificação de tópico que refatora a maneira como os botões são definidos e em outra ramificação de tópico que usa essa refatoração para implementar um botão "Relatar um bug".
A saída de git log --graph --format=%s -5pode ser assim:

*   Merge branch 'report-a-bug'
|\
| * Add the feedback button
* | Merge branch 'refactor-button'
|\ \
| |/
| * Use the Button class for all buttons
| * Extract a generic Button class from the DownloadButton one

O desenvolvedor pode querer reestruturar essas confirmações para uma mais recente master , mantendo a topologia da ramificação, por exemplo, quando se espera que a primeira ramificação do tópico seja integrada mastermuito antes do segundo, por exemplo, para resolver conflitos de mesclagem com alterações na DownloadButtonclasse que foi feita em master.

Essa rebase pode ser realizada usando a --rebase-mergesopção


Consulte commit 1644c73 para um pequeno exemplo:

rebase-helper --make-script: introduza um sinalizador para refazer a mesclagem

O sequenciador acabou de aprender novos comandos destinados a recriar a estrutura de ramificações ( semelhante em espírito --preserve-merges, mas com um design substancialmente menos quebrado ).

Vamos permitir a rebase--helpergeração de listas de tarefas utilizando esses comandos, acionados pela nova --rebase-mergesopção.
Para uma topologia de consolidação como esta (em que o HEAD aponta para C):

- A - B - C (HEAD)
    \   /
      D

a lista de tarefas gerada ficaria assim:

# branch D
pick 0123 A
label branch-point
pick 1234 D
label D

reset branch-point
pick 2345 B
merge -C 3456 D # C

Qual é a diferença --preserve-merge?
O compromisso 8f6aed7 explica:

Era uma vez, este desenvolvedor aqui pensou: não seria bom se, digamos, os patches do Git for Windows no topo do núcleo, o Git pudessem ser representados como um emaranhado de ramificações e serem reestruturados no topo do núcleo do Git para manter um conjunto cereja-pick'able de série de patches?

A tentativa original para responder a esta era: git rebase --preserve-merges.

No entanto, esse experimento nunca teve a intenção de ser uma opção interativa, e apenas foi suportado git rebase --interactiveporque a implementação desse comando já parecia muito, muito familiar: foi projetada pela mesma pessoa que projetou --preserve-merges: a sua verdadeiramente.

E por "você realmente", o autor se refere a: Johannes Schindelin ( dscho) , que é o principal motivo (com alguns outros heróis - Hannes, Steffen, Sebastian, ...) de que temos o Git For Windows (embora de volta ao dia - 2009 - isso não foi fácil ).
Ele trabalha na Microsoft desde setembro de 2015 , o que faz sentido, considerando que a Microsoft agora usa fortemente o Git e precisa de seus serviços.
Essa tendência começou em 2013, na verdade, com o TFS . Desde então, a Microsoft gerencia o maior repositório Git do planeta ! E, desde outubro de 2018, a Microsoft adquiriu o GitHub .

Você pode ver Johannes falando neste vídeo para o Git Merge 2018 em abril de 2018.

Algum tempo depois, algum outro desenvolvedor (estou olhando para você, Andreas! ;-)) decidiu que seria uma boa ideia permitir --preserve-mergescombinar com --interactive(com advertências!) E o mantenedor do Git (bem, o mantenedor interino do Git durante a ausência de Junio, isto é) concordou, e foi aí que o glamour do --preserve-mergesdesign começou a desmoronar rapidamente e sem glamour .

Aqui Jonathan está falando sobre Andreas Schwab, de Suse.
Você pode ver algumas das discussões deles em 2012 .

O motivo? No --preserve-mergesmodo, os pais de uma consolidação de mesclagem (ou, de qualquer forma, de qualquer consolidação) não foram declarados explicitamente, mas estavam implícitos no nome da consolidação passada para o pickcomando .

Isso tornou impossível, por exemplo, reordenar confirmações .
Sem mencionar a mudança de commits entre ramificações ou, deidade proibida, dividir ramificações de tópicos em duas.

Infelizmente, essas deficiências também impediram que o modo (cujo objetivo original era atender às necessidades do Git for Windows, com a esperança adicional de que também possa ser útil para outras pessoas) de atender às necessidades do Git for Windows.

Cinco anos depois, quando tornou-se realmente insustentável ter uma série de patches complicados e complicados de patches parcialmente relacionados e parcialmente não relacionados no Git for Windows, que eram reposicionados nas tags principais do Git de tempos em tempos (ganhando a ira imerecida do desenvolvedor) da git-remote-hgsérie malfadada que obsoleta a primeira abordagem concorrente do Git for Windows, para depois ser abandonada sem mantenedor mais tarde) era realmente insustentável, nasceram as " tesouras de jardinagem Git " : um script, que acompanhava o rebase interativo, que determinaria primeiro a topologia de ramificação dos patches a serem rebaseados, criaria uma lista de pseudo-tarefas para edição adicional, transformaria o resultado em uma lista de tarefas real (fazendo uso pesado doexec comando para "implementar" os comandos da lista de tarefas ausentes) e, finalmente, recriar a série de patches sobre a nova confirmação de base.

(O script Git garden shears é mencionado neste patch no commit 9055e40 )

Isso foi em 2013.
E demorou cerca de três semanas para criar o design e implementá-lo como um script fora da árvore. Desnecessário dizer que a implementação precisou de alguns anos para estabilizar, enquanto o design em si se mostrava sólido.

Com este patch, a bondade das tesouras de jardim Git trata de git rebase -isi .
Passar a --rebase-mergesopção gerará uma lista de tarefas que pode ser facilmente entendida e onde é óbvio como reorganizar as confirmações .
Novas ramificações podem ser introduzidas inserindo labelcomandos e chamando merge <label>.
E assim que esse modo se tornar estável e aceito universalmente, podemos descartar o erro de design que ocorreu--preserve-merges .


O Git 2.19 (terceiro trimestre de 2018) aprimora a nova --rebase-mergesopção, fazendo com que ela funcione --exec.

A --execopção " " para " git rebase --rebase-merges" colocou os comandos exec em locais errados, que foram corrigidos.

Consulte commit 1ace63b (09 de agosto de 2018) e commit f0880f7 (06 de agosto de 2018) por Johannes Schindelin ( dscho) .
(Mesclado por Junio ​​C Hamano - gitster- in commit 750eb11 , 20 de agosto de 2018)

rebase --exec: faça funcionar com --rebase-merges

A idéia de --execé anexar uma execchamada após cada uma pick.

Desde a introdução do fixup!/ s quash!commits, essa idéia foi estendida para aplicar-se a "pick, possivelmente seguido de uma cadeia de correção / squash", ou seja, um executivo não seria inserido entre a picke qualquer uma de suas linhas fixupou correspondentes squash.

A implementação atual usa um truque sujo para conseguir isso: assume que há apenas comandos pick / fixup / squash e, em seguida, insere as execlinhas antes de qualquer um, pickexceto o primeiro, e anexa um final.

Com as listas de tarefas geradas por git rebase --rebase-merges, essa implementação simples mostra seus problemas: produz exatamente a coisa errada quando existem label, resete mergecomandos.

Vamos mudar a implementação para fazer exatamente o que queremos: procure picklinhas, pule qualquer cadeia de correção / squash e insira a exec linha . Espuma, enxágüe, repita.

Nota: nos esforçamos para inserir antes das linhas de comentário sempre que possível, pois confirmações vazias são representadas por linhas de seleção comentadas (e queremos inserir a linha executiva de uma seleção anterior antes dessa linha, e não depois).

Enquanto isso, adicione também execlinhas após mergecomandos, porque eles são semelhantes em espírito aos pickcomandos: eles adicionam novos commits.


O Git 2.22 (Q2 2019) corrige o uso de refs / reescrito / hierarquia para armazenar estados intermediários de rebase, o que faz inerentemente a hierarquia por árvore de trabalho.

Consulte commit b9317d5 , commit 90d31ff , commit 09e6564 ( 07/03/2019 ) por Nguyễn Thái Ngọc Duy ( pclouds) .
(Mesclado por Junio ​​C Hamano - gitster- in commit 917f2cd , 09 abr 2019)

Verifique se refs / reescrito / é por árvore de trabalho

O a9be29c (sequenciador: make refs gerado pelo labelcomando worktree-local, 25/04/2018, Git 2.19) é adicionado refs/rewritten/como espaço de referência por worktree.
Infelizmente (infelizmente), existem alguns lugares que precisam de atualização para garantir que seja realmente por área de trabalho.

- add_per_worktree_entries_to_dir()é atualizado para garantir que a lista de referências analise por árvore de trabalho em refs/rewritten/vez de por árvore de repo.

  • common_list[]é atualizado para que git_path()retorne o local correto. Isso inclui " rev-parse --git-path".

Essa bagunça é criada por mim.
Comecei a tentar consertá-lo com a introdução de refs/worktree,onde todos os árbitros serão por área de trabalho sem tratamentos especiais.
Refs / reescritos infelizes vieram antes de refs / worktree, portanto é tudo o que podemos fazer.


Com o Git 2.24 (quarto trimestre de 2019), " git rebase --rebase-merges" aprendemos a conduzir diferentes estratégias de mesclagem e a passar opções específicas da estratégia para elas.

Veja commit 476998d (04 Set 2019) por Elijah Newren ( newren) .
Consulte confirmar e1fac53 , confirmar a63f990 , confirmar 5dcdd74 , confirmar e145d99 , confirmar 4e6023b , cometer f67336d , cometer a9c7107 , cometer b8c6f24 , cometer d51b771 , cometer c248d32 , cometer 8c1e240 , cometer 5efed0e , cometer 68b54f6 , cometer 2e7bbac , cometer 6180b20 , cometer d5b581f (31 Jul 2019) porJohannes Schindelin ( dscho) .
(Mesclado por Junio ​​C Hamano - gitster- no commit 917a319 , 18 de setembro de 2019)


Com o Git 2.25 (primeiro trimestre de 2020), a lógica usada para diferenciar as referências locais e do repositório global da árvore de trabalho é corrigida, para facilitar a mesclagem de preservar.

Consulte commit f45f88b , commit c72fc40 , commit 8a64881 , commit 7cb8c92 , commit e536b1f (21 de outubro de 2019) por SZEDER Gábor ( szeder) .
(Incorporado por Junio ​​C Hamano - gitster- in commit db806d7 , 10 nov 2019)

path.c: não chame a matchfunção sem valor emtrie_find()

Assinado por: SZEDER Gábor

'logs / refs' não é um caminho específico da árvore de trabalho, mas desde que o commit b9317d55a3 (verifique se refs / reescrito / é por worktree, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path' está retornando um caminho falso se um final ' /' estiver presente:

$ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
/home/szeder/src/git/.git/logs/refs
/home/szeder/src/git/.git/worktrees/WT/logs/refs/

Usamos uma trieestrutura de dados para decidir com eficiência se um caminho pertence ao diretório comum ou se está trabalhando em uma árvore específica.

Como acontece b9317d55a3 acionou um bug que é tão antigo quanto a trieprópria implementação, adicionado em 4e09cf2acf (" path: otimizar verificação comum de diretórios", 31/08/2015, Git v2.7.0-rc0 - mesclagem listada no lote # 2 ).

  • De acordo com o comentário que descreve trie_find(), ele deve chamar apenas a função de correspondência fornecida 'fn' para um prefixo "/ -ou- \ 0 terminado da chave para a qual o trie contém um valor".
    Isso não é verdade: há três lugares em que trie_find () chama a função match, mas um deles está faltando na verificação da existência do valor.

  • b9317d55a3 adicionou duas novas chaves ao trie:

    • ' logs/refs/rewritten' e
    • ' logs/refs/worktree', ao lado do já existente ' logs/refs/bisect'.
      Isso resultou em um trienó com o caminho 'logs/refs/ ', que não existia antes e que não tinha um valor anexado.
      Uma consulta para ' logs/refs/' localiza esse nó e, em seguida, acessa o local de chamada da matchfunção que não verifica a existência do valor e, portanto, chama a matchfunção com NULLcomo valor.
  • Quando a matchfunção check_common()é invocada com um NULLvalor, ela retorna 0, o que indica que o caminho consultado não pertence ao diretório comum, resultando no final do caminho falso mostrado acima.

Adicione a condição ausente a, trie_find()para que nunca invoque a função de correspondência com um valor inexistente.

check_common() não precisará mais verificar se obteve um valor diferente de NULL; portanto, remova essa condição.

Acredito que não existem outros caminhos que possam causar uma saída falsa semelhante.

AFAICT, a única outra chave que resulta na chamada da função de correspondência com um NULLvalor é ' co' (por causa das chaves 'common ' e ' config').

No entanto, como eles não estão em um diretório que pertence ao diretório comum, é esperado o caminho específico da árvore de trabalho resultante.

VonC
fonte
3
Eu acho que essa deve ser a resposta principal, --preserve-mergesna verdade não "preserva" as mesclagens como você deseja, é muito ingênua. Isso permite preservar as confirmações de mesclagem e os relacionamentos de confirmação dos pais, além de oferecer a flexibilidade de uma rebase interativa. Esse novo recurso é incrível e, se não fosse por essa resposta bem escrita, eu não saberia!
egucciar
@egucciar Obrigado. E esse não é o único recurso do Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ) e do Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% 22 )
VonC 31/08/19
11
Extremamente útil se você está tentando mover pedaços de commits em torno de como neste Q / A, stackoverflow.com/questions/45059039/...
okovko
11
Ah, isso é realmente o que eu procurava há algum tempo! Eu tinha uma solução alternativa manual para casos como aquele em que se deveria criar um commit fictício que unisse todas as mesclagens.
quer
Git típico. Você se atreve a fazer uma pergunta simples e provavelmente terá que aprender a história do Git, algoritmos internos, todos os detalhes confusos da implementação, além de precisar também de uma teoria teórica dos grafos para entender o que está acontecendo.
Dimitris