Dlaczego git-rebase daje mi konflikty scalania, kiedy wszystko, co robię, to zgniatanie zatwierdzeń?

146

Mamy repozytorium Gita z ponad 400 zatwierdzeniami, z których pierwsze kilkadziesiąt to wiele prób i błędów. Chcemy uporządkować te zatwierdzenia, zgniatając wiele w jednym zatwierdzeniu. Oczywiście git-rebase wydaje się właściwą drogą. Mój problem polega na tym, że kończy się to konfliktami scalania, a te konflikty nie są łatwe do rozwiązania. Nie rozumiem, dlaczego w ogóle powinny istnieć jakieś konflikty, ponieważ po prostu zgniatam zatwierdzenia (nie usuwam ani nie zmieniam kolejności). Bardzo prawdopodobne, że to pokazuje, że nie do końca rozumiem, w jaki sposób git-rebase robi swoje squashy.

Oto zmodyfikowana wersja skryptów, których używam:


repo_squash.sh (to jest skrypt, który jest faktycznie uruchomiony):


rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

repo_squash_helper.sh (ten skrypt jest używany tylko przez repo_squash.sh):


if grep -q "pick " $1
then
#  cp $1 ../repo_squash_history.txt
#  emacs -nw $1
  sed -f ../repo_squash_list.txt < $1 > $1.tmp
  mv $1.tmp $1
else
  if grep -q "initial import" $1
  then
    cp ../repo_squash_new_message1.txt $1
  elif grep -q "fixing bad import" $1
  then
    cp ../repo_squash_new_message2.txt $1
  else
    emacs -nw $1
  fi
fi

repo_squash_list.txt: (ten plik jest używany tylko przez repo_squash_helper.sh)


# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g

Treść „nowej wiadomości” pozostawię waszej wyobraźni. Początkowo zrobiłem to bez opcji "--strategy theirs" (tj. Używając domyślnej strategii, która, jeśli dobrze rozumiem dokumentację, jest rekurencyjna, ale nie jestem pewien, która strategia rekurencyjna jest używana), i też nie. t działa. Powinienem również zwrócić uwagę, że używając zakomentowanego kodu w repo_squash_helper.sh, zapisałem oryginalny plik, na którym działa skrypt seda, i uruchomiłem skrypt seda, aby upewnić się, że robi to, co chciałem ( to było). Ponownie, nie wiem nawet, dlaczego miałby nastąpić konflikt, więc nie wydaje się, aby miało to duże znaczenie, która strategia zostanie zastosowana. Wszelkie rady lub spostrzeżenia byłyby pomocne, ale przede wszystkim chcę, aby to zgniatanie działało.

Zaktualizowano o dodatkowe informacje z dyskusji z Jefromi:

Przed rozpoczęciem pracy nad naszym ogromnym „prawdziwym” repozytorium użyłem podobnych skryptów w repozytorium testowym. To było bardzo proste repozytorium, a test działał bezproblemowo.

Komunikat, który otrzymuję, gdy się nie powiedzie, to:

Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir

To jest pierwszy wybór po pierwszym zatwierdzeniu squasha. Uruchomienie git statusdaje czysty katalog roboczy. Jeśli następnie zrobię git rebase --continue, po kilku kolejnych zatwierdzeniach otrzymam bardzo podobny komunikat. Jeśli zrobię to ponownie, po kilkudziesięciu zatwierdzeniach otrzymam kolejną bardzo podobną wiadomość. Jeśli zrobię to jeszcze raz, tym razem przejdzie przez około sto zatwierdzeń i wyświetli następujący komunikat:

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental

Jeśli potem ucieknę git status, otrzymam:

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   repo/file_A.cpp
# modified:   repo/file_B.cpp
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified:      repo/file_X.cpp
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted:    repo/file_Z.imp

„Oba zmodyfikowane” bit brzmi dla mnie dziwnie, ponieważ był to po prostu rezultat wybrania. Warto też zauważyć, że jeśli spojrzę na „konflikt”, to sprowadza się on do jednej linii, w której jedna wersja zaczyna się znakiem [tabulatora], a druga czterema spacjami. Brzmiało to tak, jakby mógł to być problem z konfiguracją mojego pliku konfiguracyjnego, ale nie ma w tym nic takiego. (Zauważyłem, że core.ignorecase jest ustawiony na true, ale najwyraźniej git-clone zrobił to automatycznie. Nie jestem tym całkowicie zaskoczony, biorąc pod uwagę, że oryginalne źródło znajdowało się na komputerze z systemem Windows).

Jeśli ręcznie naprawię plik_X.cpp, to niedługo potem nie powiedzie się z innym konfliktem, tym razem między plikiem (CMakeLists.txt), który według jednej wersji powinien istnieć, a jedną wersją nie powinien. Jeśli naprawię ten konflikt, mówiąc, że chcę tego pliku (co robię), kilka zatwierdzeń później pojawia się inny konflikt (w tym samym pliku), w którym teraz są raczej nietrywialne zmiany. To wciąż tylko około 25% drogi przez konflikty.

Powinienem również zwrócić uwagę, ponieważ może to być bardzo ważne, że projekt ten zaczynał się w repozytorium svn. Ta początkowa historia została najprawdopodobniej zaimportowana z repozytorium svn.

Aktualizacja nr 2:

Na próżno (pod wpływem komentarzy Jefromi) zdecydowałem się zmienić mój repo_squash.sh na:

rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

A potem po prostu zaakceptowałem oryginalne wpisy, tak jak jest. To znaczy, „rebase” nie powinno było niczego zmienić. Skończyło się na tych samych wynikach, które opisaliśmy wcześniej.

Aktualizacja nr 3:

Alternatywnie, jeśli pominę strategię i zamieniam ostatnie polecenie na:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Nie mam już problemów z rebase typu „nic do popełnienia”, ale nadal pozostają mi inne konflikty.

Zaktualizuj za pomocą repozytorium zabawek, które odtwarza problem:

test_squash.sh (to jest plik, który faktycznie uruchamiasz):

#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================

#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt 
git add test_file.txt 
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..

#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================

#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================

#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================

test_squash_helper.sh (używany przez test_sqash.sh):

# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
  sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
  mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
  echo "Created two line file" > $1
fi

PS: Tak, wiem, że niektórzy z was wzdrygają się, kiedy widzicie, jak używam emacsa jako pomocniczego edytora.

PPS: Wiemy, że po rebase będziemy musieli usunąć wszystkie nasze klony z istniejącego repozytorium. (Zgodnie z wersetem „nie będziesz przebudowywać repozytorium po jego opublikowaniu”).

PPPS: Czy ktoś może mi powiedzieć, jak dodać do tego nagrodę? Nie widzę opcji nigdzie na tym ekranie, niezależnie od tego, czy jestem w trybie edycji, czy widoku.

Ben Hocking
źródło
Bardziej adekwatna niż użyte skrypty jest ostatnia próba działania - wygląda na to, że jest to lista wymieszanych pick i squasha, prawda? Czy są jakieś zatwierdzenia scalania w gałęzi, która została zmieniona? (Chociaż i tak nie używasz rebase -p)
Cascabel
Nie jestem pewien, co masz na myśli przez „ostatnią próbę akcji”, ale jest to po prostu lista wymieszanych picków i squasha, przy czym ostatnie 400 lub więcej to wszystkie typy. Na tej liście nie ma scaleń, chociaż samo ponowne bazowanie wykonuje własne scalenia. Zgodnie z tym, co przeczytałem, „rebase -p” nie jest zalecane w trybie interaktywnym (który oczywiście w moim przypadku nie jest aż tak interaktywny). Z kernel.org/pub/software/scm/git/docs/git-rebase.html : „Wykorzystuje to mechanizm --interactive wewnętrznie, ale jawne łączenie go z opcją --interactive generalnie nie jest dobrym pomysłem”
Ben Hocking
Przez „ostatnią próbę akcji” miałem na myśli listę operacji typu pick / squash przekazanych z powrotem do rebase --interactive- jest to rodzaj listy działań, które ma wykonać git. Miałem nadzieję, że uda ci się zredukować to do pojedynczego squasha, który powoduje konflikty, i uniknąć całej dodatkowej złożoności skryptów pomocniczych. Inną brakującą informacją jest sytuacja, w której występują konflikty - kiedy git nakłada łaty, aby utworzyć squash, czy też gdy próbuje przejść dalej, poza squasha i nałożyć kolejną łatkę? (I czy na pewno nic złego się nie dzieje z twoją kludge GIT_EDITOR? Kolejny głos na prosty przypadek testowy.)
Cascabel
Dzięki za przydatne komentarze. Zaktualizowałem pytanie, aby odzwierciedlić moje odpowiedzi.
Ben Hocking
2
Przypadek repozytorium zabawek jest dużo, dużo łatwiejszy do zrozumienia niż twoje skrypty - i pokazuje, że skrypty nie mają z tym nic wspólnego, tylko fakt, że próbujesz ponownie bazować gałąź zawierającą scalanie z rozwiązywaniem konfliktów (łagodnie złe połączenie ).
Cascabel

Odpowiedzi:

69

W porządku, jestem na tyle pewny, że mogę rzucić odpowiedź. Może będę musiał to edytować, ale wydaje mi się, że wiem, na czym polega twój problem.

Twój przypadek testowy repozytorium zabawek zawiera scalenie - co gorsza, ma scalenie z konfliktami. A ty zbierasz się ponownie po scaleniu. Bez -p(co nie do końca działa -i) scalenia są ignorowane. Oznacza to, że cokolwiek zrobiłeś w rozwiązywaniu konfliktów jest nie tam , kiedy próbuje do rebase cherry-pick następny popełnić, więc jego łata nie może stosować. (Uważam, że jest to pokazane jako konflikt scalania, ponieważ git cherry-pickmożna zastosować łatkę, wykonując trójstronne scalenie między oryginalnym zatwierdzeniem, bieżącym zatwierdzeniem i wspólnym przodkiem).

Niestety, jak zauważyliśmy w komentarzach, -ii -p(zachowaj połączenia) nie dogadują się zbyt dobrze. Wiem, że edycja / przeredagowanie działa, a zmiana kolejności nie działa. Ja jednak wierzę , że to działa dobrze z kabaczki. Nie jest to udokumentowane, ale zadziałało w przypadku przypadków testowych, które opisuję poniżej. Jeśli twoja sprawa jest bardziej skomplikowana, możesz mieć wiele problemów z robieniem tego, co chcesz, chociaż nadal będzie to możliwe. (Morał tej historii: posprzątaj rebase -i przed połączeniem.)

Załóżmy więc, że mamy bardzo prosty przypadek, w którym chcemy zgnieść razem A, B i C:

- o - A - B - C - X - D - E - F (master)
   \             /
    Z -----------

Teraz, jak powiedziałem, gdyby nie było konfliktów w X, git rebase -i -pdziała zgodnie z oczekiwaniami.

W przypadku konfliktów sprawy stają się nieco trudniejsze. Zrobi dobrze zgniatanie, ale kiedy spróbuje odtworzyć scalenie, konflikty pojawią się ponownie. Będziesz musiał rozwiązać je ponownie, dodać je do indeksu, a następnie użyć, git rebase --continueaby przejść dalej. (Oczywiście możesz rozwiązać je ponownie, sprawdzając wersję z oryginalnego zatwierdzenia scalania).

Jeśli zdarzy się, że rererewłączone w Twojej repo ( rerere.enabledzestaw do true), będzie to sposób łatwiej - git będzie mógł ponownie skorzystać z ponownego Przewodowy ponowne rozwiązanie od kiedy pierwotnie miał konfliktów, a wszystko co musisz zrobić, to zbadaj aby upewnić się, że wszystko działa prawidłowo, dodaj pliki do indeksu i kontynuuj. (Możesz nawet pójść o krok dalej, włączając rerere.autoupdate, a to doda je za Ciebie, więc scalenie nawet się nie powiedzie). Domyślam się jednak, że nigdy nie włączyłeś rerere, więc będziesz musiał sam rozwiązać konflikt. *

* Lub możesz wypróbować rerere-train.shskrypt z git-contrib, który próbuje "Prime [the] rerere bazy danych z istniejących merge commits" - w zasadzie sprawdza wszystkie merge commits, próbuje je scalić, a jeśli scalenie się nie powiedzie, przechwytuje wyniki i pokazuje je git-rerere. Może to być czasochłonne i nigdy z niego nie korzystałem, ale może być bardzo pomocne.

Cascabel
źródło
PS Patrząc wstecz na moje komentarze, widzę, że powinienem był złapać to wcześniej. Zapytałem, czy ponownie bazujesz gałąź zawierającą scalenia, a powiedziałeś, że na interaktywnej liście rebase nie było scaleń, co nie jest tym samym - nie było tam scaleń, ponieważ nie dostarczyłeś -popcji.
Cascabel
Zdecydowanie spróbuję. Od czasu opublikowania tego zauważyłem również, że w niektórych przypadkach mogę po prostu pisać git commit -a -m"Some message"i git rebase --continue, i będzie to kontynuowane. Działa to nawet bez -popcji, ale działa jeszcze lepiej z tą -popcją (ponieważ nie robię żadnej zmiany kolejności, wydaje się, że -pjest w porządku). Tak czy inaczej, będę cię informować.
Ben Hocking
Ustawienie git config --global rerere.enabled truei git config --global rerere.autoupdate trueprzed uruchomieniem przykładu testowego rozwiązuje podstawowy problem. Co ciekawe, nie zachowuje on jednak scaleń, nawet przy określaniu --preserve-merges. Jeśli jednak nie mam tych ustawionych i piszę git commit -a -m"Meaningful commit"i git rebase --continuepodczas określania --preserve-merges, zachowuje to scalenia. W każdym razie dziękuję za pomoc w rozwiązaniu tego problemu!
Ben Hocking
84

Jeśli nie masz nic przeciwko utworzeniu nowego oddziału, oto jak poradziłem sobie z problemem:

Będąc na głównym:

# create a new branch
git checkout -b new_clean_branch

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
git reset --soft main        

git commit -m "Squashed changes from original_messy_branch"
hlidka
źródło
3
Najszybsze i wydajne rozwiązanie :)
IslamTaha
2
To była świetna sugestia! Działał bardzo dobrze przy szybkiej konsolidacji kilku zatwierdzeń.
philidem,
3
To znacznie bezpieczniejsze rozwiązanie. Dodałem również --squash do scalenia. Bycie: git merge --squash original_messy_branch
jezpez
1
Dziękuję za uratowanie mojego dnia i życia :) Najlepsze rozwiązanie, jakie każdy może dostać
Kiedy git diff new_clean_branch..original_messy_branchpowinieneś to zrobić, czy powinny być takie same? Dla mnie są różne.
LeonardChallis
5

Szukałem podobnego wymagania, tj. Odrzucenia pośrednich zatwierdzeń mojej gałęzi programistycznej, zauważyłem, że ta procedura działa dla mnie.
na mojej gałęzi roboczej

git reset –hard mybranch-start-commit
git checkout mybranch-end-commit . // files only of the latest commit
git add -a
git commit -m”New Message intermediate commits discarded”

viola połączyliśmy najnowsze zatwierdzenie z zatwierdzeniem początkowym gałęzi! Brak problemów z konfliktami scalania! W mojej praktyce uczenia się doszedłem na tym etapie do następującego wniosku: Czy istnieje lepsze podejście do tego celu.

user28186
źródło
Do Twojej wiadomości - właśnie tego próbowałem i stwierdziłem, że wykonanie wypisania zatwierdzenia na końcu mojej gałęzi nie powoduje usunięcia usunięć, które wystąpiły w zatwierdzeniach pośrednich. Więc sprawdza tylko te pliki, które istniały w mybranch-end-commit.
ahains
1

Opierając się na świetnej odpowiedzi @ hlidka powyżej, która minimalizuje ręczną interwencję, chciałem dodać wersję, która zachowuje wszystkie nowe zatwierdzenia na serwerze głównym, których nie ma w gałęzi, do squasha.

Uważam, że można je łatwo utracić w git resetkroku w tym przykładzie.

# create a new branch 
# ...from the commit in master original_messy_branch was originally based on. eg 5654da06
git checkout -b new_clean_branch 5654da06

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
# ...base the reset on the base commit from Master
git reset --soft 5654da06       

git commit -m "Squashed changes from original_messy_branch"

# Rebase onto HEAD of master
git rebase origin/master

# Resolve any new conflicts from the new commits
JonoB
źródło
0

Zauważ, że -Xopcje i strategii zostały zignorowane, gdy zostały użyte w interaktywnej rebase.

Zobacz commit db2b3b820e2b28da268cc88adff076b396392dfe (lipiec 2013, git 1.8.4+),

Nie ignoruj ​​opcji scalania w interaktywnym rebase

Strategię scalania i jej opcje można określić w programie git rebase, ale w przypadku -- interactiveich użycia zostały one całkowicie zignorowane.

Podpisał: Arnaud Fontaine

Oznacza to, że -Xstrategia i strategia działają teraz z interaktywnym rebase, a także ze zwykłym rebase, a Twój początkowy skrypt może teraz działać lepiej.

VonC
źródło
0

Miałem prostszy, ale podobny problem, w którym 1) rozwiązałem konflikt scalania w lokalnej gałęzi, 2) kontynuowałem pracę, dodając dużo więcej małych zatwierdzeń, 3) chciałem zmienić bazę i trafić w konflikty scalania.

U mnie git rebase -p -i masterzadziałało. Zachował pierwotne zobowiązanie do rozwiązania konfliktu i pozwolił mi zmiażdżyć innych na szczycie.

Mam nadzieję, że to komuś pomoże!

abaldwinhunter
źródło
Nadal mam kilka konfliktów do ręcznego rozwiązania podczas testowania rebase z -p. Prawdą jest, że było o wiele mniej konfliktów i były one ograniczone do pliku projektu, a nie do wszystkich plików kodu, z którymi wcześniej występowały konflikty. Najwyraźniej „Scalanie rozwiązań konfliktów lub ręcznych poprawek w celu scalenia zatwierdzeń nie jest zachowywane”. - stackoverflow.com/a/35714301/727345
JonoB