Je souhaite effectuer un diff à trois voies entre deux branches git avec une base de fusion commune et l'afficher avec kdiff3.
J'ai trouvé beaucoup de conseils sur SO (et quelques questions très similaires ( 1 , 2 , 3 )) mais je n'ai pas trouvé de réponse directe. réponse. Notamment, un commentaire sur cette réponse implique que ce que je veux est possible, mais cela n’a pas fonctionné pour moi. Espérons que cet utilisateur puisse entrer ici :)
Pour le fond, quand j'effectue des fusions, j'utilise un style de conflit "diff3":
git config --global merge.conflictstyle diff3
Et j'ai git mergetool
configuré pour utiliser kdiff3.
Lors de la résolution des conflits de fusion, quatre fichiers sont affichés:
Cependant, git difftool
uniquement extraira les deux conseils de branche. Je veux aussi voir le fichier de base. Pour être clair, je veux pouvoir effectuer cette fusion diff avant, y compris sur des fichiers sans conflits de fusion . (git mergetool
affiche uniquement les différences à trois voies s'il y a des conflits).
Solution partielle n ° 1:
Avec un fichier individuel, je peux exporter les trois versions et appeler manuellement le diff:
git show local_branch:filename > localfile
git show remote_branch:filename > remotefile
git show `git merge-base local_branch remote_branch`:filename > basefile
{kdiff3_path}/kdiff3.exe --L1 "Base" --L2 "Local" --L3 "Remote" -o "outputfile" basefile localfile remotefile &
Il y a deux problèmes avec ceci:
Solution partielle n ° 2:
Merci à cette réponse et commentaire pour l'inspiration.
Créez un pilote de fusion personnalisé qui renvoie toujours "false", ce qui crée un état de fusion en conflit sans effectuer de fusion automatique. Puis effectuez le diff en utilisant git mergetool
. Puis annulez la fusion lorsque vous avez terminé.
Ajouter à .git/config
:
[merge "assert_conflict_states"]
name = assert_conflict_states
driver = false
Créez (ou ajoutez à) .git/info/attributes
pour que toutes les fusions utilisent le nouveau pilote:
* merge=assert_conflict_states
Effectuez la fusion, ce qui ne permet plus d’automatisation.
Faire le diff. Dans mon cas: git mergetool
qui appelle la fusion à trois voies de kdiff3.
Une fois terminé, annulez la fusion: git merge --abort
.
Annuler l'étape n ° 2.
Cela fonctionnerait (en quelque sorte) sauf que kdiff3 effectue un automerge à l'appel, donc I ne peut toujours pas voir les diffs pré-fusionnés. Je peux résoudre ce problème en modifiant le fichier de pilote kdiff3 de Git (.../git-core/mergetools/kdiff3
en supprimant le commutateur --auto
.
Malgré tout, cela pose les problèmes suivants:
attributes
avant et après avoir fait le diff.Informations sur la prime:
Selon les réponses données, cela n’est pas possible avec Git standard. Alors maintenant, je cherche une solution plus prête à l'emploi: comment puis-je modifier Git pour que cela se produise?
Voici une piste: apparemment, si un seul des trois fichiers a changé, ce fichier plus récent est utilisé dans le résultat de la fusion sans appeler le pilote de fusion. Cela signifie que mon pilote de fusion personnalisé "créant un conflit" n'est jamais appelé dans ce cas. Si c'était le cas, ma "solution partielle n ° 2" fonctionnerait réellement.
Ce comportement pourrait-il être modifié en modifiant des fichiers ou des configurations? Ou peut-être existe-t-il un moyen de créer un pilote de diff personnalisé? Je ne suis pas prêt à jouer avec le code source de Git ...
Des idées intelligentes?
Je veux pouvoir effectuer cette diff avant fusion, y compris sur des fichiers sans conflits de fusion.
Il vous suffit de configurer l’index comme vous le souhaitez, vous n’aurez jamais à engager de résultats. La façon de configurer exactement ce qui a été demandé, diffs-puisque-base, sans préparation de fusion, est
git merge -s ours --no-ff --no-commit $your_other_tip
un handroll complet dans lequel git configure uniquement les parents pour tout ce que vous décidez par la suite de commettre, mais il est probablement préférable de le faire avec une fusion normale tout en pouvant toujours y entrer et tout examiner,
git merge --no-ff --no-commit $your_other_tip
Choisissez votre point de départ, puis
forcer une visite de fusion pour toutes les entrées indiquant toute modification de
#!/bin/sh
git checkout -m .
# identify paths that show changes in either tip but were automerged
scratch=`mktemp -t`
sort <<EOD | uniq -u >"$scratch"
$( # paths that show changes at either tip:
( git diff --name-only ...MERGE_HEAD
git diff --name-only MERGE_HEAD...
) | sort -u )
$( # paths that already show a conflict:
git ls-files -u | cut -f2- )
EOD
# un-automerge them: strip the resolved-content entry and explicitly
# add the base/ours/theirs content entries
git update-index --force-remove --stdin <"$scratch"
stage_paths_from () {
xargs -a "$1" -d\\n git ls-tree -r $2 |
sed "s/ [^ ]*//;s/\t/ $3\t/" |
git update-index --index-info
}
stage_paths_from "$scratch" $(git merge-base @ MERGE_HEAD) 1
stage_paths_from "$scratch" @ 2
stage_paths_from "$scratch" MERGE_HEAD 3
... si vous utilisiez vimdiff, l'étape 2 serait simplement git mergetool
. vimdiff part de ce qu'il y a dans l'arbre de travail et ne fait pas son propre automerge. Il semble que kdiff3 veuille ignorer la structure de travail. Anyhoo, le mettre en place pour fonctionner sans --auto ne regarde pas trop trop hacky:
# one-time setup:
wip=~/libexec/my-git-mergetools
mkdir -p "$wip"
cp -a "$(git --exec-path)/mergetools/kdiff3" "$wip"
sed -si 's/--auto //g' "$wip"/kdiff3
et alors vous pouvez
MERGE_TOOLS_DIR=~/libexec/my-git-mergetools git mergetool
La sauvegarde de ceci est juste le git merge --abort
ou git reset --hard
habituel.
Je ne pense pas que c'est possible.
La logique de fusion est en réalité assez complexe. La base de fusion n'est pas nécessairement unique et le code de fusion prend beaucoup de temps pour faire face à une telle situation de manière raisonnable, mais cela n'est pas dupliqué dans un code diff.
Git facilite le retour à l'état précédent. Donc, rangez vos modifications si vous en avez, essayez la fusion, puis --abort
ou reset
lorsque vous avez suffisamment regardé et que vous n'avez plus besoin du résultat.
J'utilise le script bash brut et fusion suivants pour voir ce qui a été changé après fusionner deux branches:
#!/bin/bash
filename="$1"
if [ -z "$filename" ] ; then
echo "Usage: $0 filename"
exit 1
fi
if [ ! -f "$filename" ] ; then
echo "No file named \"$filename\""
exit 1
fi
hashes=$(git log --merges -n1 --parents --format="%P")
hash1=${hashes% *}
hash2=${hashes#* }
if [ -z "$hash1" || -z "$hash2" ] ; then
echo "Current commit isn't a merge of two branches"
exit 1
fi
meld <(git show $hash1:"$filename") "$filename" <(git show $hash2:"$filename")
Il peut probablement être piraté de voir les différences entre un fichier du répertoire courant et deux branches:
!/bin/bash
filename="$1"
hash1=$2
hash2=$3
if [ -z "$filename" ] ; then
echo "Usage: $0 filename hash1 hash2"
exit 1
fi
if [ ! -f "$filename" ] ; then
echo "No file named \"$filename\""
exit 1
fi
if [ -z "$hash1" || -z "$hash2" ] ; then
echo "Missing hashes to compare"
exit 1
fi
meld <(git show $hash1:"$filename") "$filename" <(git show $hash2:"$filename")
Je n'ai pas testé ce script. Cela ne vous montrera pas comment git fusionnerait le fichier mais cela vous donnera une idée de l'emplacement des conflits potentiels.
Pour autant que je m'en souvienne, ce n'est pas possible. Vous pouvez différencier mergebase avec local_branch
et mergebase contre remote_branch
comme décrit dans la réponse que vous avez citée. Mais je pense qu’il n’ya pas encore de possibilité d’obtenir une fusion à trois comme vous l’aviez demandé avec une commande git standard. Vous pouvez demander sur la liste de diffusion Git que cette fonctionnalité soit ajoutée.