Co dokładnie robi „rebase - zachowaj-scala” gita (i dlaczego?)


355

Dokumentacjarebase Gita dla tego polecenia jest dość krótka:

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

Co się dzieje, kiedy używasz --preserve-merges? Czym różni się od domyślnego zachowania (bez tej flagi)? Co to znaczy „odtworzyć” scalenie itp.


20
Ostrzeżenie: począwszy od Git 2.18 (drugi kwartał 2018 r., 5 lat później) git --rebase-mergesostatecznie zastąpi stary git --preserve-merges. Zobacz moją odpowiedź poniżej
VonC

Odpowiedzi:


464

Podobnie jak w przypadku zwykłego git rebase, git z --preserve-mergesnajpierw identyfikuje listę zatwierdzeń wykonanych w jednej części wykresu zatwierdzeń, a następnie odtwarza te zatwierdzenia na innej części. Różnice dotyczące --preserve-mergesobaw, które zatwierdzenia są wybierane do powtórki, oraz sposób, w jaki to powtórzenie działa w przypadku zatwierdzeń scalania.

Aby być bardziej precyzyjnym na temat głównych różnic między normalnym a zachowującym scalanie rebase:

  • Baza zachowująca scalanie jest gotowa odtwarzać (niektóre) zatwierdzenia scalania, podczas gdy normalny rebase całkowicie ignoruje zatwierdzenia scalania.
  • Ponieważ jest skłonny odtwarzać zatwierdzenia scalania, rebase zachowujący scalanie musi zdefiniować, co to znaczy odtworzyć zatwierdzenie scalania i radzić sobie z dodatkowymi zmarszczkami
    • Najciekawsze, pod względem koncepcyjnym, jest być może wybranie, czym powinni być rodzice łączący nowego zatwierdzenia.
    • Odtwarzanie zatwierdzeń scalania wymaga również jawnego sprawdzenia poszczególnych zatwierdzeń ( git checkout <desired first parent>), podczas gdy normalny rebase nie musi się o to martwić.
  • Baza zachowująca scalanie uwzględnia płytszy zestaw zatwierdzeń do odtworzenia:
    • W szczególności rozważy tylko odtwarzanie zatwierdzeń dokonanych od czasu ostatniej bazy (baz) scalania - tj. Ostatniego czasu, kiedy dwie gałęzie się rozdzieliły - podczas gdy normalny rebase może odtwarzać zatwierdzenia od momentu pierwszego rozejścia się dwóch gałęzi.
    • Mówiąc prowizorycznie i niejasno, uważam, że ostatecznie jest to sposób na wyeliminowanie odtwarzania „starych zmian”, które zostały już „włączone” do zatwierdzenia scalania.

Najpierw spróbuję opisać „wystarczająco dokładnie”, co --preserve-mergesrobi baza , a potem będzie kilka przykładów. Oczywiście można zacząć od przykładów, jeśli wydaje się to bardziej przydatne.

Algorytm w skrócie

Jeśli naprawdę chcesz dostać się do chwastów, pobierz źródło git i przejrzyj plik git-rebase--interactive.sh. (Rebase nie jest częścią jądra Git, ale jest napisane bash. A poza tym dzieli kod z „interaktywnym rebase”).

Ale tutaj naszkicuję to, co według mnie jest jego esencją. Aby zmniejszyć liczbę rzeczy do przemyślenia, podjąłem kilka swobód. (np. nie staram się uchwycić ze 100% dokładnością dokładnej kolejności, w jakiej odbywają się obliczenia, i ignoruję niektóre mniej pozornie centralne tematy, np. co zrobić z zatwierdzeniami, które zostały już wybrane pomiędzy gałęziami).

Po pierwsze, zauważ, że rebase nie zachowujący łączenia jest raczej prosty. To mniej więcej:

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-mergesjest stosunkowo skomplikowane. Oto, co udało mi się zrobić bez utraty rzeczy, które wydają się bardzo 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 Cargumentem powinien być bardzo podobny. Zamiast rozpocząć odtwarzanie zatwierdzenia na GŁOWIE B, zamiast tego zacznij odtwarzanie zatwierdzenia na GŁOWIE C. (I użyj C_new zamiast B_new.)

Przykład 1

Na przykład weź wykres zatwierdzania

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

m jest zatwierdzeniem przez scalenie z rodzicami E i G.

Załóżmy, że zmieniliśmy temat (H) na master (C) przy użyciu normalnego, nie zachowującego scalania rebase. (Na przykład temat kasy; master bazy danych ). W takim przypadku git wybrałby następujące zatwierdzenia do odtworzenia:

  • wybierz D.
  • wybierz E
  • wybierz F.
  • wybierz G.
  • wybierz H.

a następnie zaktualizuj wykres zatwierdzania w następujący sposób:

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

(D 'to odtworzony odpowiednik D itp.)

Zauważ, że zatwierdzenie scalania m nie jest wybrane do odtworzenia.

Gdybyśmy zamiast tego zrobili --preserve-mergesrebase z H na C. (Na przykład, sprawdź temat; rebase - zachowaj-scala wzorzec ). W tym nowym przypadku git wybrałby następujące zatwierdzenia do odtworzenia:

  • wybierz D.
  • wybierz E
  • wybierz F (na D ”w gałęzi„ podtemat ”)
  • wybierz G (na F ”w gałęzi„ podtemat ”)
  • wybierz „Podtemat” gałęzi Scal w temat
  • wybierz H.

Teraz m został wybrany do odtwarzania. Zauważ też, że rodzice E i G scaleni zostali wybrani do włączenia przed scaleniem zatwierdzenia m.

Oto wynikowy wykres zatwierdzenia:

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

Ponownie, D 'jest wybieralną (tj. Odtworzoną) wersją D. To samo dla E', itp. Każde zatwierdzenie poza masterem zostało odtworzone. Zarówno E, jak i G (łączący rodzice m) zostały odtworzone jako E 'i G', aby służyć jako rodzice m '(po zmianie baza drzew nadal pozostaje taka sama).

Przykład 2

W odróżnieniu od zwykłego rebase, rebase zachowujący scalanie może tworzyć wiele potomków upstream.

Rozważ na przykład:

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

Jeśli bazujemy na H (temat) na C (master), wówczas zatwierdzenia wybrane dla rebase to:

  • wybierz D.
  • wybierz E
  • wybierz F.
  • wybierz G.
  • wybierz m
  • wybierz H.

Wynik jest taki:

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

Przykład 3

W powyższych przykładach zarówno zatwierdzenie scalania, jak i jego dwoje rodziców są odtwarzane, zamiast oryginalnych rodziców, które mają oryginalne zatwierdzenie scalania. Jednak w innych wersjach ponownie odtworzone zatwierdzenie scalania może skończyć z rodzicami, którzy byli już na wykresie zatwierdzeń przed scaleniem.

Rozważ na przykład:

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

Jeśli zmienimy temat na master (zachowując scalenia), wówczas zatwierdzenia do odtworzenia będą

  • wybierz scalenie zatwierdz m
  • wybierz F.

Przepisany wykres zatwierdzenia będzie wyglądał tak:

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

Tutaj odtworzone scalenie zatwierdzenia m 'pobiera rodziców, którzy wcześniej istnieli na wykresie zatwierdzeń, a mianowicie D (HEAD mistrza) i E (jeden z rodziców oryginalnego scalenia zatwierdzenia m).

Przykład 4

Baza zachowująca scalanie może się mylić w niektórych przypadkach „pustego zatwierdzenia”. Przynajmniej tak jest tylko niektóre starsze wersje git (np. 1.7.8.)

Weź ten wykres zatwierdzania:

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

Zauważ, że zarówno zatwierdzenie m1, jak i m2 powinny były uwzględniać wszystkie zmiany z B i F.

Jeśli spróbujemy wykonać git rebase --preserve-mergesH (temat) na D (master), wówczas do odtworzenia zostaną wybrane następujące zatwierdzenia:

  • wybierz m1
  • wybierz H.

Zauważ, że zmiany (B, F) zjednoczone w m1 powinny już zostać włączone do D. (Te zmiany powinny już zostać włączone do m2, ponieważ m2 łączy ze sobą dzieci B i F.) Dlatego, koncepcyjnie, odtwarzanie m1 na D prawdopodobnie powinien być albo brakiem operacji, albo utworzyć pusty zatwierdzenie (tzn. Taki, w którym różnica między kolejnymi wersjami jest pusta).

Zamiast tego git może odrzucić próbę odtworzenia m1 na górze D. Możesz otrzymać taki błąd:

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

Wygląda na to, że zapomniano przekazać flagi gitowi, ale podstawowym problemem jest to, że git nie lubi tworzyć pustych zatwierdzeń.


6
Zauważyłem, że git rebase --preserve-mergesjest to znacznie wolniejsze niż rebasebez --preserve-merges. Czy to efekt uboczny znalezienia właściwych zobowiązań? Czy można coś zrobić, aby przyspieszyć? (Nawiasem mówiąc… dzięki za bardzo szczegółową odpowiedź!)
David Alan Hjelle

7
Wygląda na to, że zawsze powinieneś używać - zachowaj-scala. W przeciwnym razie istnieje ryzyko utraty historii, tzn. Zatwierdzenia scalenia.
DarVar,

19
@DarVar Zawsze tracisz historię na podstawie, ponieważ twierdzisz, że zmiany dokonano w innej bazie kodu niż w rzeczywistości.
Chronial

5
Czy to wciąż „tymczasowa odpowiedź”?
Andrew Grimm,

5
@Chronial Oczywiście masz rację, że ponowne bazowanie zawsze zawiera utratę historii, ale być może DarVar nawiązywał do faktu, że nie tylko tracisz historię, ale także zmiany w kodzie kodu. Rozwiązanie konfliktu zawiera informacje, które zostały utracone na wszystkie możliwe sposoby, które można wykonać w bazie. Zawsze musisz to powtórzyć. Czy naprawdę nie ma sposobu, aby git przerobił rozwiązanie konfliktu? Dlaczego git nie może wybrać zatwierdzenia scalania?
Nils_M,

94

Git 2.18 (Q2 2018) znacznie poprawi tę --preserve-mergeopcję, dodając nową opcję.

git rebase” Dowiedział się „ --rebase-merges”, aby przeszczepić całą topologię popełnić wykres gdzie indziej .

(Uwaga: Git 2.22, drugi kwartał 2019 r. , Przestaje być aktualny --preserve-merge , a Git 2.25, pierwszy kwartał 2020 r., Przestaje reklamować go w wynikach „ git rebase --help )

Patrz popełnienia 25cff9f , popełnienia 7543f6f , popełnienia 1131ec9 , popełnienia 7ccdf65 , popełnienia 537e7d6 , popełnienia a9be29c , popełnienia 8f6aed7 , popełnienia 1644c73 , popełnienia d1e8b01 , popełnienia 4c68e7d , popełnienia 9055e40 , popełnienia cb5206e , popełnienia a01c2a5 , popełnienia 2f6b1d1 , popełnienia bf5c057 (25 Maj 2018) autor: Johannes Schindelin ( dscho) .
Zobacz commit f431d73 (25 kwietnia 2018 r.) Autor: Stefan Beller ( stefanbeller) .
Zobacz zatwierdzenie 2429335 (25 kwietnia 2018 r.) Autor: Phillip Wood ( phillipwood) .
(Połączone przez Junio ​​C Hamano - gitster- w commit 2c18e6a , 23 maja 2018 r.)

pull: zaakceptuj, --rebase-mergesaby odtworzyć topologię gałęzi

Podobnie do preservetrybu po prostu przekazującego --preserve-merges opcję do rebasepolecenia, mergestryb po prostu przekazuje --rebase-mergesopcję.

Umożliwi to użytkownikom wygodne bazowanie na nietrywialnych topologiach zatwierdzania podczas pobierania nowych zatwierdzeń, bez ich spłaszczania.


git rebasestrona man ma teraz pełną sekcję poświęconą ponownej publikacji historii przy scalaniu .

Wyciąg:

Istnieją uzasadnione powody, dla których deweloper może chcieć odtworzyć zatwierdzenia scalania: aby zachować strukturę gałęzi (lub „topologię zatwierdzenia”) podczas pracy nad wieloma powiązanymi ze sobą gałęziami.

W poniższym przykładzie programista pracuje w gałęzi tematu, która refaktoryzuje sposób definiowania przycisków, oraz w innej gałęzi tematu, która używa tego refaktoryzacji do zaimplementowania przycisku „Zgłoś błąd”.
Dane wyjściowegit log --graph --format=%s -5 mogą wyglądać następująco:

*   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

Deweloper może chcieć zmienić te zatwierdzenia na nowsze master , zachowując topologię gałęzi, na przykład gdy oczekuje się, że pierwsza gałąź tematu zostanie zintegrowana z masterdużo wcześniejszą niż druga, powiedzmy, w celu rozwiązania konfliktów scalania ze zmianami w DownloadButtonklasie, która dokonała to w master.

Ta zmiana bazy może być wykonana przy użyciu --rebase-merges opcji.


Zobacz mały przykład, patrz 1644c73 :

rebase-helper --make-script: wprowadź flagę, aby rozłączyć bazy

Sekwencer właśnie nauczył się nowych poleceń mających na celu odtworzenie struktury gałęzi ( podobnej w duchu do--preserve-merges , ale o znacznie mniej zepsutym projekcie ).

Pozwólmy na rebase--helpergenerowanie list czynności do wykonania z wykorzystaniem tych poleceń, uruchamianych przez nową --rebase-mergesopcję.
W przypadku topologii zatwierdzania takiej jak ta (gdzie HEAD wskazuje na C):

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

wygenerowana lista czynności do wykonania wygląda następująco:

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

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

Jaka jest różnica --preserve-merge?
Commit 8f6aed7 wyjaśnia:

Dawno, dawno temu ten programista pomyślał: czy nie byłoby miło, gdyby, powiedzmy, łatki Git dla Windows na rdzeniu Git mógł być reprezentowany jako gąszcz gałęzi i zostać ponownie oparty na rdzeniu Gita, aby utrzymywać zestaw łatek do wyboru?

Pierwotna próba odpowiedzi na to była: git rebase --preserve-merges .

Jednak eksperyment ten nigdy nie był zamierzony jako opcja interaktywna, a jego wsparcie polegało na git rebase --interactivetym, że implementacja tego polecenia wyglądała już bardzo, bardzo znajomo: została zaprojektowana przez tę samą osobę, która zaprojektowała --preserve-merges: naprawdę.

I przez „twoje naprawdę” autor odnosi się do siebie: Johannes Schindelin ( dscho) , który jest głównym powodem (wraz z kilkoma innymi bohaterami - Hannes, Steffen, Sebastian, ...), że mamy Git For Windows (chociaż w ciągu dnia - 2009 - to nie było łatwe ).
Pracuje w firmie Microsoft od września 2015 r. , Co ma sens, biorąc pod uwagę, że Microsoft intensywnie korzysta z Git i potrzebuje jego usług. Trend
ten rozpoczął się w 2013 r. Wraz z TFS . Od tego czasu Microsoft zarządza największym repozytorium Git na świecie ! I od października 2018, Microsoft przejął GitHub .

Możesz zobaczyć, jak Johannes mówi w tym filmie dla Git Merge 2018 w kwietniu 2018 roku.

Jakiś czas później jakiś inny programista (patrzę na ciebie, Andreas! ;-)) zdecydował, że dobrym pomysłem --preserve-mergesbyłoby połączenie go z --interactive(z zastrzeżeniami!) I opiekunem Git (cóż, tymczasowym opiekunem Git to znaczy podczas nieobecności Junio) zgodziło się, i wtedy urok tego --preserve-mergesprojektu zaczął się rozpadać dość szybko i niezbyt haniebnie.

Tutaj Jonathan mówi o Andreasie Schwabie z Suse.
Niektóre z ich dyskusji można zobaczyć w 2012 roku .

Powód? W --preserve-mergestrybie rodzice zatwierdzenia scalania (lub, w tym przypadku, dowolnego zatwierdzenia) nie zostali wyraźnie wymienieni, ale wynikali z nazwy zatwierdzenia przekazanej do pickpolecenia .

Uniemożliwiło to na przykład zmianę kolejności zatwierdzeń .
Nie wspominając już o przenoszeniu zatwierdzeń między gałęziami lub, zabraniając bóstwa, dzieleniu gałęzi tematycznych na dwie.

Niestety, te niedociągnięcia uniemożliwiły również temu trybowi (którego pierwotnym celem było zaspokojenie potrzeb Gita dla Windows, z dodatkową nadzieją, że może być również przydatny dla innych), zaspokojenie potrzeb Gita dla Windows.

Pięć lat później, kiedy stało się naprawdę niemożliwe, aby mieć jedną nieporęczną, dużą serię łatek hodge-podge częściowo powiązanych, częściowo niezwiązanych łatek w Git dla Windows, które od czasu do czasu były bazowane na podstawowych tagach Git (zdobywając niezasłużony gniew dewelopera niefortunnej git-remote-hgserii, która po raz pierwszy zdezaktualizowała konkurencyjne podejście Git for Windows, ale później została porzucona bez opiekuna) była naprawdę niemożliwa do wykonania, polecenie „ Git nożyce ogrodowe ”, urodzili : skrypcie świnka poparcie w górnej części interaktywnej rebase, , które najpierw określą topologię rozgałęzień łat, które mają zostać ponownie oparte, utwórz pseudo listę rzeczy do zrobienia do dalszej edycji, przekształć wynik w prawdziwą listę rzeczy do zrobienia (intensywnie wykorzystującexec polecenie „zaimplementuj” brakujące polecenia z listy rzeczy do zrobienia) i na koniec odtwórz serię poprawek na szczycie nowego podstawowego zatwierdzenia.

(Skrypt nożyc ogrodowych Git jest wymieniony w tej łatce w zatwierdzeniu 9055e40 )

To było w 2013
roku. Zajęło to około trzech tygodni, aby wymyślić projekt i zaimplementować go jako skrypt z drzewa. Nie trzeba dodawać, że wdrożenie wymagało kilku lat na ustabilizowanie się, a sam projekt okazał się solidny.

Dzięki tej łatce dobroć nożyc ogrodowych Git przychodzi do git rebase -isiebie .
Pominięcie --rebase-mergesopcji spowoduje wygenerowanie listy czynności do wykonania, którą można łatwo zrozumieć i gdzie oczywiste jest, jak zmienić kolejność zatwierdzeń .
Nowe gałęzie można wprowadzić, wstawiając labelpolecenia i wywołując merge <label>.
A gdy ten tryb stanie się stabilny i powszechnie akceptowany, możemy przestać być błędem projektowym--preserve-merges .


Git 2.19 (III kwartał 2018 r.) Ulepsza nową --rebase-mergesopcję, umożliwiając jej działanie --exec.

--exec” Opcja „ git rebase --rebase-merges” umieściła polecenia exec w niewłaściwych miejscach, co zostało poprawione.

Zobacz zatwierdzenie 1ace63b (09 sierpnia 2018) i zatwierdzenie f0880f7 (06 sierpnia 2018) przez Johannesa Schindelina ( dscho) .
(Połączone przez Junio ​​C Hamano - gitster- w commit 750eb11 , 20 sierpnia 2018)

rebase --exec: spraw, by to działało --rebase-merges

Chodzi o --execto, aby dołączyć execpołączenie po każdym pick.

Od czasu wprowadzenia commits fixup!/ s quash!pomysł ten został rozszerzony na „pick, ewentualnie po łańcuchu fixup / squash”, tzn. Exec nie byłby wstawiany między a picki żadną z odpowiadających mu linii fixuplub squashwierszy.

Obecna implementacja używa brudny trick aby osiągnąć: zakłada, że istnieją tylko odebrać / fixup / squash polecenia, a następnie wstawia te execlinie przed jakimkolwiek pickale pierwszy, i dołącza się ostateczne.

Dzięki wygenerowanym listom zadań do wykonania git rebase --rebase-merges, ta prosta implementacja pokazuje swoje problemy: produkuje dokładnie coś złego, gdy są label, reseti mergepolecenia.

Zmieńmy implementację tak, aby działała dokładnie tak, jak chcemy: poszukaj picklinii, pomiń wszelkie łańcuchy napraw / squash, a następnie wstaw exec linię . Spłucz, spłucz, powtórz.

Uwaga: staramy się wstawiać przed wierszami komentarza, gdy tylko jest to możliwe, ponieważ puste zatwierdzenia są reprezentowane przez komentowane wiersze wyboru (i chcemy wstawić wiersz wykonania poprzedzającego wyboru przed taką linią, a nie później).

Będąc w tym miejscu, dodawaj również execwiersze po mergepoleceniach, ponieważ są podobne w duchu do pickpoleceń: dodają nowe zatwierdzenia.


Git 2.22 (drugi kwartał 2019 r.) Naprawia użycie refs / przepisanych / hierarchii do przechowywania stanów pośrednich bazy danych, co z natury sprawia, że ​​hierarchia jest zależna od drzewa roboczego.

Zobacz zatwierdzenie b9317d5 , zatwierdzenie 90d31ff , zatwierdzenie 09e6564 (07 marca 2019) autor: Nguyễn Thái Ngọc Duy ( pclouds) .
(Połączone przez Junio ​​C Hamano - gitster- w commit 917f2cd , 09 kwietnia 2019)

Upewnij się, że referencje / przepisane / są zgodne z drzewem roboczym

a9be29c (sekwencer: tworzenie odnośników wygenerowanych przez labelpolecenie worktree-local, 2018-04-25, Git 2.19) dodaje refs/rewritten/jako przestrzeń odniesienia dla drzewa roboczego.
Niestety (moje złe) jest kilka miejsc, które wymagają aktualizacji, aby upewnić się, że to naprawdę działa.

- add_per_worktree_entries_to_dir()jest aktualizowany, aby upewnić się, że lista referencyjna wygląda na drzewie roboczym refs/rewritten/zamiast na jednym repozytorium.

  • common_list[]jest aktualizowany, dzięki czemu git_path()zwraca poprawną lokalizację. Obejmuje to „ rev-parse --git-path”.

Ten bałagan jest tworzony przeze mnie.
Zacząłem próbować to naprawić, wprowadzając, refs/worktree,gdzie wszystkie referencje będą dla danego stołu roboczego bez specjalnych zabiegów.
Niefortunne ref / przepisane pojawiły się przed refs / worktree, więc to wszystko, co możemy zrobić.


Dzięki Git 2.24 (czwarty kwartał 2019 r.) git rebase --rebase-mergesNauczył się prowadzić różne strategie łączenia i przekazywać im opcje specyficzne dla strategii.

Zobacz commit 476998d (04 września 2019) autor: Elijah Newren ( newren) .
Zobacz popełnić e1fac53 , popełnić a63f990 , popełnić 5dcdd74 , popełnić e145d99 , popełnić 4e6023b , popełnić f67336d , popełnić a9c7107 , popełnić b8c6f24 , popełnić d51b771 , popełnić c248d32 , popełnić 8c1e240 , popełnić 5efed0e , popełnić 68b54f6 , popełnić 2e7bbac , popełnić 6180b20 , popełnić d5b581f (31 Lip 2019) przezJohannes Schindelin ( dscho) .
(Połączone przez Junio ​​C Hamano - gitster- w commit 917a319 , 18 września 2019)


W Git 2.25 (Q1 2020) logika używana do rozróżnienia lokalnych referencji lokalnych i repozytorium od siebie jest stała, aby ułatwić scalenie zachowaj.

Zobacz zatwierdzenie f45f88b , zatwierdzenie c72fc40 , zatwierdzenie 8a64881 , zatwierdzenie 7cb8c92 , zatwierdzenie e536b1f (21 października 2019 r.) Autor: SZEDER Gábor ( szeder) .
(Połączone przez Junio ​​C Hamano - gitster- w commit db806d7 , 10 listopada 2019)

path.c: nie wywoływaj matchfunkcji bez wartości wtrie_find()

Podpisano: SZEDER Gábor

'logs / refs' nie jest działającą ścieżką specyficzną dla drzewa, ale od czasu zatwierdzenia b9317d55a3 (Upewnij się, że refs / przepisane / jest na drzewko robocze, 2019-03-07, v2.22.0-rc0) ' git rev-parse --git-path' zwraca fałszywą ścieżkę jeśli występuje końcowe „ /”:

$ 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/

Używamy triestruktury danych, aby efektywnie decydować, czy ścieżka należy do wspólnego katalogu, czy działa specyficznie dla drzewa.

Tak się składa, że b9317d55a3 uruchomił błąd, który jest tak stary jak triesama implementacja, dodany w 4e09cf2acf („ path: optymalizuj wspólne sprawdzanie katalogu”, 31.08.2015, Git v2.7.0-rc0 - scalenie wymienione w partii nr 2 ).

  • Zgodnie z komentarzem opisującym trie_find(), powinien wywoływać tylko podaną funkcję dopasowania „fn” dla „/ -lub- \ 0-zakończonego przedrostka klucza, dla którego trie zawiera wartość”.
    To nieprawda: istnieją trzy miejsca, w których trie_find () wywołuje funkcję dopasowania, ale w jednym z nich brakuje sprawdzenia istnienia wartości.

  • b9317d55a3 dodał dwa nowe klucze do trie:

    • logs/refs/rewritteni
    • logs/refs/worktreeobok istniejącego logs/refs/bisect”.
      W rezultacie powstał triewęzeł ze ścieżką „ logs/refs/”, który wcześniej nie istniał i do którego nie dołączono żadnej wartości.
      Zapytanie „ logs/refs/” znajduje ten węzeł, a następnie trafia w jedno miejsce wywoływania matchfunkcji, które nie sprawdza istnienia wartości, a zatem wywołuje matchfunkcję z NULLwartością as.
  • Gdy matchfunkcja check_common()jest wywoływana z NULLwartością, zwraca 0, co oznacza, że ​​żądana ścieżka nie należy do wspólnego katalogu, co ostatecznie skutkuje fałszywą ścieżką pokazaną powyżej.

Dodaj brakujący warunek, aby trie_find()nigdy nie wywoływał funkcji dopasowania z nieistniejącą wartością.

check_common() nie będzie już musiał sprawdzać, czy ma wartość inną niż NULL, więc usuń ten warunek.

Uważam, że nie ma innych ścieżek, które mogłyby spowodować podobny fałszywy wynik.

AFAICT jedynym innym kluczem powodującym wywołanie funkcji dopasowania z NULLwartością jest „ co” (z powodu klawiszy „ common” i „ config”).

Ponieważ jednak nie znajdują się one w katalogu należącym do wspólnego katalogu, oczekiwana jest wynikowa ścieżka specyficzna dla drzewa.


3
Myślę, że powinna to być najlepsza odpowiedź, w --preserve-mergesrzeczywistości nie „zachowuje” połączeń, jak chcesz, jest bardzo naiwna. Pozwala to zachować zobowiązania do scalania i ich relacje nadrzędne zatwierdzania, zapewniając jednocześnie elastyczność interaktywnego bazowania. Ta nowa funkcja jest niesamowita i gdyby nie ta dobrze napisana odpowiedź SO, nie wiedziałbym!
egucciar

@egucciar Dziękujemy. I nie jest to jedyna funkcja Git 2.18 ( stackoverflow.com/search?q=user%3A6309+%22git+2.18%22 ) i Git 2.19 ( stackoverflow.com/search?q=user%3A6309+%22git+2.19% 22 )
VonC

1
Niezwykle pomocny, jeśli próbujesz przenieść fragmenty zatwierdzeń, jak w tym
pytaniu

1
Och, tego naprawdę szukałem od jakiegoś czasu! Miałem ręczne obejście takich przypadków, w których należy stworzyć fikcyjne zatwierdzenie łączące wszystkie połączenia.
carnicer

Typowy Git. Odważ się zadać proste pytanie i najprawdopodobniej musisz nauczyć się historii Gita, wewnętrznych algorytmów, wszystkich niechlujnych szczegółów implementacyjnych, a także potrzebujesz specjalizacji z teorii grafów, aby zrozumieć, co się dzieje.
Dimitris
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.