<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Git on kenji.blog</title><link>http://kenji.blog/de/categories/git/</link><description>Recent content in Git on kenji.blog</description><generator>Hugo -- gohugo.io</generator><language>de</language><copyright>kenjinote</copyright><lastBuildDate>Sun, 13 Sep 2026 09:00:00 +0900</lastBuildDate><atom:link href="http://kenji.blog/de/categories/git/index.xml" rel="self" type="application/rss+xml"/><item><title>【Git-Befehle】Der Unterschied zwischen rebase und merge und deren richtige Anwendung in der Praxis</title><link>http://kenji.blog/de/p/git-rebase-vs-merge-practical-guide/</link><pubDate>Sun, 13 Sep 2026 09:00:00 +0900</pubDate><guid>http://kenji.blog/de/p/git-rebase-vs-merge-practical-guide/</guid><description>&lt;img src="http://kenji.blog/p/git-rebase-vs-merge-practical-guide/img/eyecatch.jpg" alt="Featured image of post 【Git-Befehle】Der Unterschied zwischen rebase und merge und deren richtige Anwendung in der Praxis" />&lt;h1 id="1-einführung-warum-merge-oder-rebase-ein-ewiges-thema-ist">1. Einführung: Warum „merge oder rebase“ ein ewiges Thema ist
&lt;/h1>&lt;p>Git ist ein unverzichtbares Versionskontrollsystem in der modernen Softwareentwicklung. Wenn mehrere Entwickler gleichzeitig die Codebasis ändern, spielt das leistungsstarke Branching-Modell von Git seine Stärken aus. In der Teamentwicklung ist die Diskussion darüber, ob man &lt;code>merge&lt;/code> oder &lt;code>rebase&lt;/code> verwenden sollte, jedoch eines der Themen, das Entwickler vom Anfänger bis zum Experten immer wieder beschäftigt.&lt;/p>
&lt;p>In diesem Artikel werden wir die Unterschiede im Mechanismus von &lt;code>git merge&lt;/code> und &lt;code>git rebase&lt;/code> tiefgehend untersuchen, indem wir die interne Struktur von Git, wie den DAG (gerichteter azyklischer Graph) und die mathematischen Eigenschaften von Commit-Hashes, entschlüsseln. Darüber hinaus werden wir anhand von konkreten Workflows ausführlich erklären, wie man diese beiden in der Praxis richtig einsetzt. Indem Sie nicht nur die Befehle kennenlernen, sondern auch verstehen, welche Berechnungen Git im Hintergrund durchführt, verlieren Sie die Angst vor Konflikten und können eine saubere, nachvollziehbare Historie aufbauen.&lt;/p>
&lt;hr>
&lt;h1 id="2-interne-struktur-von-git-commit-hashes-und-objektmodell">2. Interne Struktur von Git: Commit-Hashes und Objektmodell
&lt;/h1>&lt;p>Um zu verstehen, wie Git die Historie integriert, müssen wir zunächst wissen, wie Git Daten speichert. Git speichert nicht einfach nur die Unterschiede (Patches) von Dateiänderungen, sondern einen Schnappschuss (Snapshot) des gesamten Dateisystems zu einem bestimmten Zeitpunkt.&lt;/p>
&lt;h2 id="21-kryptographische-eigenschaften-von-commit-hashes">2.1 Kryptographische Eigenschaften von Commit-Hashes
&lt;/h2>&lt;p>Jeder Commit in Git wird durch eine 40-stellige Hexadezimalzahl, die mit der Hash-Funktion SHA-1 (Secure Hash Algorithm 1) basierend auf seinem Inhalt berechnet wird, eindeutig identifiziert. Ein Commit-Objekt besteht aus den folgenden Elementen:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Zeiger auf das Tree-Objekt&lt;/strong>: Ein Schnappschuss der Verzeichnisstruktur und der Dateien (Blobs) zu diesem Zeitpunkt.&lt;/li>
&lt;li>&lt;strong>Zeiger auf den Eltern-Commit&lt;/strong>: Die Hash-Werte eines oder mehrerer Eltern-Commits (der erste Commit hat keinen Elternteil, ein Merge-Commit hat zwei oder mehr).&lt;/li>
&lt;li>&lt;strong>Autor-Informationen (Author)&lt;/strong>: Die Person, die den Code geschrieben hat, und das Datum.&lt;/li>
&lt;li>&lt;strong>Committer-Informationen (Committer)&lt;/strong>: Die Person, die den Commit erstellt/angewendet hat, und das Datum.&lt;/li>
&lt;li>&lt;strong>Commit-Nachricht&lt;/strong>: Text, der die Absicht der Änderung erklärt.&lt;/li>
&lt;/ol>
&lt;p>Mathematisch ausgedrückt wird der Hash-Wert $H(C)$ für ein Commit-Objekt $C$ wie folgt definiert:&lt;/p>
$$
H(C) = \text{SHA-1}( \text{tree} \parallel \text{parent} \parallel \text{author} \parallel \text{committer} \parallel \text{message} )
$$&lt;p>Hierbei steht $\parallel$ für die Verkettung von Daten. Aufgrund der Eigenschaften der Hash-Funktion führt selbst die Änderung eines einzigen Zeichens in der Commit-Nachricht oder ein anderer Eltern-Commit zu einem völlig anderen Hash-Wert. Das bedeutet: &lt;strong>Commits sind unveränderlich (immutable)&lt;/strong>. Wenn später gesagt wird, dass &lt;code>rebase&lt;/code> &amp;ldquo;die Historie umschreibt&amp;rdquo;, so bedeutet das in Wirklichkeit, dass &amp;ldquo;neue Commits erstellt werden, die einen ähnlichen Inhalt, aber unterschiedliche Hash-Werte haben&amp;rdquo;.&lt;/p>
&lt;p>Die Größe des Hash-Raums beträgt $2^{160}$. Die Wahrscheinlichkeit $P$, dass eine Kollision auftritt (dass verschiedene Commits denselben Hash-Wert haben), kann mithilfe der Theorie des Geburtstagsparadoxons (Birthday Paradox) wie folgt angenähert werden (wobei $n$ die Anzahl der Commits ist):&lt;/p>
$$
P(\text{collision}) \approx 1 - \exp\left(-\frac{n^2}{2 \times 2^{160}}\right)
$$&lt;p>Diese Wahrscheinlichkeit ist extrem gering, und in der Praxis ist es nahezu unmöglich, dass Git-Commit-Hashes kollidieren.&lt;/p>
&lt;hr>
&lt;h1 id="3-graphentheorie-und-dag-das-mathematische-modell-der-git-historie">3. Graphentheorie und DAG: Das mathematische Modell der Git-Historie
&lt;/h1>&lt;p>Die Commit-Historie von Git wird als „gerichteter azyklischer Graph“ (Directed Acyclic Graph, DAG) aus der Graphentheorie modelliert.&lt;/p>
&lt;h2 id="31-was-ist-ein-dag-gerichteter-azyklischer-graph">3.1 Was ist ein DAG (gerichteter azyklischer Graph)?
&lt;/h2>&lt;p>In einem Graphen $G = (V, E)$ ist $V$ die Menge der Commits (Knoten) und $E$ die Menge der gerichteten Kanten, die die Eltern-Kind-Beziehungen zwischen den Commits darstellen. In Git ist die Richtung der Kanten „vom Kind-Commit zum Eltern-Commit“ gerichtet, da neue Commits Zeiger auf vergangene Commits speichern.&lt;/p>
&lt;pre class="mermaid">
graph BT
A[&amp;#34;Commit A (Initial)&amp;#34;]
B[&amp;#34;Commit B&amp;#34;]
C[&amp;#34;Commit C (Main)&amp;#34;]
D[&amp;#34;Commit D (Feature)&amp;#34;]
E[&amp;#34;Commit E (Merge)&amp;#34;]
B --&amp;gt; A
C --&amp;gt; B
D --&amp;gt; B
E --&amp;gt; C
E --&amp;gt; D
&lt;/pre>
&lt;p>Das Hauptmerkmal eines DAG ist, dass „keine Zyklen (Schleifen) existieren“. Dadurch geraten Algorithmen, die die Commit-Historie zurückverfolgen, nie in eine Endlosschleife und erreichen sicher das Ende (den initialen Commit).&lt;/p>
&lt;h2 id="32-topologische-sortierung-und-die-reihenfolge-der-historie">3.2 Topologische Sortierung und die Reihenfolge der Historie
&lt;/h2>&lt;p>Wenn Git die Historie mit Befehlen wie &lt;code>git log&lt;/code> anzeigt, wird der DAG durch den Algorithmus der topologischen Sortierung (Topological Sort) als eindimensionale Liste geordnet. Für jede gerichtete Kante $u \to v$ im DAG ($u$ ist das Kind von $v$) wird die Liste so sortiert, dass $u$ vor $v$ steht.&lt;/p>
&lt;hr>
&lt;h1 id="4-der-mechanismus-und-die-arten-von-git-merge">4. Der Mechanismus und die Arten von git merge
&lt;/h1>&lt;p>Der grundlegendste Befehl zur Integration von Branch-Änderungen ist &lt;code>git merge&lt;/code>. Je nach aktuellem Zustand wählt Git jedoch automatisch unterschiedliche Merge-Strategien aus.&lt;/p>
&lt;h2 id="41-fast-forward-merge---ff">4.1 Fast-Forward-Merge (&amp;ndash;ff)
&lt;/h2>&lt;p>Wenn der Ziel-Branch (z. B. &lt;code>main&lt;/code>) ein direkter Vorfahre des Quell-Branch (z. B. &lt;code>feature&lt;/code>) ist, führt Git einen „Fast-Forward“-Merge (Vorspulen) aus. Dabei wird kein neuer Commit erstellt, sondern lediglich der Branch-Zeiger nach vorne verschoben.&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;A&amp;#34;
commit id: &amp;#34;B&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;C&amp;#34;
commit id: &amp;#34;D&amp;#34;
checkout main
merge feature
&lt;/pre>
&lt;p>Ein Fast-Forward-Merge hält die Historie geradlinig, hat aber den Nachteil, dass der Kontext verloren geht, „welche Commit-Gruppe als eine einzige Feature-Entwicklung zusammengehörte“.&lt;/p>
&lt;h2 id="42-non-fast-forward-merge---no-ff">4.2 Non-Fast-Forward-Merge (&amp;ndash;no-ff)
&lt;/h2>&lt;p>Wenn Sie explizit &lt;code>git merge --no-ff&lt;/code> angeben, wird in jedem Fall ein neuer „Merge-Commit“ erstellt, selbst wenn ein Fast-Forward möglich wäre. Ein Merge-Commit ist ein spezieller Commit mit zwei Elternteilen.&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;A&amp;#34;
commit id: &amp;#34;B&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;C&amp;#34;
commit id: &amp;#34;D&amp;#34;
checkout main
commit id: &amp;#34;Hauptarbeit 1&amp;#34;
merge feature type: NORMAL
&lt;/pre>
&lt;p>Der Vorteil dieser Methode besteht darin, dass die Existenz und die Geschichte des Feature-Branches deutlich im DAG erhalten bleiben. Wenn ein Problem auftritt, kann durch Ausführen von &lt;code>git revert -m 1 &amp;lt;Hash des Merge-Commits&amp;gt;&lt;/code> das gesamte Feature auf einmal sicher rückgängig gemacht (reverted) werden.&lt;/p>
&lt;h2 id="43-3-way-merge-algorithmus-3-wege-merge">4.3 3-Way-Merge-Algorithmus (3-Wege-Merge)
&lt;/h2>&lt;p>Wenn der Ziel- und der Quell-Branch jeweils eigene Commits haben, führt Git einen 3-Way-Merge durch. Dabei durchsucht Git den DAG, um den „nächsten gemeinsamen Vorfahren“ (Lowest Common Ancestor, LCA) der beiden Branches zu finden.&lt;/p>
&lt;p>Die zeitliche Komplexität $T_{\text{LCA}}$ des Algorithmus zum Finden des LCA ist linear in Bezug auf die Anzahl der Knoten $|V|$ und Kanten $|E|$:&lt;/p>
$$
T_{\text{LCA}} = \mathcal{O}(|V| + |E|)
$$&lt;p>Git vergleicht den „Zustand des LCA“, den „Zustand des aktuellen Branches“ und den „Zustand des anderen Branches“. Wenn die Änderungen nicht im Konflikt stehen, wird automatisch ein Merge-Commit generiert.&lt;/p>
&lt;hr>
&lt;h1 id="5-der-mechanismus-von-git-rebase-und-die-rekonstruktion-der-historie">5. Der Mechanismus von git rebase und die Rekonstruktion der Historie
&lt;/h1>&lt;p>Während &lt;code>git merge&lt;/code> die Historie „integriert“, wird sie bei &lt;code>git rebase&lt;/code> „rekonstruiert“ (neu angesetzt).&lt;/p>
&lt;h2 id="51-die-vorgänge-hinter-rebase">5.1 Die Vorgänge hinter Rebase
&lt;/h2>&lt;p>Die internen Vorgänge beim Rebasen eines &lt;code>feature&lt;/code>-Branches auf den &lt;code>main&lt;/code>-Branch (&lt;code>git rebase main&lt;/code>) sind wie folgt:&lt;/p>
&lt;ol>
&lt;li>Finde den gemeinsamen Vorfahren (LCA) des &lt;code>feature&lt;/code>-Branches und des &lt;code>main&lt;/code>-Branches.&lt;/li>
&lt;li>Speichere die Diffs (Änderungen) der Commits vom LCA bis zur Spitze des &lt;code>feature&lt;/code>-Branches in einem temporären Bereich.&lt;/li>
&lt;li>Verschiebe den Zeiger des &lt;code>feature&lt;/code>-Branches auf die Spitze des &lt;code>main&lt;/code>-Branches.&lt;/li>
&lt;li>Wende die gespeicherten Diffs der Reihe nach auf die neue Basis (die Spitze von &lt;code>main&lt;/code>) an (Cherry-Pick) und generiere so neue Commits.&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Commit A&amp;#34;] --&amp;gt; B[&amp;#34;Commit B&amp;#34;]
B --&amp;gt; C[&amp;#34;Commit C (Main)&amp;#34;]
B --&amp;gt; D[&amp;#34;Commit D (Altes Feature)&amp;#34;]
D -.-&amp;gt; E[&amp;#34;Commit D&amp;#39; (Neues Feature)&amp;#34;]
C --&amp;gt; E
style D stroke-dasharray: 5 5, fill: #f9f9f9, color: #999
&lt;/pre>
&lt;p>Hierbei ist wichtig, dass der durch den Rebase erstellte Commit $D'$ einen &lt;strong>anderen Eltern-Commit als der ursprüngliche Commit $D$ hat und somit einen völlig anderen Hash-Wert besitzt&lt;/strong> (siehe Definition der Hash-Funktion $H(C)$ weiter oben).&lt;/p>
&lt;h2 id="52-interaktives-rebase-interactive-rebase">5.2 Interaktives Rebase (Interactive Rebase)
&lt;/h2>&lt;p>Mit &lt;code>git rebase -i&lt;/code> (oder &lt;code>--interactive&lt;/code>) können Sie die Commit-Historie ganz nach Ihren Wünschen bearbeiten. Dies ist das mächtigste Werkzeug, um die lokale Historie zu bereinigen.&lt;/p>
&lt;ul>
&lt;li>&lt;code>pick&lt;/code>: Den Commit so übernehmen, wie er ist.&lt;/li>
&lt;li>&lt;code>reword&lt;/code>: Nur die Commit-Nachricht bearbeiten.&lt;/li>
&lt;li>&lt;code>edit&lt;/code>: Den Vorgang pausieren, um den Inhalt des Commits zu bearbeiten.&lt;/li>
&lt;li>&lt;code>squash&lt;/code>: Diesen Commit mit dem vorherigen Commit verschmelzen und auch die Nachrichten kombinieren.&lt;/li>
&lt;li>&lt;code>fixup&lt;/code>: Wie &lt;code>squash&lt;/code>, jedoch wird die Nachricht dieses Commits verworfen.&lt;/li>
&lt;li>&lt;code>drop&lt;/code>: Den Commit vollständig löschen.&lt;/li>
&lt;/ul>
&lt;p>Aus mathematischer Sicht ergeben sich bei $N$ Commits in einem Branch folgende Variationen (Permutationen) $P$ an möglichen linearen Historien, die durch Ändern der Reihenfolge beim Rebase generiert werden können:&lt;/p>
$$
P = N!
$$&lt;p>Git gibt Entwicklern $N!$ Möglichkeiten und erlaubt es so, die Historie in einem logischen und aufgeräumten Zustand zu halten.&lt;/p>
&lt;hr>
&lt;h1 id="6-die-goldene-regel-des-rebasens-the-golden-rule-of-rebase">6. Die Goldene Regel des Rebasens (The Golden Rule of Rebase)
&lt;/h1>&lt;p>&lt;code>rebase&lt;/code> ist extrem mächtig, aber es gibt eine absolute Regel:&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>„Rebasen Sie niemals eine veröffentlichte, öffentliche Historie!“&lt;/strong>
&lt;em>(Never rebase public history)&lt;/em>&lt;/p>
&lt;/blockquote>
&lt;h2 id="61-warum-darf-man-eine-öffentliche-historie-nicht-rebasen">6.1 Warum darf man eine öffentliche Historie nicht rebasen?
&lt;/h2>&lt;p>Git ist dezentralisiert. Die Commits, die Sie nach &lt;code>origin/main&lt;/code> pushen, werden auch in die lokalen Repositories anderer Entwickler geklont (kopiert). Was passiert, wenn Sie einen bereits gepushten Commit rebasen, die Historie neu schreiben und diese mit &lt;code>git push --force&lt;/code> erzwingen?&lt;/p>
&lt;p>Der DAG in den lokalen Repositories der anderen Entwickler weicht grundlegend vom DAG auf dem Remote-Server ab. Wenn ein anderer Entwickler &lt;code>git pull&lt;/code> ausführt, wird Git versuchen, Commits mit unterschiedlicher Historie gewaltsam zusammenzuführen. Dies führt zu massiven Konflikten und doppelten Commits (Commits mit gleichem Inhalt, aber unterschiedlichen Hashes), was das Repository in einen chaotischen Zustand versetzt.&lt;/p>
&lt;p>Die eiserne Regel lautet: Führen Sie Rebase &lt;strong>nur auf „lokalen Branches, die noch mit niemandem geteilt wurden“&lt;/strong> aus.&lt;/p>
&lt;hr>
&lt;h1 id="7-konfliktauflösung-und-git-rebase---continue">7. Konfliktauflösung und git rebase &amp;ndash;continue
&lt;/h1>&lt;p>Wenn mehrere Personen dieselbe Stelle in derselben Datei ändern, entsteht ein Konflikt (Conflict). Bei &lt;code>merge&lt;/code> und &lt;code>rebase&lt;/code> unterscheidet sich der Prozess der Konfliktauflösung.&lt;/p>
&lt;h2 id="71-konfliktauflösung-beim-merge">7.1 Konfliktauflösung beim Merge
&lt;/h2>&lt;p>Bei &lt;code>git merge&lt;/code> tritt die Konfliktauflösung &lt;strong>nur einmal&lt;/strong> auf. Kurz bevor der endgültige Merge-Commit erstellt wird, werden alle Konflikte auf einmal behoben.&lt;/p>
&lt;h2 id="72-konfliktauflösung-beim-rebase">7.2 Konfliktauflösung beim Rebase
&lt;/h2>&lt;p>Aufgrund der Eigenschaft von &lt;code>git rebase&lt;/code>, Commits nacheinander neu anzuwenden, besteht &lt;strong>die Möglichkeit, dass bei jedem einzelnen Commit ein Konflikt auftritt&lt;/strong>.&lt;/p>
&lt;p>Wenn während eines Rebase ein Konflikt auftritt, pausiert Git den Vorgang. Der Ablauf zur Lösung sieht wie folgt aus:&lt;/p>
&lt;ol>
&lt;li>Öffnen Sie einen Editor oder eine IDE (wie VS Code) und beheben Sie die Konfliktmarkierungen (&lt;code>&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/code>, &lt;code>======&lt;/code> und &lt;code>&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code>) manuell.&lt;/li>
&lt;li>Fügen Sie die bearbeitete Datei dem Index hinzu:
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git add &amp;lt;bearbeitete Datei&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>Setzen Sie den Rebase-Vorgang fort, ohne einen neuen Commit zu erstellen:
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git rebase --continue
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>Wenn Sie den Rebase komplett abbrechen und in den ursprünglichen Zustand zurückkehren möchten, führen Sie folgenden Befehl aus:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git rebase --abort
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>(&lt;em>Wenn keine Konfliktauflösung erforderlich ist und Sie den gesamten Commit überspringen möchten, verwenden Sie &lt;code>git rebase --skip&lt;/code>.&lt;/em>)&lt;/p>
&lt;hr>
&lt;h1 id="8-der-richtige-einsatz-in-der-praxis-workflow-praxis">8. Der richtige Einsatz in der Praxis (Workflow-Praxis)
&lt;/h1>&lt;p>Wie sollten Sie nun in der tatsächlichen Entwicklungsumgebung zwischen &lt;code>merge&lt;/code> und &lt;code>rebase&lt;/code> wählen? Hier stellen wir den standardmäßigsten und sichersten Ansatz vor.&lt;/p>
&lt;h2 id="81-szenario-1-bereinigung-der-lokalen-arbeitshistorie-verwendung-von-rebase">8.1 [Szenario 1] Bereinigung der lokalen Arbeitshistorie (Verwendung von Rebase)
&lt;/h2>&lt;p>Angenommen, Sie haben während der Entwicklung in einem Feature-Branch viele kleine Commits angesammelt (z. B. &amp;ldquo;Tippfehler behoben&amp;rdquo;, &amp;ldquo;Temporär gespeichert&amp;rdquo; usw.). Bevor Sie einen Pull Request (PR) erstellen, nutzen Sie interaktives Rebase, um diese zu sinnvollen Einheiten zusammenzufassen.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Ausführen, während Sie sich im feature-Branch befinden&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase -i HEAD~5
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># (Ein Editor öffnet sich; nutzen Sie squash oder fixup, um die Historie zu bereinigen)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dadurch können Sie eine saubere Commit-Historie erstellen, bei der die Absichten für den Reviewer leichter nachvollziehbar sind.&lt;/p>
&lt;h2 id="82-szenario-2-aktualisierung-auf-den-neuesten-main-branch-verwendung-von-rebase">8.2 [Szenario 2] Aktualisierung auf den neuesten main-Branch (Verwendung von Rebase)
&lt;/h2>&lt;p>Wenn die Entwicklung länger dauert und fortlaufend Änderungen anderer Personen in den &lt;code>main&lt;/code>-Branch gemergt werden, wird Ihr eigener &lt;code>feature&lt;/code>-Branch veraltet sein. In diesem Fall aktualisieren Sie Ihren &lt;code>feature&lt;/code>-Branch, indem Sie ihn auf den neuesten Stand von &lt;code>main&lt;/code> rebasen.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Die neuesten Informationen von main abrufen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git fetch origin
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Den feature-Branch auf den neuesten main-Branch aufsetzen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git rebase origin/main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dadurch bleibt die Historie linear, und Sie können Konflikte bei späteren Merges vermeiden. Außerdem wird verhindert, dass überflüssige Merge-Commits (&amp;ldquo;Merge branch &amp;lsquo;main&amp;rsquo; into feature&amp;rdquo;) erstellt werden.&lt;/p>
&lt;h2 id="83-szenario-3-integration-abgeschlossener-features-verwendung-von-merge">8.3 [Szenario 3] Integration abgeschlossener Features (Verwendung von Merge)
&lt;/h2>&lt;p>Wenn die Entwicklung im &lt;code>feature&lt;/code>-Branch abgeschlossen ist, kommt die Phase der Integration in den &lt;code>main&lt;/code>-Branch. Hier verwenden Sie &lt;strong>&lt;code>git merge --no-ff&lt;/code>&lt;/strong> (das entspricht der Auswahl von &amp;ldquo;Create a merge commit&amp;rdquo; bei einem Pull Request auf GitHub).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">git checkout main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git merge --no-ff feature -m &lt;span class="s2">&amp;#34;Merge feature: Implementierung der Benutzer-Login-Funktion&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git push origin main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dadurch bleibt im DAG des &lt;code>main&lt;/code>-Branches ein Knotenpunkt in der Historie (Merge-Commit) erhalten, der besagt: „Hier wurde ein Feature gemergt“. Wenn Sie später die Historie betrachten, können Sie den Code pro Feature-Einheit viel leichter nachvollziehen.&lt;/p>
&lt;hr>
&lt;h1 id="9-fazit-zusammenfassung">9. Fazit (Zusammenfassung)
&lt;/h1>&lt;p>Bei der Arbeit mit Git haben extreme Ansätze wie „Alles mit Merge erledigen“ oder „Alles mit Rebase linear halten“ jeweils ihre Vor- und Nachteile.&lt;/p>
&lt;p>Die Best Practice in der Praxis ist ein hybrider Ansatz: &lt;strong>„Nutzen Sie rebase, um die private lokale Historie schön aufzuräumen, und verwenden Sie merge &amp;ndash;no-ff für die öffentliche Integrationshistorie, um den Kontext zu erhalten.“&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Lokal (persönlicher Arbeitsbereich)&lt;/strong>: Verwenden Sie &lt;code>rebase&lt;/code>, um unnötige Commits zu eliminieren, der neuesten Mainline zu folgen und eine lineare Historie beizubehalten.&lt;/li>
&lt;li>&lt;strong>Global (geteilter Arbeitsbereich)&lt;/strong>: Verwenden Sie &lt;code>merge --no-ff&lt;/code>, um die Existenz des Feature-Branches als Merge-Commit im DAG aufzuzeichnen und so Reverts und Tracking zu erleichtern.&lt;/li>
&lt;/ul>
&lt;p>Durch das Verständnis des mathematischen und architektonischen Hintergrunds, wie der Struktur des DAGs und der Funktionsweise von Hash-Funktionen, werden Git-Befehle von purem Auswendiglernen zum „Entwurf einer Historie mit Absicht“ erhoben. Beachten Sie die Goldene Regel des Rebasens, wählen Sie je nach Situation den optimalen Befehl und bauen Sie eine saubere Commit-Historie auf, die für das gesamte Team leicht lesbar und wartbar ist.&lt;/p></description></item><item><title>Häufige Fehler von Git-Anfängern und eine Sammlung von Lösungsbefehlen (Konfliktlösung etc.)</title><link>http://kenji.blog/de/p/git-beginners-mistakes-and-solutions/</link><pubDate>Sat, 12 Sep 2026 17:00:00 +0900</pubDate><guid>http://kenji.blog/de/p/git-beginners-mistakes-and-solutions/</guid><description>&lt;img src="http://kenji.blog/p/git-beginners-mistakes-and-solutions/img/eyecatch.jpg" alt="Featured image of post Häufige Fehler von Git-Anfängern und eine Sammlung von Lösungsbefehlen (Konfliktlösung etc.)" />&lt;h1 id="häufige-fehler-von-git-anfängern-und-eine-sammlung-von-lösungsbefehlen-konfliktlösung-etc">Häufige Fehler von Git-Anfängern und eine Sammlung von Lösungsbefehlen (Konfliktlösung etc.)
&lt;/h1>&lt;h2 id="1-einführung-warum-machen-wir-fehler-in-git">1. Einführung: Warum machen wir Fehler in Git?
&lt;/h2>&lt;p>In der Softwareentwicklung ist Git so unverzichtbar geworden wie Luft oder Wasser. Für viele Anfänger (und manchmal sogar für erfahrene Entwickler) kann sich Git jedoch wie eine „furchteinflößende, magische Blackbox“ anfühlen. Commits verschwinden, eine große Menge an Änderungen wird versehentlich in den falschen Branch gepusht, oder bisher unbekannte Fehlermeldungen bei Konflikten überschwemmen den Bildschirm&amp;hellip; Wenn man in solche „Git-Fallen“ tappt, kommt der Arbeitsfortschritt oft komplett zum Erliegen, und im schlimmsten Fall fürchtet man, den Quellcode zu zerstören.&lt;/p>
&lt;p>Warum ist Git so schwierig und fehleranfällig? Der Hauptgrund dafür ist: „Man lernt nur oberflächliche Befehle auswendig und wendet sie an, ohne zu verstehen, was im Inneren von Git passiert.“ Git basiert auf einer robusten Designphilosophie als verteiltes Versionskontrollsystem (DVCS), aber seine Benutzeroberfläche (CLI) ist nicht immer intuitiv.&lt;/p>
&lt;p>Dieser Artikel kategorisiert zahlreiche „Missgeschicke (häufige Fehler)“, auf die Git-Anfänger in der Praxis häufig stoßen, und präsentiert konkrete Lösungsbefehle für jeden Fall. Es wird jedoch nicht nur eine einfache Liste von Befehlen (Cheat Sheet). Wir werden tiefgründig erklären, „warum der Fehler auftritt“ und „wie sich die Daten innerhalb von Git bewegen, wenn man diesen Befehl ausführt“, einschließlich der internen Struktur des &lt;code>.git&lt;/code>-Verzeichnisses, des mathematischen Hintergrunds des dahinter arbeitenden Diff-Algorithmus sowie Mermaid-Diagrammen, auf mehr als 10.000 Zeichen.&lt;/p>
&lt;p>Wenn Sie diesen Artikel bis zum Ende gelesen haben, sollten Sie von der Angst vor Git befreit sein und stattdessen davon überzeugt sein, dass „es keinen zuverlässigeren Partner als Git gibt“. Lassen Sie uns also in die tiefe Welt von Git eintauchen.&lt;/p>
&lt;hr>
&lt;h2 id="2-der-abgrund-von-git-die-interne-struktur-des-git-verzeichnisses-verstehen">2. Der Abgrund von Git: Die interne Struktur des &lt;code>.git&lt;/code>-Verzeichnisses verstehen
&lt;/h2>&lt;p>Der erste Schritt zur Erleichterung vieler Fehlerbehebungen besteht darin, zu verstehen, wie Git Daten speichert. Der versteckte Ordner &lt;code>.git&lt;/code> im Stammverzeichnis Ihres Projekts ist das Herzstück von Git. Git ist kein System, das einfach Dateidifferenzen (Patches) der Reihe nach aufzeichnet, sondern verwaltet die Daten als einen &lt;strong>Stream von Snapshots&lt;/strong>.&lt;/p>
&lt;h3 id="21-das-objektmodell-blob-tree-commit">2.1 Das Objektmodell: Blob, Tree, Commit
&lt;/h3>&lt;p>Git verwendet hauptsächlich drei Objekte, um den Status des Repositorys darzustellen. Diese Objekte werden in &lt;code>.git/objects&lt;/code> gespeichert.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Blob (Binary Large Object)&lt;/strong>
Dieses Objekt speichert den eigentlichen Inhalt der Datei. Informationen wie Dateinamen oder Berechtigungen sind hier nicht enthalten. Reine Bytefolgen werden mit zlib komprimiert und durch einen SHA-1-Hashwert (40-stellige hexadezimale Zahl) identifiziert.&lt;/li>
&lt;li>&lt;strong>Tree&lt;/strong>
Dieses Objekt stellt die Verzeichnisstruktur dar. Ein Tree-Objekt enthält Zeiger (SHA-1-Hashwerte) auf andere Tree-Objekte (Unterverzeichnisse) oder Blob-Objekte (Dateien) sowie deren Dateinamen und Zugriffsrechte. Es fungiert ähnlich wie ein UNIX-Verzeichnis.&lt;/li>
&lt;li>&lt;strong>Commit&lt;/strong>
Dies enthält einen Zeiger auf das oberste Tree-Objekt des gesamten Repositorys zu einem bestimmten Zeitpunkt, zusammen mit Metadaten (Autor, Commit-Datum, Commit-Nachricht) sowie einem Zeiger auf den vorherigen Commit (Eltern-Commit).&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
graph TD
Commit1[&amp;#34;Commit (Hash: 9f8a)&amp;#34;] --&amp;gt; Tree1[&amp;#34;Tree (Hash: 4b82)&amp;#34;]
Tree1 --&amp;gt; Blob1[&amp;#34;Blob (Hash: 8d7e) : index.js&amp;#34;]
Tree1 --&amp;gt; Tree2[&amp;#34;Tree (Hash: 3a2c) : src/&amp;#34;]
Tree2 --&amp;gt; Blob2[&amp;#34;Blob (Hash: 5f1b) : app.js&amp;#34;]
&lt;/pre>
&lt;h3 id="22-die-wahre-identität-von-head-und-referenzen-refs">2.2 Die wahre Identität von HEAD und Referenzen (Refs)
&lt;/h3>&lt;p>Das Wort &lt;code>HEAD&lt;/code>, das man bei der Arbeit mit Git häufig sieht, ist eine &lt;strong>symbolische Referenz (Symbolic Reference)&lt;/strong>, die auf den aktuell ausgecheckten Branch (oder Commit) zeigt.
Wenn Sie die Datei &lt;code>.git/HEAD&lt;/code> in einem Texteditor öffnen, sehen Sie eine Zeichenfolge wie diese:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">ref: refs/heads/main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Dies bedeutet: „Der aktuelle Status befindet sich an der Spitze des &lt;code>main&lt;/code>-Branches.“ Wenn Sie dann &lt;code>.git/refs/heads/main&lt;/code> öffnen, finden Sie dort einen 40-stelligen SHA-1-Hash, der auf das neueste Commit-Objekt zeigt.
Ein Git-Branch ist lediglich ein leichtgewichtiger Zeiger (eine Datei), der auf einen bestimmten Commit zeigt. Wenn man diese Tatsache kennt, verschwindet die Angst, dass „alle Dateien gelöscht werden, wenn ich den Branch lösche“.&lt;/p>
&lt;hr>
&lt;h2 id="3-git-durch-mathematik-entschlüsseln-diff-algorithmen-und-hash-funktionen">3. Git durch Mathematik entschlüsseln: Diff-Algorithmen und Hash-Funktionen
&lt;/h2>&lt;p>Wenn Git Konflikte erkennt oder Dateiunterschiede anzeigt, laufen im Hintergrund fortschrittliche Algorithmen ab.&lt;/p>
&lt;h3 id="31-myers-diff-algorithmus">3.1 Myers Diff-Algorithmus
&lt;/h3>&lt;p>Der standardmäßige Diff-Algorithmus in Git wurde von Eugene W. Myers entwickelt. Wenn wir zwei Textdateien $A$ und $B$ haben, kann das Problem, die „minimale Folge von Bearbeitungsschritten (Einfügungen und Löschungen)“ zu finden, um $A$ in $B$ umzuwandeln, als Problem des kürzesten Pfades in der Graphentheorie modelliert werden.&lt;/p>
&lt;p>Seien die Längen der Zeichenfolgen $N$ bzw. $M$, und die Summe $V = N + M$. Der Myers-Algorithmus sucht nach der Bearbeitungsdistanz (Edit Distance) $D$. Die Zeitkomplexität dieses Algorithmus wird durch folgende Formel ausgedrückt:&lt;/p>
$$ \mathcal{O}(V \cdot D) $$&lt;p>Wenn die Differenz zwischen den Dateien klein ist (d. h. $D$ klein ist), arbeitet der Algorithmus sehr schnell mit $\mathcal{O}(V)$. Wenn die Dateien jedoch völlig unterschiedlich sind, gilt $D \approx V$, und die Worst-Case-Zeitkomplexität beträgt $\mathcal{O}(V^2)$.&lt;/p>
&lt;h3 id="32-patience-diff-und-histogram-diff">3.2 Patience Diff und Histogram Diff
&lt;/h3>&lt;p>Obwohl der Myers-Algorithmus hervorragend ist, kann er Unterschiede generieren, die für Menschen nicht intuitiv (sinnlos) sind, z. B. wenn die Reihenfolge von Funktionen oder Klassen stark geändert wird. Um dieses Problem zu lösen, implementiert Git &lt;code>Patience Diff&lt;/code> und &lt;code>Histogram Diff&lt;/code>.&lt;/p>
&lt;p>Patience Diff konzentriert sich auf „eindeutige Zeilen, die in beiden Dateien genau einmal vorkommen“ und findet deren längste gemeinsame Teilsequenz (Longest Common Subsequence: LCS). Wenn die Anzahl der eindeutigen Elemente $U$ ist, kann die Berechnung der LCS mit folgender Zeitkomplexität gelöst werden:&lt;/p>
$$ \mathcal{O}(U \log U) $$&lt;p>Wenn Sie das Gefühl haben, dass die Konfliktlösung schwierig ist, ist es eine Option, &lt;code>git diff --histogram&lt;/code> zu verwenden oder diesen Algorithmus für die Merge-Strategie anzugeben (&lt;code>git merge -s recursive -X histogram&lt;/code>).&lt;/p>
&lt;h3 id="33-sha-1-und-kollisionswahrscheinlichkeit">3.3 SHA-1 und Kollisionswahrscheinlichkeit
&lt;/h3>&lt;p>Git verwaltet alle Objekte mit SHA-1-Hashwerten. Die Größe des Hashraums beträgt $2^{160}$. Bei einer Annäherung an die Wahrscheinlichkeit von Hash-Kollisionen (wenn unterschiedliche Inhalte denselben Hashwert haben) unter Verwendung des Geburtstagsparadoxons (Birthday Paradox) ist die Anzahl der Objekte $k$, die erforderlich ist, damit die Kollisionswahrscheinlichkeit $p$ 50% erreicht, wie folgt:&lt;/p>
$$ k \approx \sqrt{2 \ln(2)} \cdot 2^{80} \approx 1.2 \times 2^{80} $$&lt;p>Dies ist eine astronomische Zahl, und die Wahrscheinlichkeit, dass bei normaler Softwareentwicklung unbeabsichtigt eine Kollision auftritt, ist praktisch null. Daher arbeitet Git mit der Zuversicht, dass Hashwerte „absolute und eindeutige IDs“ sind.&lt;/p>
&lt;hr>
&lt;h2 id="4-fallstudie-1-auf-den-falschen-branch-committet">4. Fallstudie 1: Auf den falschen Branch committet!
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
Sie haben nicht bemerkt, dass Sie im &lt;code>main&lt;/code>-Branch arbeiten, haben zügig Code für eine neue Funktion geschrieben und, man glaubt es kaum, sogar &lt;code>git commit&lt;/code> ausgeführt. Dabei sollten Sie eigentlich einen Branch namens &lt;code>feature/login&lt;/code> erstellen und dort arbeiten!&lt;/p>
&lt;h3 id="lösung-git-reset-und-branch-erstellung">Lösung: &lt;code>git reset&lt;/code> und Branch-Erstellung
&lt;/h3>&lt;p>In Git sind Commits unabhängige Objekte, und Branches sind nur Zeiger. Daher kann dieses Problem sofort mit der Operation „Einen neuen Branch erstellen und dann den Zeiger des aktuellen Branches zurückdrehen“ gelöst werden.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. Einen neuen Branch erstellen, der auf den aktuellen Commit (den versehentlich erstellten) zeigt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git branch feature/login
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. Den Zeiger des main-Branches auf den vorherigen Commit (HEAD~1) zurücksetzen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Die Verwendung von --keep ermöglicht ein sicheres Zurücksetzen, während die nicht committeten Änderungen im Arbeitsverzeichnis beibehalten werden.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git reset --keep HEAD~1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. Zum richtigen Branch wechseln&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout feature/login
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="illustration-was-ist-intern-passiert">Illustration: Was ist intern passiert?
&lt;/h3>&lt;p>Lassen Sie uns die Verschiebung der Branch-Zeiger in diesem Moment mit &lt;code>gitGraph&lt;/code> von Mermaid visualisieren.&lt;/p>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;Initialer Commit&amp;#34;
commit id: &amp;#34;Bugfix&amp;#34;
commit id: &amp;#34;Falscher Commit&amp;#34; type: HIGHLIGHT
branch feature/login
checkout feature/login
checkout main
&lt;/pre>
&lt;p>Zunächst zeigten &lt;code>main&lt;/code> und &lt;code>HEAD&lt;/code> auf den &amp;ldquo;Falscher Commit&amp;rdquo;, aber mit &lt;code>git branch feature/login&lt;/code> wurde dort ein neuer Zeiger erstellt. Danach springt durch &lt;code>git reset&lt;/code> nur der &lt;code>main&lt;/code>-Zeiger zurück auf die Position &amp;ldquo;Bugfix&amp;rdquo;. Die Objekte selbst werden überhaupt nicht gelöscht.&lt;/p>
&lt;hr>
&lt;h2 id="5-fallstudie-2-ich-möchte-einen-bereits-gepushten-commit-rückgängig-machen">5. Fallstudie 2: Ich möchte einen bereits gepushten Commit rückgängig machen!
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
In der späten Nacht haben Sie verbuggten Code committet und diesen zudem mit &lt;code>git push origin main&lt;/code> im Remote-Repository veröffentlicht. Sie bemerken den schwerwiegenden Bug und erblassen.&lt;/p>
&lt;h3 id="lösung-1-die-historie-negieren-mit-git-revert-empfohlensicher">Lösung 1: Die Historie negieren mit &lt;code>git revert&lt;/code> (Empfohlen/Sicher)
&lt;/h3>&lt;p>Bei der Teamentwicklung ist es strengstens verboten, die Historie von Commits, die bereits gepusht wurden, mit Befehlen wie &lt;code>git reset&lt;/code> zu verändern. Dies führt zu Inkonsistenzen mit den lokalen Repositories anderer Entwickler. Der richtige Ansatz ist, &lt;strong>„einen neuen Commit zu erstellen, der die Änderungen des fehlerhaften Commits vollständig umkehrt“&lt;/strong>. Das macht &lt;code>git revert&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Einen Commit erstellen, der den neuesten Commit rückgängig macht&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git revert HEAD
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>main 7f3a8b2&lt;span class="o">]&lt;/span> Revert &lt;span class="s2">&amp;#34;Falsche Commit-Nachricht&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="m">1&lt;/span> file changed, &lt;span class="m">1&lt;/span> insertion&lt;span class="o">(&lt;/span>+&lt;span class="o">)&lt;/span>, &lt;span class="m">10&lt;/span> deletions&lt;span class="o">(&lt;/span>-&lt;span class="o">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Ins Remote-Repository pushen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git push origin main
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;Commit A&amp;#34;
commit id: &amp;#34;Commit B (Fehler)&amp;#34;
commit id: &amp;#34;Commit B rückgängig machen&amp;#34; type: REVERSE
&lt;/pre>
&lt;p>Die Historie geht weiter, und nur der Zustand des Codes wird zurückgesetzt.&lt;/p>
&lt;h3 id="lösung-2-die-historie-verändern-mit-git-push---force-with-lease">Lösung 2: Die Historie verändern mit &lt;code>git push --force-with-lease&lt;/code>
&lt;/h3>&lt;p>Wenn Sie gerade erst auf einen Branch gepusht haben, den nur Sie allein nutzen, ist es tolerierbar, die Historie umzuschreiben.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Commit lokal zurücksetzen und korrigieren&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git reset --hard HEAD~1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git add .
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git commit -m &lt;span class="s2">&amp;#34;Korrekte Implementierung&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Die Remote-Historie erzwungen überschreiben&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git push origin feature/login --force-with-lease
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>--force-with-lease&lt;/code> ist ein sicherer Force-Push, der Unfälle verhindert, bei denen die Arbeit anderer versehentlich überschrieben wird.&lt;/p>
&lt;hr>
&lt;h2 id="6-fallstudie-3-während-der-arbeit-zu-einem-anderen-branch-wechseln-die-magie-von-stash">6. Fallstudie 3: Während der Arbeit zu einem anderen Branch wechseln (Die Magie von Stash)
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
Sie sind gerade dabei, eine neue Funktion im Branch &lt;code>feature/A&lt;/code> zu implementieren, und der Quellcode befindet sich in einem unvollständigen Zustand, der nicht einmal kompiliert. Plötzlich kommt vom Chef die Anweisung: „Es gibt einen kritischen Bug in der Produktionsumgebung des &lt;code>main&lt;/code>-Branches, bitte beheben Sie ihn sofort!“&lt;/p>
&lt;h3 id="lösung-ausweichen-mit-git-stash">Lösung: Ausweichen mit &lt;code>git stash&lt;/code>
&lt;/h3>&lt;p>&lt;code>git stash&lt;/code> ist ein Befehl, mit dem nicht committete Änderungen vorübergehend in einem temporären Bereich abgelegt werden können.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 1. Die Änderungen in Arbeit speichern (ausweichen)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git stash push -m &lt;span class="s2">&amp;#34;WIP: Feature A teilweise implementiert&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 2. Wechseln zum main-Branch wird möglich&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># ... (Notfall-Bugfix durchführen, committen, pushen) ...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 3. Wenn die Arbeit beendet ist, zum ursprünglichen Branch zurückkehren&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout feature/A
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># 4. Die zurückgestellten Änderungen wiederherstellen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git stash pop
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Wenn Sie &lt;code>git stash&lt;/code> ausführen, generiert Git intern zwei spezielle Commit-Objekte und speichert sie in der Referenz namens &lt;code>refs/stash&lt;/code>. Letztlich ist ein Stash also auch nur ein „temporärer Commit ohne Namen“.&lt;/p>
&lt;hr>
&lt;h2 id="7-fallstudie-4-der-furchteinflößende-zustand-des-detached-head">7. Fallstudie 4: Der furchteinflößende Zustand des &amp;ldquo;Detached HEAD&amp;rdquo;
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
Sie wollten den Code zu einem bestimmten vergangenen Zeitpunkt überprüfen und haben &lt;code>git checkout 9f8a7b6&lt;/code> ausgeführt. Daraufhin wurde &lt;code>You are in 'detached HEAD' state.&lt;/code> angezeigt. Sie haben wie gewohnt weiter committet, aber als Sie den Branch wechselten, waren die Commits plötzlich weg!&lt;/p>
&lt;h3 id="der-mechanismus-von-detached-head">Der Mechanismus von Detached HEAD
&lt;/h3>&lt;p>Normalerweise zeigt &lt;code>HEAD&lt;/code> auf einen Branch wie &lt;code>refs/heads/main&lt;/code>. Wenn Sie jedoch einen bestimmten Commit direkt auschecken, zeigt &lt;code>HEAD&lt;/code> direkt auf das Commit-Objekt. Dies nennt man einen &lt;strong>Detached HEAD (abgetrennter HEAD)&lt;/strong>.&lt;/p>
&lt;pre class="mermaid">
graph TD
A[&amp;#34;Commit A&amp;#34;] --&amp;gt; B[&amp;#34;Commit B&amp;#34;]
B --&amp;gt; C[&amp;#34;Commit C&amp;#34;]
C --&amp;gt; D[&amp;#34;Commit D&amp;#34;]
BranchMain[&amp;#34;Branch: main&amp;#34;] --&amp;gt; D
HEAD[&amp;#34;HEAD&amp;#34;] --&amp;gt; B
style HEAD fill:#f9f,stroke:#333,stroke-width:4px
&lt;/pre>
&lt;p>Selbst wenn Sie in diesem Zustand Commits stapeln, wird kein Branch diese neuen Commits verfolgen. In dem Moment, in dem Sie zu einem anderen Branch wechseln, gehen die neuen Commits verloren.&lt;/p>
&lt;h3 id="lösung-als-neuen-branch-speichern">Lösung: Als neuen Branch speichern
&lt;/h3>&lt;p>Die Lösung besteht darin, an Ihrem aktuellen Standort einen neuen Branch zu erstellen.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Einen neuen Branch an der aktuellen HEAD-Position erstellen und dorthin wechseln&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git checkout -b feature/recovered-work
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;hr>
&lt;h2 id="8-fallstudie-5-konfliktlösung-beim-merge-und-rebase">8. Fallstudie 5: Konfliktlösung beim Merge und Rebase
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
Als Sie &lt;code>git merge&lt;/code> oder &lt;code>git rebase&lt;/code> ausgeführt haben, wurde &lt;code>CONFLICT (content)&lt;/code> angezeigt, und der Prozess wurde abgebrochen.&lt;/p>
&lt;h3 id="der-unterschied-zwischen-merge-und-rebase">Der Unterschied zwischen Merge und Rebase
&lt;/h3>&lt;ol>
&lt;li>&lt;strong>Merge&lt;/strong>
Führt einen 3-Wege-Merge (3-way merge) mit den neuesten Commits zweier Branches und ihrem gemeinsamen Vorfahren durch und erstellt einen Merge-Commit.&lt;/li>
&lt;li>&lt;strong>Rebase&lt;/strong>
Speichert die Commits des aktuellen Branches vorübergehend und wendet sie auf die Spitze des Ziel-Branches neu an. Die Historie wird linear.&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">
gitGraph
commit id: &amp;#34;M1&amp;#34;
commit id: &amp;#34;M2&amp;#34;
branch feature
checkout feature
commit id: &amp;#34;F1&amp;#34;
commit id: &amp;#34;F2&amp;#34;
checkout main
commit id: &amp;#34;M3&amp;#34;
merge feature
&lt;/pre>
&lt;h3 id="methode-zur-konfliktlösung">Methode zur Konfliktlösung
&lt;/h3>&lt;p>In der Datei, in der der Konflikt aufgetreten ist, sind Marker wie diese eingefügt:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&lt;/span> &lt;span class="nx">HEAD&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">apiUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://api.production.example.com&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">=======&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">apiUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;https://api.staging.example.com&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span> &lt;span class="nx">feature&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="k">new&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="nx">api&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Der Lösungsweg ist extrem simpel.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Entfernen Sie die Marker und korrigieren Sie den Code auf die richtige Version.&lt;/strong>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="line">&lt;span class="cl">&lt;span class="kr">const&lt;/span> &lt;span class="nx">apiUrl&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">process&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">env&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">NODE_ENV&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;production&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">?&lt;/span> &lt;span class="s2">&amp;#34;https://api.production.example.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="o">:&lt;/span> &lt;span class="s2">&amp;#34;https://api.staging.example.com&amp;#34;&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>&lt;strong>Fügen Sie die aufgelöste Datei dem Staging-Bereich hinzu.&lt;/strong>
&lt;code>git add&lt;/code> hat die Rolle, „Git mitzuteilen, dass der Konflikt gelöst wurde“.
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ git add index.js
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;li>&lt;strong>Schließen Sie den Prozess ab.&lt;/strong>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Im Falle eines Merges&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git commit -m &lt;span class="s2">&amp;#34;Merge-Konflikt in index.js gelöst&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Im Falle eines Rebases&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git rebase --continue
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>Wenn Sie in Panik geraten, können Sie mit &lt;code>$ git merge --abort&lt;/code> oder &lt;code>$ git rebase --abort&lt;/code> jederzeit abbrechen.&lt;/p>
&lt;hr>
&lt;h2 id="9-fallstudie-6-eine-chaotische-commit-historie-git-rebase--i">9. Fallstudie 6: Eine chaotische Commit-Historie! &lt;code>git rebase -i&lt;/code>
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
Es haben sich viele kleine Commits angesammelt, wie „Tippfehler korrigiert“, „doch noch geändert“, „Test hinzugefügt“. Wenn ich das so in &lt;code>main&lt;/code> merge, wird die Historie unsauber.&lt;/p>
&lt;h3 id="lösung-interaktives-rebase">Lösung: Interaktives Rebase
&lt;/h3>&lt;p>Mit &lt;code>git rebase -i&lt;/code> (interactive) können Sie die Reihenfolge vergangener Commits ändern, mehrere Commits zu einem zusammenfassen (squash) oder die Commit-Nachrichten bearbeiten.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Die letzten 3 Commits aufräumen&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git rebase -i HEAD~3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Der Editor öffnet sich und es wird Folgendes angezeigt:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pick 1a2b3c4 Tippfehler korrigiert
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick 2b3c4d5 doch noch geändert
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pick 3c4d5e6 Test hinzugefügt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Sie können dies wie folgt umschreiben:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">pick 1a2b3c4 Implementierung von Funktion X
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash 2b3c4d5 doch noch geändert
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">squash 3c4d5e6 Test hinzugefügt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Wenn Sie dies speichern und schließen, werden diese 3 Commits wunderbar zu einem einzigen vereint.&lt;/p>
&lt;hr>
&lt;h2 id="10-fallstudie-7-ich-weiß-nicht-wann-der-bug-eingeführt-wurde-git-bisect">10. Fallstudie 7: Ich weiß nicht, wann der Bug eingeführt wurde! &lt;code>git bisect&lt;/code>
&lt;/h2>&lt;p>&lt;strong>[Situation]&lt;/strong>
Der aktuelle &lt;code>main&lt;/code>-Branch hat einen Bug, aber beim Release vor einem Monat funktionierte noch alles normal. Sie möchten herausfinden, mit welchem Commit der Bug eingeführt wurde, aber bei mehr als 100 Commits ist das manuell unmöglich!&lt;/p>
&lt;h3 id="lösung-bug-suche-durch-binäre-suche-bisektion">Lösung: Bug-Suche durch binäre Suche (Bisektion)
&lt;/h3>&lt;p>Git verfügt über ein integriertes Tool, das Fehler durch mathematische binäre Suche (Binary Search) findet. Da die Komplexität $\mathcal{O}(\log N)$ beträgt, können Sie den Fehler selbst bei 1000 Commits mit etwa 10 Tests identifizieren.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Die Suche starten&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect start
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Der aktuelle Commit hat den Bug (bad)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect bad
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Vor einem Monat (z.B. Hash a1b2c3d) war alles normal (good)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect good a1b2c3d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Git checkt automatisch den mittleren Commit aus, führen Sie also den Test durch&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Wenn der Test erfolgreich ist:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect good
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Wenn der Test fehlschlägt:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">$ git bisect bad
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Wenn Sie dies einfach wiederholen, wird Git Ihnen genau sagen: „Dieser Commit ist der erste fehlerhafte (bad) Commit“. Wenn Sie fertig sind, kehren Sie mit &lt;code>$ git bisect reset&lt;/code> zum ursprünglichen Zustand zurück.&lt;/p>
&lt;hr>
&lt;h2 id="11-das-ultimative-sicherheitsnetz-git-reflog">11. Das ultimative Sicherheitsnetz: &lt;code>git reflog&lt;/code>
&lt;/h2>&lt;p>Die ultimative Technik gegen jegliche „Missgeschicke“ in Git ist &lt;code>git reflog&lt;/code>. Git protokolliert den gesamten Verlauf lokaler Operationen (Bewegungen von HEAD) für einen bestimmten Zeitraum. Selbst wenn Sie versehentlich einen Branch löschen oder einen falschen Reset durchführen, können Sie den vergangenen Hashwert mit &lt;code>git reflog&lt;/code> finden und durch ein einfaches &lt;code>git reset --hard&lt;/code> dorthin wiederherstellen.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">$ git reflog
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">9f8a7b6 &lt;span class="o">(&lt;/span>HEAD -&amp;gt; main&lt;span class="o">)&lt;/span> HEAD@&lt;span class="o">{&lt;/span>0&lt;span class="o">}&lt;/span>: commit: Add new feature
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">1a2b3c4 HEAD@&lt;span class="o">{&lt;/span>1&lt;span class="o">}&lt;/span>: reset: moving to HEAD~1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="12-fazit">12. Fazit
&lt;/h2>&lt;p>Wir haben ausführlich erläutert, welche Fehler Git-Anfänger häufig machen, welche Mechanismen in Git dahinterstecken und wie man sie behebt. Das Committen auf dem falschen Branch, das Rückgängigmachen gepushter Commits, die Verwendung von Stash, das Überleben bei einem Detached HEAD und das Lösen von Konflikten. Bei all diesen Dingen ist es wichtig, sich vorzustellen, „welche Objekte und Zeiger Git im Hintergrund manipuliert“.&lt;/p>
&lt;p>Dateidifferenzen werden durch einen strengen Diff-Algorithmus berechnet, der als mathematische Formel ausgedrückt werden kann, und die Konsistenz der Historie wird durch kryptografische Hash-Funktionen gewährleistet. Wenn Sie diese schöne Designphilosophie verstehen, werden Sie erkennen, dass Git keineswegs eine „unheimliche Blackbox“ ist, sondern der stärkste Schild zum Schutz Ihres Quellcodes.&lt;/p>
&lt;p>Wenn Sie das nächste Mal denken „Ich hab&amp;rsquo;s verbockt!“, schließen Sie nicht in Panik das Terminal, sondern atmen Sie tief durch und tippen Sie &lt;code>git status&lt;/code> ein. Git wird Ihnen immer Hinweise zur Wiederherstellung geben.&lt;/p></description></item></channel></rss>