Wie kann ich git anweisen, immer meine lokale Version für widersprüchliche Zusammenführungen in einer bestimmten Datei auszuwählen?

100

Angenommen, ich arbeite mit jemandem über ein Git-Repository zusammen, und es gibt eine bestimmte Datei, an der ich keine externen Änderungen akzeptieren möchte.

Gibt es eine Möglichkeit für mich, mein lokales Repo so einzurichten, dass ich mich nicht jedes Mal über einen Zusammenschluss beschwere, wenn ich einen Zug mache? Ich möchte beim Zusammenführen dieser Datei immer meine lokale Version auswählen.

saffsd
quelle
1
Wir haben gerade eine einfache Lösung durch .gitattributes und einen sehr einfachen "Merge-Treiber"
hinzugefügt
12
TD; LR:echo 'path/to/file merge=ours' >> .gitattributes && git config --global merge.ours.driver true
Ciro Santilli 法轮功 冠状 病 六四 事件 28
@CiroSantilli: Funktioniert wie ein Zauber unter Linux. Dieser Treiber ist einfach genug, um in Git eingebaut zu werden ...
krlmlr
Möchten Sie Ihre Änderungen in die Datei übertragen? Oder ist es zum Beispiel eine Konfigurationsdatei, in der der Standard in git gespeichert ist?
Ian Ringrose
Der Kommentar von @CiroSantilli correct make 中心 六四 事件 法轮功 ist korrekt, führt jedoch dazu, dass dieses Verhalten für jedes Repo auf Ihrem System mit dem --globalTag auftritt . Wenn Sie dieses Verhalten nur für ein einzelnes Repo möchten, lassen Sie die --globalFlagge echo 'path/to/file merge=ours' >> .gitattributes && git config merge.ours.driver true
weg

Antworten:

140

In Bezug auf die spezifische Instanz einer Konfigurationsdatei würde ich Rons Antwort zustimmen :
Eine Konfiguration sollte für Ihren Arbeitsbereich "privat" sein (daher "ignoriert", wie in "in einer .gitignoreDatei deklariert ").
Sie können eine Konfigurationsdatei haben Schablone mit Zeichen übersetzten Werte in ihm, und ein Skript , das die Umwandlung config.templateDatei in eine private (und ignoriert) Config - Datei.


Diese spezifische Bemerkung beantwortet jedoch nicht eine allgemeinere Frage, dh Ihre Frage (!):

Wie kann ich git anweisen, immer meine lokale Version für Zusammenführungen von Konflikten in einer bestimmten Datei auszuwählen? (für jede Datei oder Dateigruppe)

Diese Art der Zusammenführung ist eine "Zusammenführung von Kopien", bei der Sie bei Konflikten immer "unsere" oder "ihre" Version einer Datei kopieren.

(Wie Brian Vandenberg in den Kommentaren feststellt , werden ' ours' und ' theirs' hier für eine Zusammenführung verwendet .
Sie werden für eine Rebase umgekehrt : siehe " ", das eine Rebase verwendet, " und verfolgt 'local' und 'remote' ". )Why is the meaning of “ours” and “theirs” reversed with git-svngit rebase

Für "eine Datei" (eine Datei im Allgemeinen, die nicht von einer "Konfigurations" -Datei spricht, da dies ein schlechtes Beispiel ist) würden Sie dies mit einem benutzerdefinierten Skript erreichen, das durch Zusammenführungen aufgerufen wird.
Git ruft dieses Skript auf, da Sie einen gitattributes- Wert definiert haben , der einen benutzerdefinierten Zusammenführungstreiber definiert .

Der "benutzerdefinierte Zusammenführungstreiber" ist in diesem Fall ein sehr einfaches Skript, das die aktuelle Version grundsätzlich unverändert lässt, sodass Sie immer Ihre lokale Version auswählen können.

IE., Wie von Ciro Santilli bemerkt :

echo 'path/to/file merge=ours' >> .gitattributes
git config --global merge.ours.driver true

Lassen Sie uns dies in einem einfachen Szenario mit einem msysgit 1.6.3 unter Windows in einer bloßen DOS-Sitzung testen:

cd f:\prog\git\test
mkdir copyMerge\dirWithConflicts
mkdir copyMerge\dirWithCopyMerge
cd copyMerge
git init
Initialized empty Git repository in F:/prog/git/test/copyMerge/.git/

Lassen Sie uns nun zwei Dateien erstellen, die beide Konflikte aufweisen, aber unterschiedlich zusammengeführt werden.

echo a > dirWithConflicts\a.txt
echo b > dirWithCopyMerge\b.txt
git add -A
git commit -m "first commit with 2 directories and 2 files"
[master (root-commit) 0adaf8e] first commit with 2 directories and 2 files

Wir werden einen "Konflikt" im Inhalt dieser beiden Dateien in zwei verschiedenen Git-Zweigen einführen:

git checkout -b myBranch
Switched to a new branch 'myBranch'
echo myLineForA >> dirWithConflicts\a.txt
echo myLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in myBranch"
[myBranch 97eac61] add modification in myBranch

git checkout master
Switched to branch 'master'
git checkout -b hisBranch
Switched to a new branch 'hisBranch'
echo hisLineForA >> dirWithConflicts\a.txt
echo hisLineForB >> dirWithCopyMerge\b.txt
git add -A
git commit -m "add modification in hisBranch"
[hisBranch 658c31c] add modification in hisBranch

Versuchen wir nun, "hisBranch" mit "myBranch" zusammenzuführen, mit:

  • manuelle Lösung für widersprüchliche Zusammenführungen
  • ausnehmen für , dirWithCopyMerge\b.txtwo ich will immer halten meine Version b.txt.

Da die Zusammenführung in ' MyBranch' erfolgt, wechseln wir zurück und fügen die ' gitattributes' Anweisungen hinzu, mit denen das Zusammenführungsverhalten angepasst wird.

git checkout myBranch
Switched to branch 'myBranch'
echo b.txt merge=keepMine > dirWithCopyMerge\.gitattributes
git config merge.keepMine.name "always keep mine during merge"
git config merge.keepMine.driver "keepMine.sh %O %A %B"
git add -A
git commit -m "prepare myBranch with .gitattributes merge strategy"
[myBranch ec202aa] prepare myBranch with .gitattributes merge strategy

Wir haben eine .gitattributesDatei im dirWithCopyMergeVerzeichnis definiert (definiert nur in dem Zweig, in dem die Zusammenführung stattfinden wird :) myBranch, und wir haben eine .git\configDatei, die jetzt einen Zusammenführungstreiber enthält.

[merge "keepMine"]
        name = always keep mine during merge
        driver = keepMine.sh %O %A %B

Wenn Sie keepMine.sh noch nicht definieren und die Zusammenführung trotzdem starten, erhalten Sie Folgendes.

git merge hisBranch
sh: keepMine.sh: command not found
fatal: Failed to execute internal merge
git st
# On branch myBranch
# 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:   dirWithConflicts/a.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

type dirWithConflicts\a.txt
a
<<<<<<< HEAD:dirWithConflicts/a.txt
myLineForA
=======
hisLineForA
>>>>>>> hisBranch:dirWithConflicts/a.txt

Das ist gut:

  • a.txt ist bereit zum Zusammenführen und hat Konflikte
  • b.txtist immer noch unberührt, da der Zusammenführungstreiber sich darum kümmern soll (aufgrund der Anweisung in der .gitattributesDatei in seinem Verzeichnis).

Definieren Sie eine keepMine.shbeliebige Stelle in Ihrem %PATH%(oder $PATHfür unseren Unix-Freund. Ich mache natürlich beides: Ich habe eine Ubuntu-Sitzung in einer VirtualBox-Sitzung)

Wie von lrkwz kommentiert und im Abschnitt " Strategien zusammenführen " unter Anpassen von Git - Git-Attributen beschrieben , können Sie das Shell-Skript durch den Shell-Befehl ersetzen .true

git config merge.keepMine.driver true

Im allgemeinen Fall können Sie jedoch eine Skriptdatei definieren:

keepMine.sh

# I want to keep MY version when there is a conflict
# Nothing to do: %A (the second parameter) already contains my version
# Just indicate the merge has been successfully "resolved" with the exit status
exit 0

(das war ein einfacher Zusammenführungstreiber;) (In diesem Fall noch einfacher verwenden true)
(Wenn Sie die andere Version behalten möchten , fügen Sie einfach vor der exit 0Zeile hinzu :
cp -f $3 $2.
Das war's. Ihr Zusammenführungstreiber würde immer die Version von der anderen erhalten Zweigstelle, die alle lokalen Änderungen außer Kraft setzt)

Lassen Sie uns nun die Zusammenführung von Anfang an wiederholen:

git reset --hard
HEAD is now at ec202aa prepare myBranch with .gitattributes merge strategy

git merge hisBranch
Auto-merging dirWithConflicts/a.txt
CONFLICT (content): Merge conflict in dirWithConflicts/a.txt
Auto-merging dirWithCopyMerge/b.txt
Automatic merge failed; fix conflicts and then commit the result.

Die Zusammenführung schlägt fehl ... nur für a.txt .
Bearbeiten Sie a.txt und verlassen Sie die Zeile von 'hisBranch'. Dann:

git add -A
git commit -m "resolve a.txt by accepting hisBranch version"
[myBranch 77bc81f] resolve a.txt by accepting hisBranch version

Lassen Sie uns überprüfen, ob b.txt während dieser Zusammenführung beibehalten wurde

type dirWithCopyMerge\b.txt
b
myLineForB

Das letzte Commit stellt die vollständige Zusammenführung dar:

git show -v 77bc81f5e
commit 77bc81f5ed585f90fc1ca5e2e1ddef24a6913a1d
Merge: ec202aa 658c31c
git merge hisBranch
Already up-to-date.

(Die mit Merge beginnende Zeile beweist das)


Überlegen Sie, ob Sie den Zusammenführungstreiber wie bei Git definieren, kombinieren und / oder überschreiben können:

  • Prüfen <dir>/.gitattributes(das sich im selben Verzeichnis befindet wie der betreffende Pfad): Wird sich .gitattributesin Verzeichnissen gegen das andere durchsetzen
  • Dann prüft es .gitattributes(das sich im übergeordneten Verzeichnis befindet), setzt nur Direktiven, wenn es nicht bereits gesetzt ist
  • Schließlich untersucht es $GIT_DIR/info/attributes. Diese Datei wird verwendet, um die In-Tree-Einstellungen zu überschreiben. <dir>/.gitattributesDirektiven werden überschrieben .

Mit "Kombinieren" meine ich den "aggregierten" Mehrfachzusammenführungstreiber.
Nick Green versucht in den Kommentaren , Zusammenführungstreiber tatsächlich zu kombinieren: siehe " Zusammenführen von Poms über Python-Git-Treiber ".
Wie in seiner anderen Frage erwähnt , funktioniert dies jedoch nur bei Konflikten (gleichzeitige Änderung in beiden Zweigen).

VonC
quelle
1
Vielen Dank für die ausführliche Antwort! Ich verstehe, dass es keinen Sinn macht, Konfigurationsdateien zur Versionskontrolle zu verwenden, aber ich war nach einem geradlinig motivierenden Beispiel. In der Tat hat mich die umfassendere Frage interessiert. Ich hatte noch nie von Git Merge-Treibern gehört. Vielen Dank, dass Sie mich aufgeklärt haben.
Saffsd
6
Das cp -f $3 $2sollte wohl zitiert werden, dh cp -f "$3" "$2".
Arc
1
@VonC danke für die ausführliche Antwort! Das Problem, das ich damit habe, ist, dass es von Leuten abhängt, die den Treiber in ihrer .git / config-Datei einstellen. Ich möchte die Treiberinformationen zum Projekt selbst hinzufügen, damit dies automatisch erfolgt und weniger Einrichtungsarbeiten erforderlich sind. Irgendwelche Hinweise?
Juan Delgado
2
@ulmangt: Sie können dieses Skript auch sehr viel im Git-Repo speichern, ... solange Sie eine Möglichkeit finden, das übergeordnete Verzeichnis zum PATH(Unix oder Windows PATH) hinzuzufügen . Da dieses Skript über die Unix-Bash-Shell oder über die MingWin-Bash-MsysGit-Windows-Shell interpretiert wird, ist es portabel.
VonC
5
@VonC Danke. Noch ein Problem. Unter bestimmten Umständen (wenn keine Änderungen am lokalen Zweig vorgenommen wurden, in den zusammengeführt wird) wird der Zusammenführungstreiber anscheinend nie aufgerufen, was dazu führt, dass die lokalen Dateien geändert werden (die den benutzerdefinierten Zusammenführungstreiber verwenden sollen) um zu verhindern, dass sie sich während einer Zusammenführung ändern). Gibt es eine Möglichkeit, git zu zwingen , immer den Merge-Treiber zu verwenden?
Ulmangt
1

Wie @ ciro-santilli kommentiert hat, ist die einfache Möglichkeit, dies .gitattributesmit Einstellungen zu tun :

path/to/file merge=ours

und aktivieren Sie diese Strategie mit:

git config --global merge.ours.driver true

(Ich füge dies als Antwort hinzu, um es sichtbarer zu machen, mache es aber zu einem Community-Wiki, um nicht zu versuchen, die Credits des Benutzers für mich selbst zu übertreffen. Bitte stimme seinem Kommentar unter dem Q hier zu, um ihm ein Lob zu geben!)

Greg Dubicki
quelle
(Nebenbei: Wenn jemand die Antwort als Kommentar gibt und keine Antwort hinzufügt, ist es vollkommen in Ordnung, eine Nicht-CW-Antwort zu schreiben und die Credits zu erhalten. Wenn er noch ein aktives Mitglied ist, können Sie ihn anpingen, um eine Antwort hinzuzufügen, wenn Sie wünschen, aber sie hatten technisch bereits ihre Chance :-)).
Halfer
0

Wir haben mehrere Konfigurationsdateien, die niemals überschrieben werden sollen. .Gitignore und .gitattributes funktionierten jedoch in unserer Situation nicht. Unsere Lösung bestand darin, die Konfigurationsdateien in einem Konfigurationszweig zu speichern. Lassen Sie dann zu, dass die Dateien während der Git-Zusammenführung geändert werden. Verwenden Sie jedoch unmittelbar nach der Zusammenführung den "Git-Checkout-Zweig -". um unsere Konfigurationsdateien nach jeder Zusammenführung aus dem Zweig configs zu kopieren. Detaillierte Antwort zum Stackoverflow hier

HamletHub
quelle