2013-04-10 11 views
250

Git na documentation for the rebase command jest dość krótki (i dlaczego?):Co dokładnie robi git w "rebase --preserve-scala" zrobić

--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). 

Więc co tak naprawdę dzieje się podczas korzystania --preserve-merges? Czym różni się od domyślnego zachowania (bez tej flagi)? Co to znaczy "odtworzyć" scalenie, itp.

Odpowiedz

353

Podobnie jak w przypadku zwykłego git rebase, git z --preserve-merges najpierw identyfikuje listę commitów wykonanych w jednej części wykresu zatwierdzenia, a następnie odtwarza te zatwierdzenia na wierzchu kolejna część. Różnice z --preserve-merges dotyczą tego, które zatwierdzenia są wybierane do powtórzenia i jak te powtórki działają w przypadku zatwierdzeń scalania.

Aby być bardziej wyraźne o głównych różnic między normalnym i scalić-konserwowanie rebase:

  • Merge-konserwowanie rebase jest skłonny do odtworzenia (troche) seryjnej zobowiązuje, podczas gdy normalny rebase całkowicie ignoruje scalić zobowiązuje.
  • Bo jest gotów powtarzać seryjnej zobowiązuje, scalanie, zachowując rebase musi określić, co to oznacza odtworzyć scalanie popełnienia, oraz radzenia sobie z pewnym dodatkowym zmarszczki
    • najciekawsza część, koncepcyjnie, to być może w wybierając to, co powinni połączyć rodzice nowego zatwierdzenia.
    • Powtórne zatwierdzenia scalania wymagają również jawnego sprawdzenia poszczególnych zatwierdzeń (git checkout <desired first parent>), podczas gdy zwykły rebase nie musi się tym martwić.
  • Merge-konserwowanie rebase uważa się płytszy zestaw zobowiązuje do odsłuchania:
    • W szczególności, będzie brał pod uwagę jedynie Powtarzając zobowiązuje się od ostatniej podstawy (ów) seryjnej - czyli najnowszego czas, w którym dwie gałęzie rozdzieliły się -, podczas gdy normalny rebase może odtwarzać zatwierdzenia cofające się do najpierw, gdy dwie gałęzie rozdzieliły się.
    • To tymczasowe i niejasne, uważam, że jest to w końcu środek do sprawdzenia powtórzenia "starych zobowiązań", które zostały już "włączone do" scalenia.

Najpierw postaram się opisać „wystarczająco dokładnie” co rebase --preserve-merges robi, a potem będzie kilka przykładów. Można oczywiście zacząć od przykładów, jeśli wydaje się to bardziej przydatne.

Algorytm w „Brief”

Jeśli naprawdę chcesz dostać się chwastów, należy pobrać i zbadać źródło git plik git-rebase--interactive.sh. (Rebase nie jest częścią jądra Git's C, ale raczej jest zapisany w bashie, a za kulisami dzieli kod z "interakcyjną bazą danych".)

Ale tutaj opiszę, co według mnie jest esencją tego . Aby zmniejszyć liczbę spraw do przemyślenia, skorzystałem z kilku swobód. (np. nie próbuję uchwycić ze 100% dokładnością dokładnej kolejności, w jakiej odbywają się obliczenia, i ignorować pewne mniej centralne zagadnienia, np. co zrobić z zatwierdzeniami, które zostały już wybrane między gałęziami).

Po pierwsze, należy pamiętać, że niezbieranie scalania bazy jest raczej proste. Jest to bardziej lub mniej:

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 jest stosunkowo skomplikowane. Oto tak proste, jak udało mi się zrobić to bez utraty rzeczy, które wydają się dość ważne:

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 z --onto C argument powinien być bardzo podobny. Zamiast uruchamiać odtwarzanie zatwierdzenia na HEAD of B, zamiast tego rozpoczyna się odtwarzanie zatwierdzenia HEAD of C. (I korzystać C_new zamiast B_new).

Przykład 1

Na przykład, weźmy popełnienia wykres

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

m jest scalających z rodzicami E i G.

Załóżmy, odtworzyliśmy temat (H) na wierzchu wzorca (C), stosując normalną, niezawierającą do scalania podpórkę . (. Na przykład, tematu Zamówienie; przebazować pana) W tym przypadku, git wybierze następujące przesyła potwierdzenie do odtwarzania:

  • odbiór D
  • transfer e
  • odbiór M
  • odbiór G
  • odbiór H

a następnie aktualizuje popełnienia wykres tak:

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

(D”jest powtarzana równoważnik kwasu D, itp ..)

Należy zauważyć, że scalających m mogą być wybrane do odtwarzania.

Gdybyśmy zamiast zrobił --preserve-merges rebase H w górnej części C (. Na przykład, Zamówienie temat; rebase --preserve-scala opanować) W tej nowej sytuacji byłoby git wybrać następujące zobowiązuje do odsłuchania:

  • odbiór D
  • transfer e
  • odbiór M (na D 'w '' podtematu gałęzi)
  • odbiór G (na F' w '' podtematu gałęzi)
  • odebrać Merge oddział 'podrzędny' w temacie
  • pick H

Teraz jestem był wybrany do powtórki. Zwróć też uwagę, że rodzice E i G scaleni zostali wybrani do włączenia przed scaleniem commit m.

Tutaj otrzymany popełnienia wykresu:

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

Ponownie, D „jest wiśniowy zbierane (tj odtworzone) wersja D. Tak samo dla E”, itp .. W każdym nie popełnia na suwaku jest powtórzono. Zarówno E, jak i G (scaleni rodzice m) zostały odtworzone jako E 'i G', aby służyć jako rodzice m '(po ponownym założeniu historia drzew pozostaje taka sama).

Przykład 2

przeciwieństwie do normalnej rebase, połączenie zabezpieczonego przebazować mogą tworzyć wiele dzieci górnej głowicy.

Na przykład, należy rozważyć:

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

Jeśli przebazować H (powierzchniowo) na górnej części C (master), a następnie przesyła potwierdzenie wybrane dla rebase są:

  • odbiór D
  • wybierz E
  • wybierz F
  • wybierz G
  • pic km
  • odbiór H

, a wynik tak:

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

przykładu 3

W powyższych przykładach, zarówno scalających i jego dwa rodzice odtwarzane zobowiązuje , zamiast oryginalnych rodziców, które mają oryginalne scalenia. Jednak w innych rebase powtórne zatwierdzenie scalania może skończyć się z rodzicami, którzy byli już na wykresie zatwierdzenia przed scaleniem.

Rozważmy na przykład:

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

Gdybyśmy rebase wątek na mistrza (zachowując scala), wówczas zobowiązuje się do odtworzenia będzie

  • pick scalającej m
  • pick F

Przepisany wykres zatwierdzenia będzie wyglądał następująco:

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

tu powtórzony scalających m”dostaje rodziców, preegzystował na wykresie zatwierdzenia, to znaczy D (wybrany z Master) i E (jednego z rodziców pierwotnego scalających m).

Przykład 4

Merge-konserwowanie rebase mogą się mylić w pewnych "pustych commit" przypadkach. Przynajmniej tak jest tylko niektóre starsze wersje git (np. 1.7.8.)

Bierzcie popełnić wykres:

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

Należy zauważyć, że zarówno popełnić m1 i m2 powinien wprowadzać wszystkie zmiany od B i F.

Jeśli spróbujemy zrobić git rebase --preserve-merges H (temat) na D (master), a następnie dodaje się dopuszcza wybranych do odtwarzania:

  • odbiór m1
  • odbioru H

Należy zauważyć, że zmiany (B, F) zjednoczone w m1 powinny już zostać włączone do D. (Zmiany te powinny być już włączone do m2, ponieważ m2 łączy dzieci B i F.) , powtórzenie m1 na górze D powinno być albo no-op albo utworzyć puste zatwierdzenie (np jeden, w którym różnica między kolejnymi wersjami jest pusta).

Zamiast jednak git może odrzucić próbę powtórzenia m1 na szczycie D. Można się błąd tak:

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

To wygląda jak jeden zapomniał przekazać flagę do git, ale Podstawowym problemem jest to, że git nie lubi tworzyć pustych zatwierdzeń.

+3

Zauważyłem, że 'git rebase --preserve-merges' jest ** dużo ** wolniejsze niż' rebase' bez '--preserve-merges'. Czy jest to efekt uboczny znalezienia odpowiednich zatwierdzeń? Czy jest coś, co można zrobić, aby to przyspieszyć? (Przy okazji ... dziękuję za bardzo szczegółową odpowiedź!) –

+4

Wygląda na to, że powinieneś zawsze używać --preserve-merges. W przeciwnym razie istnieje możliwość utraty historii, np. Scalenie się zobowiązuje. – DarVar

+12

@DarVar Zawsze tracisz historię w bazie, ponieważ twierdzisz, że zmiany wprowadzono w innej bazie kodu niż w rzeczywistości. – Chronial