A volte è effettivamente impossibile (con alcune eccezioni di dove potresti essere fortunato ad avere dati aggiuntivi) e le soluzioni qui non funzioneranno.
Git non conserva la cronologia degli ref (che include le filiali). Memorizza solo la posizione corrente per ciascun ramo (la testa). Questo significa che puoi perdere un po 'di storia del ramo in git nel tempo. Ogni volta che si ramifica, ad esempio, si perde immediatamente quale ramo era quello originale. Tutto un ramo fa è:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
Si potrebbe presumere che il primo a cui è stato eseguito il commit sia il ramo. Questo tende ad essere il caso, ma non è sempre così. Non c'è nulla che ti impedisca di impegnarti in una delle filiali prima dell'operazione sopra descritta. Inoltre, i timestamp di git non sono garantiti come affidabili. Non è fino a quando non ti impegni ad entrambi che diventano veramente rami strutturalmente.
Mentre nei diagrammi tendiamo a numerare i commit concettualmente, git non ha un vero concetto stabile di sequenza quando l'albero di commit si ramifica. In questo caso puoi supporre che i numeri (che indicano l'ordine) siano determinati dal timestamp (potrebbe essere divertente vedere come un'interfaccia utente git gestisce le cose quando imposti tutti i timestamp sullo stesso).
Questo è ciò che un umano si aspetta concettualmente:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
Questo è ciò che effettivamente ottieni:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
Supponeresti che B1 sia il ramo originale ma potrebbe essere semplicemente un ramo morto (qualcuno ha fatto il checkout -b ma non si è mai impegnato con esso). Non è fino a quando non ti impegni a ottenere una struttura di ramo legittima all'interno di git:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
Sai sempre che C1 è venuto prima di C2 e C3 ma non sai mai in modo affidabile se C2 è venuto prima di C3 o C3 prima di C2 (perché puoi impostare l'ora sulla tua workstation su qualsiasi cosa, ad esempio). Anche B1 e B2 sono fuorvianti in quanto non si può sapere quale ramo è arrivato per primo. Puoi fare un'ipotesi molto buona e di solito accurata in molti casi. È un po 'come una pista. A parità di tutte le cose, quindi, si può presumere che un'auto che arriva in un giro dietro abbia iniziato un giro dietro. Abbiamo anche convenzioni molto affidabili, ad esempio il maestro rappresenterà quasi sempre i rami più longevi, anche se purtroppo ho visto casi in cui anche questo non è il caso.
L'esempio fornito qui è un esempio di conservazione della storia:
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)
Il reale qui è anche fuorviante perché noi come umani lo leggiamo da sinistra a destra, da radice a foglia (rif). Git non lo fa. Dove facciamo (A-> B) nella nostra testa git fa (A <-B o B-> A). Lo legge da ref a root. I riferimenti possono essere ovunque ma tendono ad essere foglie, almeno per i rami attivi. Un ref indica un commit e contiene solo un like nei confronti dei loro genitori, non dei loro figli. Quando un commit è un commit di merge avrà più di un genitore. Il primo genitore è sempre il commit originale in cui è stato unito. Gli altri genitori sono sempre commit che sono stati uniti al commit originale.
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)))))
Questa non è una rappresentazione molto efficiente, piuttosto un'espressione di tutti i percorsi che git può prendere da ciascun riferimento (B1 e B2).
La memoria interna di Git è più simile a questa (non che A come genitore appare due volte):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
Se scarichi un commit git grezzo vedrai zero o più campi padre. Se ci sono zero, significa che nessun genitore e il commit è una radice (puoi effettivamente avere più radici). Se ce n'è uno, significa che non c'era unione e non è un commit root. Se ce n'è più di uno, significa che il commit è il risultato di una fusione e tutti i genitori dopo il primo sono commit di fusione.
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 entrambi colpiscono A la loro catena sarà la stessa, prima che la loro catena sarà completamente diversa. Il primo commesso che altri due hanno in comune è l'antenato comune e da cui divergevano. potrebbe esserci un po 'di confusione tra i termini commit, branch e ref. Puoi infatti unire un commit. Questo è ciò che fa davvero l'unione. Un ref indica semplicemente un commit e un ramo non è altro che un ref nella cartella .git / refs / heads, la posizione della cartella è ciò che determina che un ref è un ramo piuttosto che qualcos'altro come un tag.
Dove perdi la storia è che l'unione farà una delle due cose a seconda delle circostanze.
Prendere in considerazione:
/ - B (B1)
- A
\ - C (B2)
In questo caso, un'unione in entrambe le direzioni creerà un nuovo commit con il primo genitore come il commit indicato dal ramo estratto corrente e il secondo genitore come il commit sulla punta del ramo che hai unito nel ramo corrente. Deve creare un nuovo commit poiché entrambi i rami hanno dei cambiamenti dal loro antenato comune che devono essere combinati.
/ - B - D (B1)
- A /
\ --- C (B2)
A questo punto D (B1) ora ha entrambe le serie di modifiche da entrambi i rami (se stesso e B2). Tuttavia, il secondo ramo non ha le modifiche da B1. Se unisci le modifiche da B1 a B2 in modo che siano sincronizzate, potresti aspettarti qualcosa che assomigli a questo (puoi forzare git merge a farlo in questo modo comunque con --no-ff):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
Lo otterrai anche se B1 ha ulteriori commit. Finché non ci sono cambiamenti in B2 che B1 non ha, i due rami verranno uniti. Fa un avanzamento veloce che è come un rebase (anche i rebase mangiano o linearizzano la storia), tranne che a differenza di un rebase poiché solo un ramo ha un set di modifiche che non deve applicare un changeset da un ramo sopra quello da un altro.
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
Se smetti di lavorare su B1, allora le cose andranno benissimo per preservare la storia nel lungo periodo. Solamente B1 (che potrebbe essere il master) avanzerà in genere, quindi la posizione di B2 nella storia di B2 rappresenta con successo il punto in cui è stata fusa in B1. Questo è ciò che git si aspetta che tu faccia, ramificare B da A, quindi puoi unire A in B quanto desideri quando si accumulano le modifiche, tuttavia quando si fonde nuovamente B in A, non si prevede che lavorerai su B e oltre . Se continui a lavorare sul tuo ramo dopo averlo fatto avanzare rapidamente e lo hai reintegrato nel ramo su cui stavi lavorando, cancellerai ogni volta la storia precedente di B. Stai davvero creando un nuovo ramo ogni volta che esegui il commit di avanzamento rapido sull'origine, quindi esegui il commit sul ramo.
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
Da 1 a 3 e da 5 a 8 sono rami strutturali che si presentano se segui la storia per 4 o 9. Non c'è modo di sapere a quale di questi rami strutturali senza nome e senza riferimento appartengono i rami nominati e riferimenti come fine della struttura. Si potrebbe supporre da questo disegno che 0 a 4 appartiene a B1 e da 4 a 9 appartiene a B2 ma a parte 4 e 9 non è stato possibile sapere quale ramo appartiene a quale ramo, l'ho semplicemente disegnato in un modo che dà il illusione di quello. 0 potrebbe appartenere a B2 e 5 potrebbe appartenere a B1. Esistono 16 diverse possibilità in questo caso, di cui potrebbe appartenere ciascuno dei rami nominali.
Ci sono una serie di strategie git che aggirano questo. Puoi forzare git merge a non avanzare mai velocemente e creare sempre un ramo di unione. Un modo orribile per preservare la cronologia dei rami è con tag e / o rami (i tag sono davvero consigliati) secondo alcune convenzioni di tua scelta. Non consiglierei davvero un fittizio commit vuoto nel ramo in cui ti stai unendo. Una convenzione molto comune è quella di non fondersi in un ramo di integrazione fino a quando non si desidera chiudere realmente il proprio ramo. Questa è una pratica a cui le persone dovrebbero tentare di aderire poiché altrimenti si sta lavorando intorno al punto di avere rami. Tuttavia, nel mondo reale l'ideale non è sempre un significato pratico, fare la cosa giusta non è praticabile in ogni situazione. Se quello che tu '