Às vezes, é efetivamente impossível (com algumas exceções de onde você pode ter sorte de ter dados adicionais) e as soluções aqui não funcionam.
O Git não preserva o histórico de referências (que inclui ramificações). Ele armazena apenas a posição atual de cada filial (a cabeça). Isso significa que você pode perder algum histórico de ramificação no git ao longo do tempo. Sempre que você ramifica, por exemplo, perde-se imediatamente qual ramificação era a original. Tudo o que um ramo faz é:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Você pode supor que o primeiro comprometido seja o ramo. Isso costuma ser o caso, mas nem sempre é assim. Não há nada que impeça você de se comprometer com qualquer ramificação primeiro após a operação acima. Além disso, não é garantido que os carimbos de data e hora do git sejam confiáveis. Não é até você se comprometer com os dois que eles realmente se tornam ramificações estruturalmente.
Enquanto nos diagramas tendemos a numerar confirmações conceitualmente, o git não tem um conceito realmente estável de sequência quando a árvore de confirmação se ramifica. Nesse caso, você pode assumir que os números (indicando a ordem) são determinados pelo registro de data e hora (pode ser divertido ver como uma interface do usuário do git lida com as coisas quando você define todos os registros de data e hora para o mesmo).
Isto é o que um humano espera conceitualmente:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
Isto é o que você realmente obtém:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
Você assumiria que B1 é o ramo original, mas poderia ser simplesmente um ramo morto (alguém fez o checkout -b, mas nunca se comprometeu com ele). Não é até você se comprometer com os dois que você obtém uma estrutura de ramificação legítima no git:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
Você sempre sabe que C1 veio antes de C2 e C3, mas nunca sabe com segurança se C2 veio antes de C3 ou C3 antes de C2 (porque você pode definir o horário da estação de trabalho para qualquer coisa, por exemplo). B1 e B2 também são enganosos, pois você não pode saber qual ramificação veio primeiro. Você pode adivinhar muito bem e geralmente preciso em muitos casos. É um pouco como uma pista de corrida. Todas as coisas geralmente são iguais aos carros, então você pode assumir que um carro que vem atrás de uma volta começou uma volta atrás. Também temos convenções muito confiáveis, por exemplo, o mestre quase sempre representa os ramos mais antigos, embora, infelizmente, eu tenha visto casos em que mesmo esse não é o caso.
O exemplo fornecido aqui é um exemplo de preservação de histórico:
Human:
- X - A - B - C - D - F (B1)
\ / \ /
G - H ----- I - J (B2)
Real:
B ----- C - D - F (B1)
/ / \ /
- X - A / \ /
\ / \ /
G - H ----- I - J (B2)
O real aqui também é enganoso, porque nós, como humanos, o lemos da esquerda para a direita, da raiz à folha (ref). Git não faz isso. Onde fazemos (A-> B) em nossas cabeças, o git faz (A <-B ou B-> A). Ele lê de ref para root. As referências podem estar em qualquer lugar, mas tendem a ser folhas, pelo menos para ramos ativos. Um ref aponta para um commit e commit apenas contém um like para seus pais, não para seus filhos. Quando um commit é um commit de mesclagem, ele terá mais de um pai. O primeiro pai é sempre o commit original que foi mesclado. Os outros pais sempre são confirmados que foram mesclados no commit original.
Paths:
F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
Esta não é uma representação muito eficiente, mas uma expressão de todos os caminhos que o git pode seguir de cada referência (B1 e B2).
O armazenamento interno do Git se parece mais com isso (não que A como pai apareça duas vezes):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
Se você despejar um commit git bruto, verá zero ou mais campos pai. Se houver zero, significa que não há pai e o commit é uma raiz (você pode realmente ter várias raízes). Se houver um, significa que não houve mesclagem e não é um commit raiz. Se houver mais de um, significa que o commit é o resultado de uma mesclagem e todos os pais após o primeiro são mesclados.
Paths simplified:
F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
F->D->C->B->A | J->I->->G->A | A->X
Topological:
- X - A - B - C - D - F (B1)
\
G - H - I - J (B2)
Quando os dois atingem A, a cadeia será a mesma, antes disso a cadeia será totalmente diferente. O primeiro commit que outros dois commits têm em comum é o ancestral comum e de onde eles divergem. pode haver alguma confusão aqui entre os termos commit, branch e ref. Na verdade, você pode mesclar um commit. É isso que a mesclagem realmente faz. Uma ref simplesmente aponta para um commit e uma ramificação nada mais é que uma ref na pasta .git / refs / heads, o local da pasta é o que determina que uma ref é uma ramificação e não algo como uma tag.
Onde você perde a história é que a mesclagem fará uma de duas coisas, dependendo das circunstâncias.
Considerar:
/ - B (B1)
- A
\ - C (B2)
Nesse caso, uma mesclagem em qualquer direção criará um novo commit com o primeiro pai como o commit apontado pelo ramo atual com check-out e o segundo pai como o commit na ponta do ramo que você mesclou em seu ramo atual. Ele precisa criar um novo commit, pois os dois ramos têm alterações desde o ancestral comum que deve ser combinado.
/ - B - D (B1)
- A /
\ --- C (B2)
Neste ponto, D (B1) agora possui os dois conjuntos de alterações de ambos os ramos (ele mesmo e B2). No entanto, o segundo ramo não possui as alterações de B1. Se você mesclar as alterações de B1 em B2 para que elas sejam sincronizadas, você pode esperar algo parecido com isso (você pode forçar o git merge a fazer isso dessa maneira, porém com --no-ff):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
Você conseguirá isso mesmo que B1 tenha confirmações adicionais. Enquanto não houver alterações em B2 que B1 não possua, as duas ramificações serão mescladas. Ele faz um avanço rápido que é como uma rebase (as rebotes também comem ou linearizam o histórico), exceto ao contrário de uma rebase, pois apenas uma ramificação tem um conjunto de alterações, não é necessário aplicar um conjunto de alterações de uma ramificação em cima da outra.
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
Se você parar de trabalhar no B1, tudo ficará bem em preservar a história a longo prazo. Somente B1 (que pode ser mestre) avançará normalmente, portanto o local de B2 no histórico de B2 representa com êxito o ponto em que foi mesclado em B1. Isto é o que o git espera que você faça, para ramificar B de A e, em seguida, você pode mesclar A em B o quanto quiser enquanto as mudanças se acumulam, no entanto, ao mesclar B de volta a A, não é esperado que você trabalhe em B e mais . Se você continuar trabalhando em sua ramificação após avançar rapidamente, incorporando-a novamente à ramificação em que estava trabalhando, apagará o histórico anterior de B todas as vezes. Você está realmente criando uma nova ramificação toda vez que o avanço rápido é confirmado na origem e depois confirmado na ramificação.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
1 a 3 e 5 a 8 são ramificações estruturais que aparecem se você seguir o histórico de 4 ou 9. Não há como o git saber a qual desses ramificações estruturais sem nome e sem referência pertencem os ramos nomeados e de referência como o fim da estrutura. Você pode supor neste desenho que 0 a 4 pertence a B1 e 4 a 9 pertence a B2, mas, além de 4 e 9, não era possível saber qual ramo pertence a qual ramo, eu simplesmente o desenhei de uma maneira que ilusão disso. 0 pode pertencer a B2 e 5 pode pertencer a B1. Existem 16 possibilidades diferentes nesse caso, das quais ramificações nomeadas às quais cada uma das ramificações estruturais poderia pertencer.
Existem várias estratégias git que resolvem isso. Você pode forçar o git merge a nunca avançar rapidamente e sempre criar um branch de mesclagem. Uma maneira horrível de preservar o histórico de ramificações é com tags e / ou ramificações (as tags são realmente recomendadas), de acordo com algumas convenções de sua escolha. Eu realmente não recomendaria um commit vazio no ramo em que você está se fundindo. Uma convenção muito comum é não mesclar em uma ramificação de integração até que você queira realmente fechar sua ramificação. Essa é uma prática que as pessoas devem tentar aderir, caso contrário você está trabalhando em torno de ter ramificações. No entanto, no mundo real, o ideal nem sempre é um significado prático; fazer a coisa certa não é viável para todas as situações. Se o que você '