Pourquoi git-rebase me donne-t-il des conflits de fusion alors que je ne fais que écraser les commits?

146

Nous avons un référentiel Git avec plus de 400 commits, dont les deux premières douzaines étaient beaucoup d'essais et d'erreurs. Nous voulons nettoyer ces commits en en écrasant plusieurs en un seul commit. Naturellement, git-rebase semble la voie à suivre. Mon problème est que cela aboutit à des conflits de fusion, et ces conflits ne sont pas faciles à résoudre. Je ne comprends pas du tout pourquoi il devrait y avoir des conflits, car je suis juste en train d'écraser les commits (pas de suppression ni de réorganisation). Très probablement, cela démontre que je ne comprends pas complètement comment git-rebase fait ses squashes.

Voici une version modifiée des scripts que j'utilise:


repo_squash.sh (c'est le script qui est réellement exécuté):


rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

repo_squash_helper.sh (ce script n'est utilisé que par repo_squash.sh):


if grep -q "pick " $1
then
#  cp $1 ../repo_squash_history.txt
#  emacs -nw $1
  sed -f ../repo_squash_list.txt < $1 > $1.tmp
  mv $1.tmp $1
else
  if grep -q "initial import" $1
  then
    cp ../repo_squash_new_message1.txt $1
  elif grep -q "fixing bad import" $1
  then
    cp ../repo_squash_new_message2.txt $1
  else
    emacs -nw $1
  fi
fi

repo_squash_list.txt: (ce fichier est utilisé uniquement par repo_squash_helper.sh)


# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g

Je laisse le contenu du "nouveau message" à votre imagination. Au départ, je l'ai fait sans l'option "--strategy theirs" (c'est-à-dire en utilisant la stratégie par défaut, qui, si je comprends bien la documentation, est récursive, mais je ne suis pas sûr de la stratégie récursive utilisée), et cela n'a pas non plus été le cas. t travailler. De plus, je dois souligner qu'en utilisant le code commenté dans repo_squash_helper.sh, j'ai sauvegardé le fichier original sur lequel le script sed fonctionne et j'ai exécuté le script sed pour m'assurer qu'il faisait ce que je voulais qu'il fasse ( c'était). Encore une fois, je ne sais même pas pourquoi il y aurait un conflit, donc peu importe quelle stratégie est utilisée. Tout conseil ou aperçu serait utile, mais je veux surtout que cela fonctionne.

Mise à jour avec des informations supplémentaires issues de la discussion avec Jefromi:

Avant de travailler sur notre énorme dépôt "réel", j'ai utilisé des scripts similaires sur un dépôt de test. C'était un référentiel très simple et le test a fonctionné proprement.

Le message que je reçois en cas d'échec est:

Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir

C'est le premier choix après le premier commit de squash. L'exécution git statusdonne un répertoire de travail propre. Si je fais ensuite un git rebase --continue, j'obtiens un message très similaire après quelques commits supplémentaires. Si je le fais à nouveau, j'obtiens un autre message très similaire après quelques dizaines de commits. Si je le fais encore une fois, cette fois, il passe par une centaine de commits, et renvoie ce message:

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental

Si je cours ensuite git status, j'obtiens:

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   repo/file_A.cpp
# modified:   repo/file_B.cpp
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified:      repo/file_X.cpp
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted:    repo/file_Z.imp

Le bit «les deux modifiés» me semble bizarre, car ce n'était que le résultat d'un choix. Il est également intéressant de noter que si je regarde le "conflit", il se résume à une seule ligne avec une version commençant par un caractère [tabulation], et l'autre avec quatre espaces. Cela sonnait comme si cela pouvait être un problème avec la façon dont j'ai configuré mon fichier de configuration, mais il n'y a rien de tel. (J'ai noté que core.ignorecase est défini sur true, mais évidemment git-clone l'a fait automatiquement. Je ne suis pas complètement surpris par cela étant donné que la source d'origine était sur une machine Windows.)

Si je corrige manuellement file_X.cpp, il échoue peu de temps après avec un autre conflit, cette fois entre un fichier (CMakeLists.txt) qu'une version pense devrait exister et une version ne devrait pas. Si je résout ce conflit en disant que je veux ce fichier (ce que je fais), quelques commits plus tard, j'obtiens un autre conflit (dans ce même fichier) où maintenant il y a des changements plutôt non triviaux. Ce n'est encore qu'environ 25% du chemin à travers les conflits.

Je dois également souligner, puisque cela peut être très important, que ce projet a commencé dans un référentiel svn. Cet historique initial a très probablement été importé de ce référentiel svn.

Mise à jour n ° 2:

Sur une alouette (influencée par les commentaires de Jefromi), j'ai décidé de changer mon repo_squash.sh pour qu'il soit:

rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Et puis, je viens d'accepter les entrées originales, telles quelles. C'est-à-dire que le "rebase" n'aurait rien dû changer. Il s'est retrouvé avec les mêmes résultats décrits précédemment.

Mise à jour n ° 3:

Alternativement, si j'omets la stratégie et remplace la dernière commande par:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Je n'obtiens plus les problèmes de rebase «rien à commettre», mais il me reste encore les autres conflits.

Mise à jour avec le référentiel de jouets qui recrée le problème:

test_squash.sh (c'est le fichier que vous exécutez réellement):

#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================

#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt 
git add test_file.txt 
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..

#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================

#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================

#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================

test_squash_helper.sh (utilisé par test_sqash.sh):

# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
  sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
  mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
  echo "Created two line file" > $1
fi

PS: Oui, je sais que certains d'entre vous grincent des dents quand vous me voyez utiliser emacs comme éditeur de secours.

PPS: Nous savons que nous devrons éliminer tous nos clones du référentiel existant après le rebase. (Dans le sens de "vous ne rebaserez pas un référentiel après sa publication".)

PPPS: Quelqu'un peut-il me dire comment ajouter une prime à cela? Je ne vois l'option nulle part sur cet écran, que je sois en mode d'édition ou en mode d'affichage.

Ben Hocking
la source
L'action finale tentée est plus pertinente que les scripts utilisés - elle semble assez sûre d'être une liste de pick and squash mélangés, non? Et y a-t-il des commits de fusion dans la branche rebasée? (Bien que vous n'utilisiez pas de rebase -ptoute façon)
Cascabel
Je ne sais pas ce que vous entendez par « dernière tentative d' action », mais il est juste une liste de choix entremêlées et le squash, la dernière 400 ou afin d' être tous les choix. Il n'y a pas de fusion dans cette liste, bien que le rebasage effectue ses propres fusions. D'après ce que j'ai lu, "rebase -p" n'est pas recommandé avec le mode interactif (qui dans mon cas n'est pas tout à fait interactif, bien sûr). De kernel.org/pub/software/scm/git/docs/git-rebase.html : "Cela utilise la machine --interactive en interne, mais la combiner explicitement avec l'option --interactive n'est généralement pas une bonne idée"
Ben Hocking
Par "dernière tentative d'action", je voulais dire la liste des sélections / squash renvoyées rebase --interactive- ce sont en quelque sorte une liste d'actions que git doit tenter. J'espérais que vous pourriez être en mesure de réduire cela à un seul squash qui causait des conflits, et éviter toute la complexité supplémentaire de vos scripts d'aide. L'autre information manquante est quand les conflits se produisent - quand git applique les correctifs pour former le squash, ou quand il essaie de passer au-delà du squash et d'appliquer le patch suivant? (Et êtes-vous sûr que rien de mal ne se passe avec votre kludge GIT_EDITOR? Un autre vote pour un cas de test simple.)
Cascabel
Merci pour les commentaires utiles. J'ai mis à jour la question pour refléter mes réponses.
Ben Hocking
2
Le cas du référentiel de jouets est beaucoup, beaucoup plus facile à comprendre que vos scripts - et montre que les scripts n'ont rien à voir avec cela, juste le fait que vous essayez de rebaser une branche contenant une fusion avec résolution de conflit (une fusion légèrement diabolique ).
Cascabel le

Réponses:

69

Très bien, je suis assez confiant pour lancer une réponse. Il faudra peut-être le modifier, mais je crois savoir quel est votre problème.

Votre scénario de test de repo de jouets a une fusion - pire, il a une fusion avec des conflits. Et vous rebasez à travers la fusion. Sans -p(qui ne fonctionne pas totalement avec -i), les fusions sont ignorées. Cela signifie que tout ce que vous avez fait dans votre résolution de conflit n'est pas là lorsque le rebase tente de sélectionner le prochain commit, donc son correctif peut ne pas s'appliquer. (Je pense que cela est montré comme un conflit de fusion car git cherry-pickon peut appliquer le correctif en effectuant une fusion à trois voies entre le commit d'origine, le commit actuel et l'ancêtre commun.)

Malheureusement, comme nous l'avons noté dans les commentaires, -iet -p(préserver les fusions) ne s'entendent pas très bien. Je sais que l'édition / la reformulation fonctionnent, et que la réorganisation ne fonctionne pas. Cependant, je pense que cela fonctionne bien avec les courges. Cela n'est pas documenté, mais cela a fonctionné pour les cas de test que je décris ci-dessous. Si votre cas est beaucoup plus complexe, vous aurez peut-être beaucoup de mal à faire ce que vous voulez, même si cela sera toujours possible. (Morale de l'histoire: nettoyer avec rebase -i avant de fusionner.)

Donc, supposons que nous ayons un cas très simple, où nous voulons écraser ensemble A, B et C:

- o - A - B - C - X - D - E - F (master)
   \             /
    Z -----------

Maintenant, comme je l'ai dit, s'il n'y avait pas de conflits dans X, git rebase -i -pfonctionne comme vous vous en doutez.

S'il y a des conflits, les choses deviennent un peu plus délicates. Cela fera bien écraser, mais quand il essaiera de recréer la fusion, les conflits se reproduiront. Vous devrez les résoudre à nouveau, les ajouter à l'index, puis utiliser git rebase --continuepour continuer. (Bien sûr, vous pouvez les résoudre à nouveau en extrayant la version de la validation de fusion d'origine.)

Si vous arrive d'avoir rerereactivé dans votre repo ( rerere.enabledvaleur true), ce sera plus facile - git sera en mesure de re utiliser la re côtelé re solution à partir de laquelle vous avez eu à l' origine des conflits, et tout ce que vous avez à faire est de l' inspecter pour vous assurer que cela fonctionne correctement, ajoutez les fichiers à l'index et continuez. (Vous pouvez même aller plus loin en les activant rerere.autoupdate, et cela les ajoutera pour vous, de sorte que la fusion n'échouera même pas). Je suppose, cependant, que vous n'avez jamais activé la rerere, vous devrez donc résoudre vous-même le conflit. *

* Ou, vous pouvez essayer le rerere-train.shscript de git-contrib, qui tente de "Prime [the] rerere database from existing merge commits" - en gros, il vérifie tous les commits de fusion, essaie de les fusionner, et si la fusion échoue, il saisit les résultats et les montre git-rerere. Cela peut prendre du temps et je ne l'ai jamais utilisé, mais cela pourrait être très utile.

Cascabel
la source
PS En repensant à mes commentaires, je vois que j'aurais dû attraper ça plus tôt. J'ai demandé si vous rebasiez une branche contenant des fusions, et vous avez dit qu'il n'y avait pas de fusion dans la liste de rebase interactive, ce qui n'est pas la même chose - il n'y avait pas de fusion parce que vous n'avez pas fourni l' -poption.
Cascabel
Je vais certainement essayer. Depuis que j'ai publié ceci, j'ai également remarqué que dans certains cas, je peux simplement taper git commit -a -m"Some message"et git rebase --continue, et cela continuera. Cela fonctionne même sans l' -poption, mais cela fonctionne encore mieux avec l' -poption (puisque je ne fais aucune nouvelle commande, il semble que ce -psoit bien). Quoi qu'il en soit, je vous tiendrai au courant.
Ben Hocking
La définition git config --global rerere.enabled trueet git config --global rerere.autoupdate trueavant d'exécuter l'exemple de test résout le problème principal. Fait intéressant, cependant, il ne préserve pas les fusions, même lors de la spécification --preserve-merges. Cependant, si je ne les ai pas définis, et que je tape git commit -a -m"Meaningful commit"et git rebase --continuetout en spécifiant --preserve-merges, cela préserve les fusions. Quoi qu'il en soit, merci de m'avoir aidé à surmonter ce problème!
Ben Hocking
84

Si cela ne vous dérange pas de créer une nouvelle branche, voici comment j'ai traité le problème:

Être sur le principal:

# create a new branch
git checkout -b new_clean_branch

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
git reset --soft main        

git commit -m "Squashed changes from original_messy_branch"
hlidka
la source
3
La solution la plus rapide et efficace :)
IslamTaha
2
C'était une excellente suggestion! Fonctionne très bien pour consolider rapidement plusieurs commits.
philidem
3
C'est une solution beaucoup plus sûre. J'ai également ajouté --squash à la fusion. Être: git merge --squash original_messy_branch
jezpez
1
Merci d'avoir sauvé ma journée et ma vie :) La meilleure solution que tout le monde puisse obtenir
Quand est-ce git diff new_clean_branch..original_messy_branchqu'ils devraient être les mêmes? Pour moi, ils sont différents.
LeonardChallis
5

Je cherchais une exigence similaire, c'est-à-dire rejetant les commits intermédiaires de ma branche de développement, j'ai trouvé que cette procédure fonctionnait pour moi.
sur ma branche de travail

git reset –hard mybranch-start-commit
git checkout mybranch-end-commit . // files only of the latest commit
git add -a
git commit -m”New Message intermediate commits discarded”

alto nous avons connecté le dernier commit au commit de début de la branche! Aucun problème de conflit de fusion! Dans ma pratique d'apprentissage, j'en suis venu à cette conclusion à ce stade: Y a-t-il une meilleure approche à cet effet?

user28186
la source
FYI - J'essayais juste ceci et j'ai trouvé que la vérification de mybranch-end-commit ne m'obtient pas les suppressions qui se sont produites dans les commits intermédiaires. Donc, il ne vérifie que les fichiers qui existaient dans mybranch-end-commit.
ahains
1

En m'appuyant sur la bonne réponse de @ hlidka ci-dessus qui minimise les interventions manuelles, je voulais ajouter une version qui préserve tous les nouveaux commits sur master qui ne sont pas dans la branche pour squash.

Comme je le crois, ceux-ci pourraient être facilement perdus dans l' git resetétape de cet exemple.

# create a new branch 
# ...from the commit in master original_messy_branch was originally based on. eg 5654da06
git checkout -b new_clean_branch 5654da06

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
# ...base the reset on the base commit from Master
git reset --soft 5654da06       

git commit -m "Squashed changes from original_messy_branch"

# Rebase onto HEAD of master
git rebase origin/master

# Resolve any new conflicts from the new commits
JonoB
la source
0

Notez que -Xles options de stratégie et étaient ignorées lorsqu'elles étaient utilisées dans un rebase interactif.

Voir commit db2b3b820e2b28da268cc88adff076b396392dfe (juillet 2013, git 1.8.4+),

N'ignorez pas les options de fusion dans le rebase interactif

La stratégie de fusion et ses options peuvent être spécifiées dans git rebase, mais avec -- interactive, elles ont été complètement ignorées.

Signé par: Arnaud Fontaine

Cela signifie -Xque la stratégie fonctionne désormais avec le rebase interactif, ainsi que le rebase simple, et votre script initial pourrait désormais mieux fonctionner.

VonC
la source
0

Je rencontrais un problème plus simple mais similaire, où j'avais 1) résolu un conflit de fusion sur une branche locale, 2) continué à travailler en ajoutant beaucoup plus de petits commits, 3) je voulais rebaser et frapper des conflits de fusion.

Pour moi, a git rebase -p -i masterfonctionné. Cela a gardé l'engagement initial de résolution de conflit et m'a permis d'écraser les autres.

J'espère que cela aide quelqu'un!

abaldwinhunter
la source
J'ai encore quelques conflits à résoudre manuellement lors de la tentative de rebase avec -p Certes, il y avait beaucoup moins de conflits et ils étaient simplement limités au fichier de projet plutôt qu'à tous les fichiers de code avec lesquels j'avais des conflits auparavant. Apparemment, «les résolutions de conflits de fusion ou les modifications manuelles pour fusionner les validations ne sont pas conservées». - stackoverflow.com/a/35714301/727345
JonoB le