Come faccio a dire a git di selezionare sempre la mia versione locale per unioni in conflitto su un file specifico?


100

Supponiamo che stia collaborando con qualcuno tramite un repository git e che c'è un file particolare a cui non voglio mai accettare modifiche esterne.

C'è un modo per impostare il mio repository locale per non lamentarsi di un'unione in conflitto ogni volta che eseguo il pull? Vorrei sempre selezionare la mia versione locale quando unisco questo file.


1
Ho appena aggiunto una semplice soluzione tramite .gitattributes e un "merge driver" molto semplice
VonC

12
TD; LR:echo 'path/to/file merge=ours' >> .gitattributes && git config --global merge.ours.driver true
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

@CiroSantilli: funziona a meraviglia su Linux. Questo driver è abbastanza semplice da essere integrato in Git ...
krlmlr

Desideri inviare le modifiche al file? O è ad esempio un file di configurazione in cui il valore predefinito è memorizzato in git.
Ian Ringrose

Il commento di @CiroSantilli 新疆 改造 中心 六四 事件 法轮功 è corretto, ma farà sì che questo comportamento si verifichi per ogni repo sul tuo sistema con il --globaltag. Se desideri questo comportamento solo per un singolo repo, lascia fuori la --globalbandiera:echo 'path/to/file merge=ours' >> .gitattributes && git config merge.ours.driver true
majorobot

Risposte:


140

Nell'istanza specifica di un file di configurazione, sarei d'accordo con la risposta di Ron :
una configurazione dovrebbe essere "privata" per il tuo spazio di lavoro (quindi "ignorata", come in "dichiarata in un .gitignorefile").
Potresti avere un modello di file di configurazione con valori tokenizzati e uno script che trasforma quel config.templatefile in un file di configurazione privato (e ignorato).


Tuttavia, quella specifica osservazione non risponde a quella che è una domanda più ampia e generale, ovvero la tua domanda (!):

Come faccio a dire a git di selezionare sempre la mia versione locale per unioni in conflitto su un file specifico? (per qualsiasi file o gruppo di file)

Questo tipo di unione è una "unione di copie", in cui copierai sempre la versione "nostra" o "loro" di un file ogni volta che si verifica un conflitto.

(come nota Brian Vandenberg nei commenti , " ours" e " theirs" sono qui usati per un merge .
Sono invertiti per un rebase : vedi " Why is the meaning of “ours” and “theirs” reversed with git-svn", che usa un rebase, " git rebase, tenendo traccia di 'local' e 'remote' " )

Per "un file" (un file in generale, non parlando di un file "config", poiché è un cattivo esempio), lo otterresti con uno script personalizzato chiamato tramite unioni.
Git chiamerà quello script perché dovrai definire un valore gitattributes , che definisce un driver di unione personalizzato .

Il "custom merge driver" è, in questo caso, uno script molto semplice che sostanzialmente manterrà invariata la versione corrente, permettendoti quindi di selezionare sempre la tua versione locale.

IE., Come notato da Ciro Santilli :

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

Proviamolo in uno scenario semplice, con un msysgit 1.6.3 su Windows, in una semplice sessione DOS:

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/

Ora, creiamo due file, che avranno entrambi conflitti, ma che verranno uniti in modo diverso.

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

Introdurremo un "conflitto" nel contenuto di entrambi i file in due diversi rami git:

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

Ora, proviamo a unire "hisBranch" a "myBranch", con:

  • risoluzione manuale per unioni in conflitto
  • tranne per dirWithCopyMerge\b.txtcui ho sempre voglia di mantenere la mia versione di b.txt.

Poiché l'unione avviene in " MyBranch", torneremo ad essa e aggiungeremo le gitattributesdirettive " " che personalizzeranno il comportamento di unione.

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

Abbiamo un .gitattributesfile definito nella dirWithCopyMergedirectory (definito solo nel ramo in cui avverrà l'unione :) myBranch, e abbiamo un .git\configfile che ora contiene un merge driver.

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

Se non definisci ancora keepMine.sh e avvii comunque l'unione, ecco cosa ottieni.

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

Questo va bene:

  • a.txt è pronto per essere unito e contiene un conflitto
  • b.txtè ancora intatto, poiché si suppone che il driver di fusione se ne occupi (a causa della direttiva nel .gitattributesfile nella sua directory).

Definisci un keepMine.shpunto qualsiasi nel tuo %PATH%(o $PATHper il nostro amico Unix. Faccio entrambe le cose ovviamente: ho una sessione di Ubuntu in una sessione di VirtualBox)

Come commentato da lrkwz e descritto nella sezione " Unisci strategie " di Personalizzazione di Git - Attributi Git , puoi sostituire lo script della shell con il comando della shell true.

git config merge.keepMine.driver true

Ma nel caso generale, puoi definire un file di script:

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

(che era un pilota merge semplice;) (Ancora più semplice in questo caso, l'uso true)
(Se si voleva mantenere l'altra versione, basta aggiungere prima della exit 0riga:
cp -f $3 $2.
Questo è tutto Si uniscono conducente avrebbe away mantenere la versione proveniente dall'altro. branch, ignorando qualsiasi modifica locale)

Ora, riproviamo l'unione dall'inizio:

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.

L'unione fallisce ... solo per a.txt .
Modifica a.txt e lascia la riga da "hisBranch", quindi:

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

Controlliamo che b.txt sia stato preservato durante questa unione

type dirWithCopyMerge\b.txt
b
myLineForB

L'ultimo commit rappresenta l' unione completa :

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

(La riga che inizia con Merge lo dimostra)


Considera di poter definire, combinare e / o sovrascrivere il driver di unione, come farà Git:

  • esaminare <dir>/.gitattributes(che si trova nella stessa directory del percorso in questione): prevarrà sull'altro .gitattributesnelle directory
  • Quindi esamina .gitattributes(che si trova nella directory padre), imposterà solo le direttive se non già impostate
  • Infine esamina $GIT_DIR/info/attributes. Questo file viene utilizzato per sovrascrivere le impostazioni nell'albero. Sovrascriverà le <dir>/.gitattributesdirettive.

Con "combinazione", intendo "aggregare" più driver di unione.
Nick Green cerca, nei commenti , di combinare effettivamente i driver di unione: vedere " Unisci i pom tramite il driver git di python ".
Tuttavia, come menzionato nella sua altra domanda , funziona solo in caso di conflitti (modifica simultanea in entrambi i rami).


1
Grazie per la risposta dettagliata! Capisco che non ha senso controllare la versione dei file di configurazione, ma stavo cercando un semplice esempio motivante. In effetti, è la domanda più ampia che mi interessava. Non avevo mai sentito parlare di driver git merge prima, quindi grazie per avermi illuminato.
saffsd

6
L' cp -f $3 $2dovrebbe probabilmente essere citato, vale a dire cp -f "$3" "$2".
Arco

1
@VonC grazie per la risposta dettagliata! Il problema che ho con esso è che dipende dalle persone che impostano il driver nel loro file .git / config. Vorrei aggiungere le informazioni sul driver al progetto stesso, quindi sarebbe automatico e meno lavoro di configurazione da fare. Qualche suggerimento?
Juan Delgado

2
@ulmangt: puoi memorizzare lo script anche nel repository git, ... purché trovi un modo per aggiungere la sua directory genitore a PATH(Unix o Windows PATH). Poiché quello script verrà interpretato tramite la shell bash Unix, o tramite la shell bash MsysGit di MingWin per Windows, sarà portabile.
VonC

5
@VonC Grazie. Un altro problema. In determinate circostanze (se non sono state apportate modifiche al ramo locale in cui è stato unito) sembra che il driver di unione non venga mai nemmeno chiamato, il che si traduce nella modifica dei file locali (che dovrebbero utilizzare il driver di unione personalizzato per impedire che cambino durante un'unione). C'è un modo per forzare git a usare sempre il driver di unione?
ulmangt

1

Come ha commentato @ ciro-santilli, il modo più semplice per farlo da utilizzare .gitattributescon le impostazioni:

path/to/file merge=ours

e abilita questa strategia con:

git config --global merge.ours.driver true

(Sto aggiungendo questo come risposta per renderlo più visibile ma rendendolo un Wiki della comunità per non cercare di ottenere i crediti dell'utente sopra per me stesso. Per favore, vota il suo commento sotto la Q qui per fargli i complimenti!)


(A parte: se qualcuno fornisce la risposta come commento e non aggiunge una risposta, è perfettamente OK scrivere una risposta non CW e ottenere i crediti. Se è ancora un membro attivo, puoi eseguire il ping per aggiungere una risposta se desideri, ma tecnicamente hanno già avuto la loro possibilità :-)).
halfer

0

Abbiamo più file di configurazione che non vogliamo mai sovrascrivere. Tuttavia .gitignore e .gitattributes non hanno funzionato nella nostra situazione. La nostra soluzione era archiviare i file di configurazione in un ramo di configurazione. Quindi, consenti la modifica dei file durante l'unione git, ma immediatamente dopo l'unione usa "git checkout branch -." per copiare i nostri file di configurazione dal ramo configs dopo ogni unione. Risposta dettagliata dello stackoverflow qui

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.