git ready

Lerne Git Commit für Commit
von Nick Quaranto, Übersetzung von Nico Gulden

Einzelne Commits auswählen

eingetragen am 04 Mar 2009

Du möchtest manchmal vielleicht nur einen Commit aus einem Branch oder nur eine Datei aus einem Changeset lesen. Dieser Beitrag beschäftigt sich mit der Frage, wie man einzelne Commits auswählt und mit ihnen arbeitet.

Beim einfachen Fall siehst du einen anderen Branch oder vielleicht den Fork eines Projekts, aber du möchtest nur einen Commit, ein paar wenige Commits oder was auch immer du hast, übernehmen. Glücklicherweise hat Git eine ganze Menge an Werkzeugen, um dir diese Aufgabe zu erleichtern. Hier ist as Szenario. Es gab in letzter Zeit eine ganze Menge an Entwicklungen an jekyll, aber ich möchte nur oder zwei einzelne Changesets übernehmen. Das Repository sieht folgendermaßen aus:

Wie du sehen kannst, ist unser Hauptzweig (master) um unteren Ende dieser aufregenden Entwicklung und darüber gibt es eine ganze Reihe an Aktivitäten. Ich möchte jedoch nur den markierten Commit in mein Repository übernehmen. Sobald der SHA bekannt ist, gibt es ein paar Optionen:

1) Benutze git cherry-pick zur Auswahl des Commits. Das erzeugt einen neuen Commit und behält die Metadaten der alten Übergabe bei.

2) Erzeuge einen Patch mit git format-patch und übernehme ihn mit git am. Das erlaubt dir eine Gegenzeichnung des Commits mit --signoff. Auch dieser Schritt erzeugt einen neuen Commit.

3) Benutze git apply mit dem Patch, um die Änderungen in dein Arbeitsverzeichnis zu übernehmen und sie weiter zu bearbeiten.

4) Übernehme die Änderungen direkt mit git merge. Dieser Weg bewahrt den ursprünglichen Commit, sofern keine Konflikte auftreten.

Wir versuchen alle vier Möglichkeiten. Wir kennen den SHA b50788b des Commits und können mit ihm arbeiten. Als erstes wählen wir den Commit mit cherry-pick.

$ git cherry-pick b50788b
Finished one cherry-pick.
[master]: created bcb0d1b: "First pass at rake task."
 3 files changed, 63 insertions(+), 3 deletions(-)
 create mode 100644 lib/jekyll/task.rb

$ git log --pretty=oneline --abbrev-commit HEAD~3..HEAD
bcb0d1b... First pass at rake task.
2569e9f... update history
2135a53... Using block syntax of popen4 to ensure that subprocesses...

Wie du sehen kannst, wurde ein neuer Commit erzeugt und die Arbeit wurde erfolgreich übernommen. Die erste und dritte Methode erzeugen beide eine ähnliche Historie mit einem neuen Commit:

Mit format-patch wird ein Commit eingepackt und mit git am angewendet.

$ git format-patch -1 b50788b
0001-First-pass-at-rake-task.patch

$ git am 0001-First-pass-at-rake-task.patch
Applying: First pass at rake task.

$ git log --pretty=oneline --abbrev-commit HEAD~3..HEAD
c05eaa1... First pass at rake task.
2569e9f... update history
2135a53... Using block syntax of popen4 to ensure that subprocesses...

Über diesen Prozess werden normalerweise Patches per E-Mail angenommen. Aber die Methode funktioniert ebenso für unsere Zwecke. Natürlich könnten format-patch und am über eine Pipe zu einem Befehl zusammengefügt werden. Auf diese Weise musst du dich nicht um die zusätzliche Datei kümmern, die sonst erzeugt werden würde.

Als nächstes benutzen wir git apply und erhalten die Änderungen in unserem Arbeitsverzeichnis und können noch an ihnen arbeiten.

$ git format-patch -1 b50788b
0001-First-pass-at-rake-task.patch

$ git apply 0001-First-pass-at-rake-task.patch 
0001-First-pass-at-rake-task.patch:59: trailing whitespace.

0001-First-pass-at-rake-task.patch:75: trailing whitespace.
    private
warning: 2 lines add whitespace errors.

$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified:   jekyll.gemspec
# modified:   lib/jekyll.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
# 0001-First-pass-at-rake-task.patch
# lib/jekyll/task.rb
no changes added to commit (use "git add" and/or "git commit -a")

Abschließend können wir den Commit mit einem Merge übernehmen, was in diesem Fall sehr einfach ist, da keine Konflikte auftreten.

$ git merge b50788b
Updating 2569e9f..b50788b
Fast forward
 jekyll.gemspec     |    6 ++--
 lib/jekyll.rb      |    2 +
 lib/jekyll/task.rb |   58 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 3 deletions(-)
 create mode 100644 lib/jekyll/task.rb

$ git log --pretty=oneline --graph --abbrev-commit
b50788b... First pass at rake task.
2569e9f... update history
2135a53... Using block syntax of popen4 to ensure that subprocesses...

Die Grafik zeigt die Übernahme des Commits auf der linke Seite in die Hauptlinie:

Du kannst, wie immer, mit git reset --hard HEAD^ einen Commit zurück gehen, falls etwas nach der Anwendung des Patches schief gelaufen ist. Vielleicht ist es sogar möglich, ein Revert der Dateien durchzuführen. Wenn alles drunter und drüber geht, kannst du immer noch das Reflog prüfen. Wenn du andere Wege kennst, wie man mit dieser Situation klar kommt, teile sie uns mit einem Kommentar mit!