Comment dire à git de toujours sélectionner ma version locale pour les fusions en conflit sur un fichier spécifique?

100

Disons que je collabore avec quelqu'un via un référentiel git et qu'il existe un fichier particulier auquel je ne souhaite jamais accepter de modifications externes.

Existe-t-il un moyen pour moi de configurer mon dépôt local pour ne pas me plaindre d'une fusion en conflit à chaque fois que je tire? Je souhaite toujours sélectionner ma version locale lors de la fusion de ce fichier.

saffsd
la source
1
Je viens d'ajouter une solution simple via .gitattributes et un "pilote de fusion" très basique
VonC
12
TD; LR:echo 'path/to/file merge=ours' >> .gitattributes && git config --global merge.ours.driver true
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
@CiroSantilli: Fonctionne comme un charme sur Linux. Ce pilote est assez simple pour être intégré à Git ...
krlmlr
Souhaitez-vous appliquer vos modifications au fichier? Ou est-ce par exemple un fichier de configuration où la valeur par défaut est stockée dans git.
Ian Ringrose le
Le commentaire de @CiroSantilli 新疆 改造 中心 六四 事件 法轮功 est correct, mais provoquera ce problème pour chaque dépôt sur votre système avec la --globalbalise. Si vous ne voulez ce comportement que pour un seul repo, laissez de côté l' --globalindicateur:echo 'path/to/file merge=ours' >> .gitattributes && git config merge.ours.driver true
majorobot

Réponses:

140

Sur l'instance spécifique d'un fichier de configuration, je serais d'accord avec la réponse de Ron :
une configuration devrait être "privée" pour votre espace de travail (donc "ignorée", comme dans "déclarée dans un .gitignorefichier").
Vous pouvez avoir un modèle de fichier de configuration avec des valeurs symbolisées et un script transformant ce config.templatefichier en un fichier de configuration privé (et ignoré).


Cependant, cette remarque spécifique ne répond pas à ce qui est une question plus large, plus générale, à savoir votre question (!):

Comment dire à git de toujours sélectionner ma version locale pour les fusions en conflit sur un fichier spécifique? (pour tout fichier ou groupe de fichiers)

Ce type de fusion est une «fusion par copie», dans laquelle vous copiez toujours la version «nôtre» ou «la leur» d'un fichier chaque fois qu'il y a un conflit.

(comme Brian Vandenberg le note dans les commentaires , « ours» et « theirs» sont ici utilisés pour une fusion .
Ils sont inversés pour un rebase : voir " Why is the meaning of “ours” and “theirs” reversed with git-svn", qui utilise un rebase, " git rebase, en gardant une trace de" local "et" distant " " )

Pour "un fichier" (un fichier en général, sans parler d'un fichier "config", car c'est un mauvais exemple), vous y parviendrez avec un script personnalisé appelé par fusion.
Git appellera ce script car vous aurez défini une valeur gitattributes , qui définit un pilote de fusion personnalisé .

Le "pilote de fusion personnalisé" est, dans ce cas, un script très simple qui gardera fondamentalement inchangé la version actuelle, vous permettant ainsi de toujours sélectionner votre version locale.

IE., Comme indiqué par Ciro Santilli :

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

Testons cela dans un scénario simple, avec un msysgit 1.6.3 sous Windows, dans une simple session 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/

Maintenant, créons deux fichiers, qui auront tous deux des conflits, mais qui seront fusionnés différemment.

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

Nous allons introduire un "conflit" dans le contenu de ces deux fichiers dans deux branches git différentes:

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

Maintenant, essayons de fusionner "hisBranch" sur "myBranch", avec:

  • résolution manuelle des fusions conflictuelles
  • excepter pour dirWithCopyMerge\b.txtoù je veux toujours garder ma version b.txt.

Puisque la fusion se produit dans « MyBranch», nous y retournerons et ajouterons les gitattributesdirectives « » qui personnaliseront le comportement de fusion.

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

Nous avons un .gitattributesfichier défini dans le dirWithCopyMergerépertoire (défini uniquement dans la branche où la fusion aura lieu :) myBranch, et nous avons un .git\configfichier qui contient maintenant un pilote de fusion.

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

Si vous ne définissez pas encore keepMine.sh et que vous lancez quand même la fusion, voici ce que vous obtenez.

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

C'est bon:

  • a.txt est prêt à être fusionné et a un conflit
  • b.txtest toujours inchangé, puisque le pilote de fusion est censé s'en occuper (en raison de la directive dans le .gitattributesfichier dans son répertoire).

Définissez un keepMine.shn'importe où dans votre %PATH%(ou $PATHpour notre ami Unix. Je fais les deux bien sûr: j'ai une session Ubuntu dans une session VirtualBox)

Comme commenté par lrkwz et décrit dans la section « Stratégies de fusion » de Personnalisation des attributs Git - Git , vous pouvez remplacer le script shell par la commande shell true.

git config merge.keepMine.driver true

Mais dans le cas général, vous pouvez définir un fichier 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

(qui était un conducteur simple de fusion;) (encore plus simple dans ce cas, l' utilisation true)
(Si vous voulez garder l'autre version, il suffit d' ajouter avant la exit 0ligne:
cp -f $3 $2.
Ca y est vous fusionnez pilote serait aways garder la version à venir de l'autre. branche, annulant tout changement local)

Maintenant, réessayons la fusion depuis le début:

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.

La fusion échoue ... uniquement pour a.txt .
Editez a.txt et laissez la ligne de 'hisBranch', puis:

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

Vérifions que b.txt a été conservé lors de cette fusion

type dirWithCopyMerge\b.txt
b
myLineForB

Le dernier commit représente la fusion complète :

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

(La ligne commençant par Merge le prouve)


Considérez que vous pouvez définir, combiner et / ou écraser le pilote de fusion, comme Git le fera:

  • examine <dir>/.gitattributes(qui est dans le même répertoire que le chemin en question): prévaudra sur l'autre.gitattributes dans les répertoires
  • Ensuite, il examine .gitattributes(qui se trouve dans le répertoire parent), ne définira des directives que s'il n'est pas déjà défini
  • Enfin, il examine $GIT_DIR/info/attributes. Ce fichier est utilisé pour remplacer les paramètres de l'arborescence. Cela écrasera les <dir>/.gitattributesdirectives.

Par «combinaison», je veux dire «agréger» plusieurs pilotes de fusion.
Nick Green essaie, dans les commentaires , de combiner réellement les pilotes de fusion: voir " Fusionner les pom via le pilote python git ".
Cependant, comme mentionné dans son autre question , cela ne fonctionne qu'en cas de conflits (modification simultanée dans les deux branches).

VonC
la source
1
Merci pour la réponse détaillée! Je comprends que les fichiers de configuration de contrôle de version n'ont aucun sens, mais je cherchais un exemple simple et motivant. En effet, c'est la question plus large qui m'intéressait. Je n'avais jamais entendu parler de pilotes git merge auparavant, alors merci de m'avoir éclairé.
saffsd
6
Le cp -f $3 $2devrait probablement être cité, à savoir cp -f "$3" "$2".
Arc
1
@VonC merci pour la réponse détaillée! Le problème que j'ai avec cela est que cela dépend des personnes qui définissent le pilote dans leur fichier .git / config. Je voudrais ajouter les informations du pilote au projet lui-même, donc ce serait automatique et moins de travail d'installation à faire. Des pointeurs?
Juan Delgado
2
@ulmangt: vous pouvez également stocker ce script dans le repo git, ... tant que vous trouvez un moyen d'ajouter son répertoire parent à PATH(Unix ou Windows PATH). Puisque ce script sera interprété via le shell bash Unix, ou via le shell Windows MingWin bash MsysGit, il sera portable.
VonC le
5
@VonC Merci. Encore un problème. Dans certaines circonstances (s'il n'y a pas eu de changements dans la branche locale fusionnée), il semble que le pilote de fusion n'est même jamais appelé, ce qui entraîne la modification des fichiers locaux (qui sont censés utiliser le pilote de fusion personnalisé pour les empêcher de changer lors d'une fusion). Existe-t-il un moyen de forcer git à toujours utiliser le pilote de fusion?
ulmangt
1

Comme @ ciro-santilli l'a commenté, le moyen simple de le faire est de l'utiliser .gitattributesavec les paramètres:

path/to/file merge=ours

et activez cette stratégie avec:

git config --global merge.ours.driver true

(J'ajoute ceci comme réponse pour le rendre plus visible, mais en faisant un wiki communautaire pour ne pas essayer d'obtenir au-dessus des crédits de l'utilisateur pour moi-même. Veuillez voter pour son commentaire sous le Q ici pour lui donner des félicitations!)

Greg Dubicki
la source
(À part: si quelqu'un donne la réponse sous forme de commentaire et n'ajoute pas de réponse, il est parfaitement normal d'écrire une réponse non CW et d'obtenir les crédits. S'il est toujours un membre actif, vous pouvez lui envoyer un ping pour ajouter une réponse si vous souhaitez, mais techniquement ils avaient déjà leur chance :-)).
halfer le
0

Nous avons plusieurs fichiers de configuration que nous ne voulons jamais écraser. Cependant .gitignore et .gitattributes ne fonctionnaient pas dans notre situation. Notre solution était de stocker les fichiers de configuration dans une branche de configuration. Ensuite, autorisez les fichiers à changer pendant la fusion git, mais immédiatement après la fusion, utilisez la "branche git checkout -". pour copier nos fichiers de configuration de la branche configs après chaque fusion. Réponse détaillée de stackoverflow ici

HamletHub
la source