Warum gibt mir Git-Rebase Zusammenführungskonflikte, wenn ich nur Commits quetsche?

146

Wir haben ein Git-Repository mit über 400 Commits, von denen das erste Dutzend viel Versuch und Irrtum war. Wir möchten diese Commits bereinigen, indem wir viele zu einem einzigen Commit zusammenfassen. Natürlich scheint Git-Rebase der richtige Weg zu sein. Mein Problem ist, dass es zu Zusammenführungskonflikten kommt und diese Konflikte nicht einfach zu lösen sind. Ich verstehe nicht, warum es überhaupt Konflikte geben sollte, da ich nur Commits quetsche (nicht lösche oder neu anordne). Sehr wahrscheinlich zeigt dies, dass ich nicht ganz verstehe, wie Git-Rebase seine Kürbisse macht.

Hier ist eine modifizierte Version der Skripte, die ich verwende:


repo_squash.sh (dies ist das Skript, das tatsächlich ausgeführt wird):


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 (dieses Skript wird nur von repo_squash.sh verwendet):


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: (Diese Datei wird nur von repo_squash_helper.sh verwendet.)


# 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

Ich überlasse den Inhalt der "neuen Nachricht" Ihrer Fantasie. Anfangs habe ich dies ohne die Option "--strategy Theirs" getan (dh mit der Standardstrategie, die, wenn ich die Dokumentation richtig verstehe, rekursiv ist, aber ich bin nicht sicher, welche rekursive Strategie verwendet wird), und es hat auch nicht funktioniert. t arbeiten. Außerdem sollte ich darauf hinweisen, dass ich mit dem auskommentierten Code in repo_squash_helper.sh die Originaldatei, an der das sed-Skript arbeitet, gespeichert und das sed-Skript dagegen ausgeführt habe, um sicherzustellen, dass es das tut, was ich wollte ( es war). Auch hier weiß ich nicht einmal , warum es würde ein Konflikt sein, so wäre es nicht so viel Rolle zu spielen, welche Strategie verwendet wird. Jeder Rat oder jede Einsicht wäre hilfreich, aber meistens möchte ich nur, dass dieses Quetschen funktioniert.

Aktualisiert mit zusätzlichen Informationen aus der Diskussion mit Jefromi:

Bevor ich an unserem riesigen "echten" Repository gearbeitet habe, habe ich ähnliche Skripte für ein Test-Repository verwendet. Es war ein sehr einfaches Repository und der Test funktionierte sauber.

Die Nachricht, die ich bekomme, wenn es fehlschlägt, ist:

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

Dies ist die erste Auswahl nach dem ersten Squash-Commit. Das Ausführen git statusergibt ein sauberes Arbeitsverzeichnis. Wenn ich dann eine mache git rebase --continue, erhalte ich nach ein paar weiteren Commits eine sehr ähnliche Nachricht. Wenn ich es dann noch einmal mache, erhalte ich nach ein paar Dutzend Commits eine weitere sehr ähnliche Nachricht. Wenn ich es noch einmal mache, durchläuft es diesmal ungefähr hundert Commits und liefert die folgende Meldung:

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

Wenn ich dann renne git status, bekomme ich:

# 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

Das "beide modifizierte" Bit klingt für mich komisch, da dies nur das Ergebnis einer Auswahl war. Es ist auch erwähnenswert, dass wenn ich mir den "Konflikt" anschaue, er sich auf eine einzelne Zeile beschränkt, wobei eine Version mit einem [Tab] -Zeichen beginnt und die andere mit vier Leerzeichen. Das klang, als wäre es ein Problem mit der Einrichtung meiner Konfigurationsdatei, aber es ist nichts dergleichen darin. (Ich habe bemerkt, dass core.ignorecase auf true gesetzt ist, aber offensichtlich hat git-clone dies automatisch getan. Ich bin nicht völlig überrascht, wenn man bedenkt, dass sich die ursprüngliche Quelle auf einem Windows-Computer befand.)

Wenn ich file_X.cpp manuell behebe, schlägt dies kurz darauf mit einem weiteren Konflikt fehl, diesmal zwischen einer Datei (CMakeLists.txt), von der eine Version glaubt, dass sie existieren sollte, und einer Version, die nicht denken sollte. Wenn ich diesen Konflikt behebe, indem ich sage, dass ich diese Datei möchte (was ich auch tue), erhalte ich einige Commits später einen weiteren Konflikt (in derselben Datei), bei dem es jetzt einige eher nicht triviale Änderungen gibt. Es sind immer noch nur etwa 25% des Weges durch die Konflikte.

Da dies sehr wichtig sein könnte, sollte ich auch darauf hinweisen, dass dieses Projekt in einem SVN-Repository gestartet wurde. Diese anfängliche Historie wurde sehr wahrscheinlich aus diesem SVN-Repository importiert.

Update Nr. 2:

Auf einer Lerche (beeinflusst von Jefromis Kommentaren) habe ich beschlossen, die Änderung meiner repo_squash.sh wie folgt vorzunehmen:

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

Und dann habe ich die ursprünglichen Einträge so wie sie sind akzeptiert. Das heißt, die "Rebase" hätte nichts ändern sollen. Es endete mit den gleichen Ergebnissen, die zuvor beschrieben wurden.

Update Nr. 3:

Alternativ, wenn ich die Strategie weglasse und den letzten Befehl durch Folgendes ersetze:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Ich bekomme nicht mehr die Rebase-Probleme "nichts zu begehen", aber ich habe immer noch die anderen Konflikte.

Update mit Spielzeug-Repository, das das Problem neu erstellt:

test_squash.sh (dies ist die Datei, die Sie tatsächlich ausführen):

#========================================================
# 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 (wird von test_sqash.sh verwendet):

# 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: Ja, ich weiß, dass einige von Ihnen zusammenzucken, wenn Sie sehen, dass ich Emacs als Fallback-Editor verwende.

PPS: Wir wissen, dass wir nach der Rebase alle unsere Klone des vorhandenen Repositorys wegblasen müssen. (Nach dem Motto "Du sollst ein Repository nicht neu starten, nachdem es veröffentlicht wurde".)

PPPS: Kann mir jemand sagen, wie ich dem ein Kopfgeld hinzufügen kann? Ich sehe die Option nirgendwo auf diesem Bildschirm, egal ob ich mich im Bearbeitungsmodus oder im Ansichtsmodus befinde.

Ben Hocking
quelle
Relevanter als die verwendeten Skripte ist die letzte versuchte Aktion - es scheint ziemlich sicher zu sein, dass es sich um eine Liste von vermischten Pick and Squash handelt, oder? Und gibt es Merge Commits in der neu basierten Niederlassung? (Obwohl Sie rebase -psowieso nicht verwenden )
Cascabel
Ich bin mir nicht sicher, was Sie unter "endgültiger versuchter Aktion" verstehen, aber es ist nur eine Liste von vermischten Pick- und Squash- Aktionen , wobei die letzten 400 alle Pick sind. Diese Liste enthält keine Zusammenführungen, obwohl die Neuausrichtung selbst ihre Zusammenführungen durchführt. Nach dem, was ich gelesen habe, wird "rebase -p" im interaktiven Modus nicht empfohlen (was in meinem Fall natürlich nicht allzu interaktiv ist). Aus kernel.org/pub/software/scm/git/docs/git-rebase.html : "Dies verwendet die --interactive-Maschinerie intern, aber eine explizite Kombination mit der --interactive-Option ist im Allgemeinen keine gute Idee"
Ben Hocking
Mit "letzte versuchte Aktion" meinte ich die Liste der Pick / Squash, an die zurückgegeben wurde rebase --interactive- das ist eine Art Liste von Aktionen, die Git versuchen soll. Ich hatte gehofft, Sie könnten dies auf einen einzigen Squash reduzieren, der Konflikte verursacht, und die zusätzliche Komplexität Ihrer Hilfsskripte vermeiden. Die andere fehlende Information ist, wann die Konflikte auftreten - wann Git die Patches anwendet, um den Squash zu bilden, oder wenn es versucht, über den Squash hinauszugehen und den nächsten Patch anzuwenden? (Und sind Sie sicher, dass mit Ihrem GIT_EDITOR-Kludge nichts Schlimmes passiert? Eine weitere Abstimmung für einen einfachen Testfall.)
Cascabel
Danke für die nützlichen Kommentare. Ich habe die Frage aktualisiert, um meine Antworten wiederzugeben.
Ben Hocking
2
Der Fall des Spielzeug-Repositorys ist viel, viel einfacher zu verstehen als Ihre Skripte - und zeigt, dass die Skripte nichts damit zu tun haben, sondern nur die Tatsache, dass Sie versuchen, einen Zweig, der eine Zusammenführung mit Konfliktlösung enthält, neu zu gründen (eine leicht böse Zusammenführung) ).
Cascabel

Antworten:

69

In Ordnung, ich bin zuversichtlich genug, eine Antwort herauszugeben. Vielleicht muss es bearbeitet werden, aber ich glaube, ich weiß, was Ihr Problem ist.

Ihr Toy Repo-Testfall enthält eine Zusammenführung - schlimmer noch, es gibt eine Zusammenführung mit Konflikten. Und Sie stützen sich auf die Fusion. Ohne -p(was nicht ganz funktioniert -i) werden die Zusammenführungen ignoriert. Dies bedeutet, dass alles, was Sie in Ihrer Konfliktlösung getan haben, nicht vorhanden ist, wenn die Rebase versucht, das nächste Commit auszuwählen, sodass der Patch möglicherweise nicht angewendet wird. (Ich glaube, dies wird als Zusammenführungskonflikt angezeigt, da git cherry-pickder Patch angewendet werden kann, indem eine dreifache Zusammenführung zwischen dem ursprünglichen Commit, dem aktuellen Commit und dem gemeinsamen Vorfahren durchgeführt wird.)

Leider kommen wir, wie wir in den Kommentaren bemerkt haben, -iund -p(Zusammenführungen beibehalten) nicht sehr gut miteinander aus. Ich weiß, dass das Bearbeiten / Umformulieren funktioniert und das Umordnen nicht. Ich glaube jedoch, dass es gut mit Kürbissen funktioniert. Dies ist nicht dokumentiert, hat aber für die unten beschriebenen Testfälle funktioniert. Wenn Ihr Fall viel, viel komplexer ist, haben Sie möglicherweise große Probleme, das zu tun, was Sie wollen, obwohl dies immer noch möglich ist. (Moral der Geschichte: Aufräumen rebase -i vor dem Zusammenführen.)

Nehmen wir also an, wir haben einen sehr einfachen Fall, in dem wir A, B und C zusammenpressen wollen:

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

Nun, wie gesagt, wenn es in X keine Konflikte gab, git rebase -i -pfunktioniert es wie erwartet.

Wenn es Konflikte gibt, wird es etwas schwieriger. Es wird gut gequetscht, aber wenn es dann versucht, die Zusammenführung wiederherzustellen, werden die Konflikte erneut auftreten. Sie müssen sie erneut auflösen, zum Index hinzufügen und dann git rebase --continuezum Fortfahren verwenden. (Natürlich können Sie sie erneut beheben, indem Sie die Version aus dem ursprünglichen Zusammenführungs-Commit auschecken.)

Wenn Sie haben , passieren rererein Ihrem Repo (enabled rerere.enabledauf true eingestellt), wird auf diese Weise einfacher sein - git Lage sein wird, wieder die Verwendung re Corded re Lösung aus , als Sie die Konflikte hatten, und alle müssen Sie tun , ist es inspizieren Um sicherzustellen, dass es richtig funktioniert, fügen Sie die Dateien zum Index hinzu und fahren Sie fort. (Sie können sogar einen Schritt weiter gehen und sich einschalten rerere.autoupdate, und es werden sie für Sie hinzugefügt, damit die Zusammenführung nicht einmal fehlschlägt.) Ich vermute jedoch, dass Sie Rerere nie aktiviert haben, sodass Sie die Konfliktlösung selbst durchführen müssen. *

* Oder Sie können das rerere-train.shSkript von git-contrib ausprobieren , das versucht, "die Datenbank aus vorhandenen Zusammenführungs-Commits neu zu sortieren" - im Grunde werden alle Zusammenführungs-Commits überprüft, versucht, sie zusammenzuführen, und wenn die Zusammenführung fehlschlägt, es erfasst die Ergebnisse und zeigt sie an git-rerere. Dies kann zeitaufwändig sein und ich habe es nie benutzt, aber es könnte sehr hilfreich sein.

Cascabel
quelle
PS Wenn ich auf meine Kommentare zurückblicke, sehe ich, dass ich das früher hätte fangen sollen. Ich habe Sie gefragt, ob Sie einen Zweig mit Zusammenführungen neu gründen möchten, und Sie haben gesagt, dass die interaktive Wiederherstellungsliste keine Zusammenführungen enthält, was nicht dasselbe ist. Es gab dort keine Zusammenführungen, weil Sie die -pOption nicht angegeben haben.
Cascabel
Ich werde es auf jeden Fall versuchen. Seit ich dies gepostet habe, ist mir in einigen Fällen auch aufgefallen , dass ich einfach git commit -a -m"Some message"und eingeben kann git rebase --continueund es weitergeht. Dies funktioniert auch ohne die -pOption, aber es funktioniert noch besser mit der -pOption (da ich keine Nachbestellung mache, scheint das -pin Ordnung zu sein). Wie auch immer, ich werde dich auf dem Laufenden halten.
Ben Hocking
Durch Festlegen git config --global rerere.enabled trueund git config --global rerere.autoupdate truevor dem Ausführen des Testbeispiels wird das Hauptproblem behoben. Interessanterweise werden die Zusammenführungen jedoch nicht beibehalten, selbst wenn sie angegeben werden --preserve-merges. Wenn ich diese jedoch nicht festgelegt habe git commit -a -m"Meaningful commit"und git rebase --continuewährend der Angabe tippe --preserve-merges, bleiben die Zusammenführungen erhalten. Wie auch immer, danke, dass du mir geholfen hast, dieses Problem zu überwinden!
Ben Hocking
84

Wenn es Ihnen nichts ausmacht, einen neuen Zweig zu erstellen, habe ich das Problem folgendermaßen behandelt:

Auf der Hauptstraße sein:

# 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
quelle
3
Die schnellste und effizienteste Lösung :)
IslamTaha
2
Dies war ein großartiger Vorschlag! Hat sehr gut funktioniert, um mehrere Commits schnell zu konsolidieren.
Philidem
3
Dies ist eine weitaus sicherere Lösung. Ich habe auch --squash zur Zusammenführung hinzugefügt. Sein: git merge --squash original_messy_branch
jezpez
1
Vielen Dank, dass Sie meinen Tag und mein Leben
Wann git diff new_clean_branch..original_messy_branchsollten Sie gleich sein? Für mich sind sie anders.
LeonardChallis
5

Ich suchte nach einer ähnlichen Anforderung, dh dem Verwerfen von Zwischenverpflichtungen meines Entwicklungszweigs. Ich habe festgestellt, dass dieses Verfahren für mich funktioniert.
auf meinem Arbeitszweig

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 Wir haben das letzte Commit mit dem Start-Commit der Filiale verbunden! Keine Probleme beim Zusammenführen von Konflikten! In meiner Lernpraxis bin ich zu diesem Zeitpunkt zu dem Schluss gekommen, ob es einen besseren Ansatz für diesen Zweck gibt.

user28186
quelle
Zu Ihrer Information: Ich habe dies nur versucht und festgestellt, dass ich beim Auschecken von mybranch-end-commit nicht die Löschungen bekomme, die in den Zwischen-Commits aufgetreten sind. Es werden also nur die Dateien ausgecheckt, die in mybranch-end-commit vorhanden waren.
Ahains
1

Aufbauend auf der obigen großartigen Antwort von @ hlidka , die manuelle Eingriffe minimiert, wollte ich eine Version hinzufügen, die alle neuen Commits auf dem Master beibehält , die nicht in der Verzweigung zum Squash sind.

Wie ich glaube, könnten diese in diesem git resetSchritt leicht verloren gehen .

# 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
quelle
0

Beachten Sie, dass -Xund Strategieoptionen bei Verwendung in einer interaktiven Rebase ignoriert wurden.

Siehe Commit db2b3b820e2b28da268cc88adff076b396392dfe (Juli 2013, Git 1.8.4+),

Ignorieren Sie die Zusammenführungsoptionen in der interaktiven Rebase nicht

Die Zusammenführungsstrategie und ihre Optionen können in angegeben werden git rebase, mit -- interactivewurden sie jedoch vollständig ignoriert.

Unterzeichnet von: Arnaud Fontaine

Das bedeutet, dass -XStrategie und Strategie jetzt sowohl mit interaktiver als auch mit einfacher Rebase funktionieren und Ihr ursprüngliches Skript jetzt besser funktionieren könnte.

VonC
quelle
0

Ich stieß auf ein einfacheres, aber ähnliches Problem, bei dem ich 1) einen Zusammenführungskonflikt in einem lokalen Zweig gelöst hatte, 2) weiter daran arbeitete, viel mehr kleine Commits hinzuzufügen, 3) Zusammenführungskonflikte neu zu definieren und zu treffen.

Für mich hat git rebase -p -i mastergearbeitet. Es behielt die ursprüngliche Verpflichtung zur Konfliktlösung bei und erlaubte mir, die anderen an der Spitze zu zerquetschen.

Hoffe das hilft jemandem!

abaldwinhunter
quelle
Ich musste noch einige Konflikte manuell lösen, als ich die Rebase mit -p versuchte. Zugegeben, es gab viel weniger Konflikte und sie beschränkten sich nur auf die Projektdatei und nicht auf alle Codedateien, mit denen ich zuvor Konflikte hatte. Anscheinend "werden Konfliktlösungen oder manuelle Änderungen zum Zusammenführen von Commits nicht zusammengeführt." - stackoverflow.com/a/35714301/727345
JonoB