Outil simple pour 'accepter le leur' ​​ou 'accepter le mien' sur un fichier entier en utilisant git

400

Je ne veux pas d'outil de fusion visuel, et je ne veux pas non plus avoir à vi le fichier en conflit et choisir manuellement entre HEAD (le mien) et le changement importé (le leur). La plupart du temps, je veux soit tous leurs changements, soit tous les miens. Généralement, cela est dû au fait que ma modification est arrivée en amont et me revient par un pull, mais peut être légèrement modifiée à divers endroits.

Existe-t-il un outil en ligne de commande qui permettra de se débarrasser des marqueurs de conflit et de choisir d'une manière ou d'une autre en fonction de mon choix? Ou un ensemble de commandes git que je peux alias moi-même pour faire chacune.

# accept mine
alias am="some_sequence;of;commands"
alias at="some_other_sequence;of;commands"

Faire cela est plutôt ennuyeux. Pour «accepter le mien», j'ai essayé:

randy@sabotage ~/linus $ git merge test-branch
Auto-merging Makefile
CONFLICT (content): Merge conflict in Makefile
Automatic merge failed; fix conflicts and then commit the result.

randy@sabotage ~/linus $ git checkout Makefile 
error: path 'Makefile' is unmerged

andy@sabotage ~/linus $ git reset --hard HEAD Makefile 
fatal: Cannot do hard reset with paths.

Comment suis-je censé me débarrasser de ces marqueurs de changement?

Je peux faire:

git reset HEAD Makefile; rm Makefile; git checkout Makefile

Mais cela semble plutôt rond, il doit y avoir un meilleur moyen. Et à ce stade, je ne sais pas si git pense même que la fusion a eu lieu, donc je ne pense pas que cela fonctionne même nécessairement.

Dans l'autre sens, faire «accepter le leur» est tout aussi compliqué. La seule façon de le comprendre est de faire:

git show test-branch:Makefile > Makefile; git add Makefile;

Cela me donne également un message de validation foiré, qui contient deux fois Conflits: Makefile.

Quelqu'un peut-il indiquer comment effectuer les deux actions ci-dessus de manière plus simple? Merci

nosatalian
la source
4
Je dois vous le donner en tant qu'utilisateur de ligne de commande de trois ans + git, je trouve cela ridiculement difficile à faire de mémoire. Il devrait vraiment être intégré par défaut.
Mauvis Ledford

Réponses:

603

La solution est très simple. git checkout <filename>tente d'extraire le fichier de l'index et échoue donc lors de la fusion.

Ce que vous devez faire (c'est-à-dire extraire un commit ):

Pour commander votre propre version, vous pouvez utiliser l' un des éléments suivants:

git checkout HEAD -- <filename>

ou

git checkout --ours -- <filename>

ou

git show :2:<filename> > <filename> # (stage 2 is ours)

Pour commander l'autre version, vous pouvez utiliser l' une des options suivantes :

git checkout test-branch -- <filename>

ou

git checkout --theirs -- <filename>

ou

git show :3:<filename> > <filename> # (stage 3 is theirs)

Vous devrez également exécuter «ajouter» pour le marquer comme résolu:

git add <filename>
Jakub Narębski
la source
31
J'ai trouvé cela un peu étrange --ourset cela --theirssignifie exactement le contraire de ce que je pensais intuitivement en essayant cette commande ...
Joshua Muheim
6
Soyez prudent lors de l'utilisation git show- cela saute la normalisation de la nouvelle ligne.
Chronial
2
C'est bien pour quelques fichiers, mais lorsque vous avez plusieurs fichiers en conflit (car une date dans un commentaire a été modifiée!), Comment faites-vous?
JhovaniC
4
@Santhos: le --est utilisé par Git pour séparer les révisions (noms de branches, etc.) des noms de chemin (noms de fichiers, répertoires). Il est important que Git ne puisse pas décider si un nom est le nom de la branche ou le nom du fichier. Cela suit la convention POSIX (ou GNU) d'utiliser un double tiret pour séparer les options des arguments (noms de fichiers).
Jakub Narębski
3
@Sammaron @Joshua Muheim; le theirs/ ourspeut apparaître inversé si vous résolvez des conflits dans le contexte d'une opération de rebase. Parce que le rebase fonctionne en extrayant la branche cible, puis la sélection de cerises de "votre" branche sur la cible, la modification entrante ("la leur") vient de "votre" branche, et la branche actuelle est la branche cible ("la nôtre") ).
RJFalconer
93

Essaye ça:

Pour accepter leurs modifications: git merge --strategy-option theirs

Pour accepter le vôtre: git merge --strategy-option ours

Siva Mandadi
la source
5
Notez que cela conservera vos modifications pour TOUS les fichiers en conflit, donc pourrait être dangereux si un conflit inattendu se produit.
John
3
Et vous pouvez l'utiliser pour d'autres commandes de fusion-y comme la sélection de cerise et le rebase.
idbrii
50

Sur la base de la réponse de Jakub, vous pouvez configurer les alias git suivants pour plus de commodité:

accept-ours = "!f() { git checkout --ours -- \"${@:-.}\"; git add -u \"${@:-.}\"; }; f"
accept-theirs = "!f() { git checkout --theirs -- \"${@:-.}\"; git add -u \"${@:-.}\"; }; f"

Ils peuvent éventuellement prendre un ou plusieurs chemins de fichiers à résoudre et par défaut tout résoudre sous le répertoire actuel si aucun n'est indiqué.

Ajoutez-les au [alias] section de votre ~/.gitconfigou exécutez

git config --global alias.accept-ours '!f() { git checkout --ours -- "${@:-.}"; git add -u "${@:-.}"; }; f'
git config --global alias.accept-theirs '!f() { git checkout --theirs -- "${@:-.}"; git add -u "${@:-.}"; }; f'
kynan
la source
1
Ne fonctionne pas pour moi ... S'agit-il de bash ou d'un autre shell?
user456584
Ce sont des alias git, ajoutez-les à la [alias]section de votre ~.gitconfigou utilisez git config --global accept-ours "...". J'ai édité ma réponse.
kynan
2
Vous n'avez aucune idée du temps que cet alias m'a fait gagner. Pouces vers le haut!
Adam Parkin
1
@hakre Assurez-vous de citer l'alias, sinon votre shell essaiera de l'interpréter. Ou modifiez simplement votre fichier manuellement ~/.gitconfig.
kynan
1
Syntaxe du shell pour les valeurs par défaut:!f() { git checkout --ours -- "${@:-.}" git add -u "${@:-.}; }; f
jthill
17

Sur la base de la réponse de kynan, voici les mêmes alias, modifiés afin qu'ils puissent gérer les espaces et les tirets initiaux dans les noms de fichiers:

accept-ours = "!f() { [ -z \"$@\" ] && set - '.'; git checkout --ours -- \"$@\"; git add -u -- \"$@\"; }; f"
accept-theirs = "!f() { [ -z \"$@\" ] && set - '.'; git checkout --theirs -- \"$@\"; git add -u -- \"$@\"; }; f"
Dar
la source
0

La situation idéale pour résoudre les conflits est quand vous savez à l' avance de quelle façon vous voulez les résoudre et peut passer les -Xoursou -Xtheirsfusion récursives options de stratégie. En dehors de cela, je peux voir trois scénarios:

  1. Vous voulez simplement conserver une seule version du fichier (cela ne devrait probablement être utilisé que sur des fichiers binaires non fusionnables, car sinon les fichiers en conflit et non en conflit peuvent se désynchroniser les uns avec les autres).
  2. Vous voulez simplement décider de tous les conflits dans une direction particulière.
  3. Vous devez résoudre certains conflits manuellement, puis résoudre tous les autres dans une direction particulière.

Pour répondre à ces trois scénarios, vous pouvez ajouter les lignes suivantes à votre .gitconfigfichier (ou équivalent):

[merge]
  conflictstyle = diff3
[mergetool.getours]
  cmd = git-checkout --ours ${MERGED}
  trustExitCode = true
[mergetool.mergeours]
  cmd = git-merge-file --ours ${LOCAL} ${BASE} ${REMOTE} -p > ${MERGED}
  trustExitCode = true
[mergetool.keepours]
  cmd = sed -I '' -e '/^<<<<<<</d' -e '/^|||||||/,/^>>>>>>>/d' ${MERGED}
  trustExitCode = true
[mergetool.gettheirs]
  cmd = git-checkout --theirs ${MERGED}
  trustExitCode = true
[mergetool.mergetheirs]
  cmd = git-merge-file --theirs ${LOCAL} ${BASE} ${REMOTE} -p > ${MERGED}
  trustExitCode = true
[mergetool.keeptheirs]
  cmd = sed -I '' -e '/^<<<<<<</,/^=======/d' -e '/^>>>>>>>/d' ${MERGED}
  trustExitCode = true

le get(ours|theirs) outil conserve simplement la version respective du fichier et supprime toutes les modifications de l'autre version (donc aucune fusion ne se produit).

L' merge(ours|theirs)outil refait la fusion à trois à partir des versions locale, de base et distante du fichier, en choisissant de résoudre les conflits dans la direction donnée. Cela a quelques mises en garde, en particulier: il ignore les options de diff qui ont été passées à la commande de fusion (telles que l'algorithme et la gestion des espaces); effectue la fusion proprement à partir des fichiers d'origine (donc toutes les modifications manuelles apportées au fichier sont supprimées, ce qui pourrait être bon ou mauvais); et a l'avantage de ne pas pouvoir être confondu par les marqueurs de différence qui sont censés être dans le fichier.

le keep(ours|theirs) outil édite simplement les marqueurs de différence et les sections fermées, les détectant par expression régulière. Cela a l'avantage de conserver les options de diff de la commande de fusion et vous permet de résoudre certains conflits à la main, puis de résoudre automatiquement le reste. Il présente l'inconvénient que s'il y a d'autres marqueurs de conflit dans le fichier, il peut être confondu.

Ils sont tous utilisés en exécutant git mergetool -t (get|merge|keep)(ours|theirs) [<filename>]où, s'il <filename>n'est pas fourni, il traite tous les fichiers en conflit.

D'une manière générale, en supposant que vous savez qu'il n'y a pas de marqueurs diff pour confondre l'expression régulière, les keep*variantes de la commande sont les plus puissantes. Si vous laissez l' mergetool.keepBackupoption non définie ou vraie, après la fusion, vous pouvez différencier le *.origfichier du résultat de la fusion pour vérifier que cela a du sens. Par exemple, j'exécute ce qui suit mergetooljuste après pour inspecter les modifications avant de valider:

for f in `find . -name '*.orig'`; do vimdiff $f ${f%.orig}; done

Remarque : Si ce merge.conflictstylen'est pas diff3le cas /^|||||||/, le modèle de la sedrègle doit l'être à la /^=======/place.

Parakleta
la source