Wie kann ich nur Änderungen an bestimmten Dateien auswählen?

587

Wenn ich in einem Git-Zweig die Änderungen zusammenführen möchte, die nur an einigen der Dateien vorgenommen wurden, die in einem bestimmten Commit geändert wurden, einschließlich Änderungen an mehreren Dateien, wie kann dies erreicht werden?

Angenommen , die Git commit genannt stuffhat Änderungen an Dateien A, B, C, und , Daber ich möchte nur verschmelzen stuff‚s Änderungen an Dateien Aund B. Es klingt wie ein Job für, kann git cherry-pickaber cherry-picknur ganze Commits zusammenführen, nicht eine Teilmenge der Dateien.

Tobias Kienzler
quelle

Antworten:

689

Ich würde es mit cherry-pick -n( --no-commit) tun, mit dem Sie das Ergebnis überprüfen (und ändern) können, bevor Sie Folgendes festlegen:

git cherry-pick -n <commit>

# unstage modifications you don't want to keep, and remove the
# modifications from the work tree as well.
# this does work recursively!
git checkout HEAD <path>

# commit; the message will have been stored for you by cherry-pick
git commit

Wenn die überwiegende Mehrheit der Änderungen Dinge sind, die Sie nicht möchten, anstatt einzelne Pfade zu überprüfen (der mittlere Schritt), können Sie alles zurücksetzen und dann hinzufügen, was Sie möchten:

# unstage everything
git reset HEAD

# stage the modifications you do want
git add <path>

# make the work tree match the index
# (do this from the top level of the repo)
git checkout .
Cascabel
quelle
10
Zusätzlich git checkout .würde ich empfehlen, auch git clean -falle neuen, aber unerwünschten Dateien zu entfernen, die durch das von Cherry ausgewählte Commit eingeführt wurden.
rlat
4
Zusätzlicher Hinweis für die letztere Methode: Ich verwende, mit git add -pder Sie interaktiv entscheiden können, welche Änderungen Sie dem Index pro Datei
matthaeus
6
Dies ist nicht so groß in dem Fall , dass der handverlesene verpflichtet sich auf die aktuelle Arbeitskopie nicht anwenden , weil es so anders ist, aber dass eine Datei würde sauber anwenden.
Begrenzte Versöhnung
3
Sie können auch selektiv mit inszenieren git reset -p HEAD. Es ist das Äquivalent von, add -paber nur sehr wenige wissen, dass es existiert.
Patrick Schlüter
1
Sehr nützlicher Trick. Ich habe es in eine Liste aufgenommen, falls jemand es als schnelles Skript benötigt. Gist.github.com/PiDayDev/68c39b305ab9d61ed8bb2a1195ee1afc
Damiano
146

Die anderen Methoden haben bei mir nicht funktioniert, da das Commit viele Änderungen und Konflikte mit vielen anderen Dateien aufwies. Was ich mir ausgedacht habe, war einfach

git show SHA -- file1.txt file2.txt | git apply -

Es werden nicht adddie Dateien oder ein Commit für Sie ausgeführt, sodass Sie möglicherweise nachverfolgen müssen

git add file1.txt file2.txt
git commit -c SHA

Oder wenn Sie das Hinzufügen überspringen möchten, können Sie das --cachedArgument verwenden, umgit apply

git show SHA -- file1.txt file2.txt | git apply --cached -

Sie können dasselbe auch für ganze Verzeichnisse tun

git show SHA -- dir1 dir2 | git apply -
Michael Anderson
quelle
2
Interessante Methode, danke. Aber macht das nicht im show SHA -- file | applyGrunde das Gleiche checkout SHA -- filewie in Mark Longairs Antwort ?
Tobias Kienzler
4
Nein, checkout SHA -- filewird genau die Version bei SHA auschecken, während show SHA -- file | applynur die Änderungen in SHA angewendet werden (genau wie bei Cherry-Pick). Es ist wichtig, ob (a) mehr als ein Commit die angegebene Datei im Quellzweig ändert oder (b) ein Commit die Datei in Ihrem aktuellen Zielzweig ändert.
Michael Anderson
9
Habe gerade eine andere großartige Verwendung dafür gefunden: selektives Zurücksetzen, wenn Sie nur eine Datei git revertzurücksetzen möchten (da das gesamte Commit rückgängig gemacht wird). In diesem Fall verwenden Sie einfachgit show -R SHA -- file1.txt file2.txt | git apply -
Michael Anderson
2
@RoeiBahumi das hat eine ganz andere Bedeutung. git diff SHA -- file1.txt file2.txt | git apply -bedeutet, alle Unterschiede zwischen der aktuellen Version der Datei und der Version bei SHA auf die aktuelle Version anzuwenden. Im Wesentlichen ist es das gleiche wie git checkout SHA -- file1.txt file2.txt. In meinem früheren Kommentar erfahren Sie, warum sich das von der git showVersion unterscheidet.
Michael Anderson
5
Wenn Sie Konflikte lösen müssen, verwenden Sie git apply -3 -statt nur git apply -. Wenn ein Konflikt auftritt, können Sie Ihre Standardtechnik zur Konfliktlösung verwenden, einschließlich der Verwendung git mergetool.
qwertzguy
87

Normalerweise verwende ich das -pFlag mit einer Git-Kasse aus dem anderen Zweig, die ich einfacher und detaillierter finde als die meisten anderen Methoden, auf die ich gestoßen bin.

Allgemein gesagt:

git checkout <other_branch_name> <files/to/grab in/list/separated/by/spaces> -p

Beispiel:

git checkout mybranch config/important.yml app/models/important.rb -p

Sie erhalten dann einen Dialog, in dem Sie gefragt werden, welche Änderungen in "Blobs" gewünscht werden. Dies funktioniert so ziemlich für jeden Teil der kontinuierlichen Codeänderung, den Sie dann für jeden Teil des Codes signalisieren können y(Ja) n(Nein) usw.

Die Option -poder patchfunktioniert für eine Vielzahl von Befehlen in git, einschließlich git stash save -pder Option , mit der Sie auswählen können, was Sie aus Ihrer aktuellen Arbeit speichern möchten

Ich verwende diese Technik manchmal, wenn ich viel Arbeit geleistet habe und sie trennen und in mehr themenbasierten Commits git add -pfestschreiben möchte, indem ich für jedes Commit wähle und auswähle, was ich will :)

Tyrone Wilson
quelle
3
Ich benutze regelmäßig git-add -p, aber ich weiß nicht, git-checkouthat auch eine -pFahne - das tut fix die fusionierenden Probleme die Nicht - -pAntwort hat?
Tobias Kienzler
1
Zumindest -pwürde eine manuelle Bearbeitung für einen solchen widersprüchlichen Abschnitt möglich sein, was cherry-pickwahrscheinlich auch ohnehin nachgeben würde. Ich werde dies das nächste Mal testen, wenn ich es brauche, definitiv ein interessanter Ansatz
Tobias Kienzler
2
Eine der beiden besten Antworten, die nicht gleichzeitige Änderungen an den Zweigen zunichte machen.
Akostadinov
1
In dieser Antwort erfahren Sie, wie Sie auswählen, welche Hunks angewendet werden sollen : stackoverflow.com/a/10605465/4816250 Insbesondere die Option 's' war sehr hilfreich.
Jvd10
1
git reset -p HEADermöglicht auch das, -pwas praktisch sein kann, wenn Sie nur einige Patches aus dem Index entfernen möchten.
Patrick Schlüter
42

Vielleicht ist der Vorteil dieser Methode gegenüber Jefromis Antwort, dass Sie sich nicht daran erinnern müssen, welches Verhalten beim Zurücksetzen von Git das richtige ist :)

 # Create a branch to throw away, on which we'll do the cherry-pick:
 git checkout -b to-discard

 # Do the cherry-pick:
 git cherry-pick stuff

 # Switch back to the branch you were previously on:
 git checkout -

 # Update the working tree and the index with the versions of A and B
 # from the to-discard branch:
 git checkout to-discard -- A B

 # Commit those changes:
 git commit -m "Cherry-picked changes to A and B from [stuff]"

 # Delete the temporary branch:
 git branch -D to-discard
Mark Longair
quelle
2
Danke für deine Antwort. Das hat mich zum Nachdenken angeregt, warum nicht das überspringen cherry-pickund direkt verwenden git checkout stuff -- A B? Und mit git commit -C stuffder Commit-Nachricht würde auch die gleiche bleiben
Tobias Kienzler
8
@Tobias: Dies würde nur funktionieren, wenn die am geänderten Dateien stuffin Ihrem aktuellen Zweig oder irgendwo zwischen dem gemeinsamen Vorfahren von HEADund stuffund der Spitze von nicht geändert wurden stuff. Wenn dies der Fall ist, cherry-pickwird das richtige Ergebnis erstellt (im Wesentlichen das Ergebnis einer Zusammenführung), während Ihre Methode die Änderungen im aktuellen Zweig wegwirft und alle Änderungen vom gemeinsamen Vorfahren bis zu stuff- nicht nur den darin enthaltenen - beibehält Single Commit.
Cascabel
2
@Tobias Kienzler: Ich ging davon aus, dass sich Ihr Ausgangspunkt ausreichend von dem übergeordneten Punkt unterscheidet stuff, den das Ergebnis der Kirschauswahl verlassen würde, Aund Bmit einem anderen Inhalt als dem Inhalt des Commits stuff. Wenn es jedoch genauso wäre, haben Sie Recht - Sie könnten einfach tun, was Sie sagen.
Mark Longair
@ Jeromi, @ Mark: Vielen Dank für Ihr Feedback. In meinem Fall behandle ich Zweige mit völlig getrennten Dateien, was mich zu meinem Vorschlag geführt hat. Aber in der Tat hatte ich früher oder später Probleme damit, also danke, dass Sie das angesprochen haben
Tobias Kienzler
Ich denke, meine Antwort in diesem anderen Thread könnte das sein, wonach Sie suchen.
Ian
30

Cherry Pick ist das Auswählen von Änderungen aus einem bestimmten "Commit". Die einfachste Lösung besteht darin, alle Änderungen bestimmter zu verwendender Dateien auszuwählen

 git checkout source_branch <paths>...

Zum Beispiel:

$ git branch
* master
  twitter_integration
$ git checkout twitter_integration app/models/avatar.rb db/migrate/20090223104419_create_avatars.rb test/unit/models/avatar_test.rb test/functional/models/avatar_test.rb
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   app/models/avatar.rb
#   new file:   db/migrate/20090223104419_create_avatars.rb
#   new file:   test/functional/models/avatar_test.rb
#   new file:   test/unit/models/avatar_test.rb
#
$ git commit -m "'Merge' avatar code from 'twitter_integration' branch"
[master]: created 4d3e37b: "'Merge' avatar code from 'twitter_integration' branch"
4 files changed, 72 insertions(+), 0 deletions(-)
create mode 100644 app/models/avatar.rb
create mode 100644 db/migrate/20090223104419_create_avatars.rb
create mode 100644 test/functional/models/avatar_test.rb
create mode 100644 test/unit/models/avatar_test.rb

Quellen und vollständige Erklärung http://jasonrudolph.com/blog/2009/02/25/git-tip-how-to-merge-specific-files-from-another-branch/

AKTUALISIEREN:

Mit dieser Methode verschmilzt git die Datei nicht, sondern überschreibt lediglich alle anderen Änderungen, die am Zielzweig vorgenommen wurden. Sie müssen die Änderungen manuell zusammenführen:

$ git diff HEAD Dateiname

cminatti
quelle
5
Das habe ich auch gedacht , aber das schlägt schrecklich fehl, wenn sich die Dateien in beiden Zweigen geändert haben, da die Änderungen Ihres aktuellen Zweigs
verworfen werden
Sie haben Recht, es ist ein Muss zu klären, dass auf diese Weise Git nicht verschmilzt, sondern nur überschreibt. Sie können dann "git diff HEAD filename" ausführen, um zu sehen, was sich geändert hat, und die Zusammenführung manuell durchführen.
cminatti
18

Die Situation:

Sie befinden sich in Ihrer Niederlassung, sagen wir, masterund Sie haben Ihr Commit in einer anderen Niederlassung. Sie müssen nur eine Datei aus diesem bestimmten Commit auswählen.

Die Vorgehensweise:

Schritt 1: Kasse in der gewünschten Filiale.

git checkout master

Schritt 2: Stellen Sie sicher, dass Sie den erforderlichen Commit-Hash kopiert haben.

git checkout commit_hash path\to\file

Schritt 3: Sie haben jetzt die Änderungen der erforderlichen Datei in Ihrem gewünschten Zweig. Sie müssen sie nur hinzufügen und festschreiben.

git add path\to\file
git commit -m "Your commit message"
Techdreams
quelle
1
Genial! Funktionierte auch für alle Änderungen in einem Verzeichnis mit \ Pfad \ zu \ Verzeichnis \ für mich
zaggi
13

Ich würde einfach alles auswählen und dann Folgendes tun:

git reset --soft HEAD^

Dann würde ich die Änderungen, die ich nicht möchte, zurücksetzen und dann ein neues Commit durchführen.

Funroll
quelle
11

Mit git merge --squash branch_namedieser Option erhalten Sie alle Änderungen aus dem anderen Zweig und bereiten ein Commit für Sie vor. Entfernen Sie nun alle nicht benötigten Änderungen und belassen Sie die gewünschte. Und Git wird nicht wissen, dass es eine Fusion gab.

Unbezwingbar
quelle
Vielen Dank, ich wusste nichts über diese Zusammenführungsoption. Es ist eine praktikable Alternative, wenn Sie den größten Teil eines ganzen Zweigs pflücken möchten (aber im Gegensatz zum Kirschpflücken funktioniert es nicht, wenn es keinen gemeinsamen Vorfahren gibt)
Tobias Kienzler
4

Ich habe einen anderen Weg gefunden, der jede widersprüchliche Verschmelzung beim Kirschpflücken verhindert, die IMO leicht zu merken und zu verstehen ist. Da Sie eigentlich kein Commit auswählen, sondern einen Teil davon, müssen Sie es zuerst aufteilen und dann ein Commit erstellen, das Ihren Anforderungen entspricht, und es auswählen.

Erstellen Sie zunächst einen Zweig aus dem Commit, den Sie teilen möchten, und checken Sie ihn aus:

$ git checkout COMMIT-TO-SPLIT-SHA -b temp

Setzen Sie dann das vorherige Commit zurück:

$ git reset HEAD~1

Fügen Sie dann die Dateien / Änderungen hinzu, die Sie auswählen möchten:

$ git add FILE

und begebe es:

$ git commit -m "pick me"

Beachten Sie den Commit-Hash. Nennen wir ihn PICK-SHA und kehren Sie zu Ihrem Hauptzweig zurück. Master erzwingt beispielsweise das Auschecken:

$ git checkout -f master

und wählen Sie das Commit aus:

$ git cherry-pick PICK-SHA

Jetzt können Sie den temporären Zweig löschen:

$ git branch -d temp -f
Alexey
quelle
2

Füge einen Zweig zu einem neuen zusammen (Squash) und entferne die nicht benötigten Dateien:

git checkout master
git checkout -b <branch>
git merge --squash <source-branch-with-many-commits>
git reset HEAD <not-needed-file-1>
git checkout -- <not-needed-file-1>
git reset HEAD <not-needed-file-2>
git checkout -- <not-needed-file-2>
git commit
nvd
quelle
2

Der Vollständigkeit halber funktioniert für mich am besten:

git show YOURHASH --no-color -- file1.txt file2.txt dir3 dir4 | git apply -3 --index -

git status

Es macht genau das, was OP will. Es löst Konflikte bei Bedarf, ähnlich wie merge. Es tut addaber nicht commitIhre neuen Änderungen.

kubanczyk
quelle
1

Sie können verwenden:

git diff <commit>^ <commit> -- <path> | git apply

Die Notation <commit>^gibt das (erste) Elternteil von an <commit>. Daher wählt dieser diff-Befehl die Änderungen aus, die <path>im Commit vorgenommen wurden <commit>.

Beachten Sie, dass dies noch nichts festlegt (wie git cherry-pickauch). Wenn Sie das wollen, müssen Sie Folgendes tun:

git add <path>
git commit
Garogolun
quelle