web-dev-qa-db-fra.com

Pourquoi git-rebase me donne-t-il des conflits de fusion alors que tout ce que je fais, c'est écraser les commits?

Nous avons un référentiel Git avec plus de 400 commits, dont la première douzaine a été un grand nombre d'essais et d'erreurs. Nous voulons nettoyer ces commits en en écrasant plusieurs en un seul. Naturellement, git-rebase semble être la voie à suivre. Mon problème est qu'il se termine par des conflits de fusion, et ces conflits ne sont pas faciles à résoudre. Je ne comprends pas pourquoi il devrait y avoir des conflits, car je ne fais qu'écraser les commits (pas les supprimer ni les réorganiser). 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. Initialement, 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 elle ne l'a pas non plus t travailler. De plus, je dois souligner que, en utilisant le code commenté dans repo_squash_helper.sh, j'ai enregistré le fichier d'origine sur lequel le script sed fonctionne et j'ai exécuté le script sed contre 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 serait un conflit, donc cela ne semble pas avoir autant d'importance quelle stratégie est utilisée. Tout conseil ou avis serait utile, mais surtout je veux juste que ce squash fonctionne.

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

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

Le message que j'obtiens 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

Il s'agit du premier choix après le premier commit de squash. Fonctionnement git status donne un répertoire de travail propre. Si je fais alors un git rebase --continue, Je reçois un message très similaire après quelques autres validations. Si je recommence, 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 donne 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 lance alors git status, Je reçois:

# 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 convient également de noter que si je regarde le "conflit", cela se résume à une seule ligne avec une version commençant par un caractère [tab] et l'autre avec quatre espaces. Cela semblait ê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 le fait que la source d'origine se trouvait sur une machine Windows.)

Si je corrige manuellement file_X.cpp, il échoue ensuite peu de temps après avec un autre conflit, cette fois entre un fichier (CMakeLists.txt) qu'une version pense qu'il devrait exister et qu'une version pense qu'il ne devrait pas. Si je corrige 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ù il y a maintenant des changements plutôt non triviaux. Ce n'est encore qu'environ 25% du chemin à travers les conflits.

Je dois également souligner, car cela pourrait être très important, que ce projet a commencé dans un référentiel svn. Cette histoire initiale a très probablement été importée de ce dépôt svn.

Mise à jour # 2:

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

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 a abouti aux mêmes résultats que ceux décrits précédemment.

Mise à jour # 3:

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

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

Je ne reçois plus les problèmes de rebase "rien à commettre", mais je reste avec les autres conflits.

Mise à jour avec un 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

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

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

P.P.P.S: 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 édition ou en mode affichage.

120
Ben Hocking

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 cas de test de mise en pension de jouets a une fusion - pire, il a une fusion avec des conflits. Et vous rebasez à travers la fusion. Sans pour autant -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 essaie de choisir le prochain commit, donc son patch peut ne pas s'appliquer . (Je pense que cela apparaît comme un conflit de fusion car git cherry-pick peut appliquer le correctif en effectuant une fusion à trois voies entre la validation d'origine, la validation actuelle et l'ancêtre commun.)

Malheureusement, comme nous l'avons noté dans les commentaires, -i et -p (préserver les fusions) ne s'entend pas très bien. Je sais que le travail d'édition/de reformulation fonctionne, et que la réorganisation ne fonctionne pas. Cependant, je crois que cela fonctionne bien avec les courges. Ce 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, bien que ce soit toujours possible. (Morale de l'histoire: nettoyer avec rebase -i avant fusion.)

Supposons donc 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 -p fonctionne comme prévu.

S'il y a des conflits, les choses deviennent un peu plus délicates. Cela fera du bon squashing, 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 --continue passer à autre chose. (Bien sûr, vous pouvez les résoudre à nouveau en consultant la version du commit de fusion d'origine.)

S'il vous arrive d'avoir rerere activé dans votre référentiel (rerere.enabled mis à true), ce sera beaucoup plus facile - git pourra re utiliser le re filaire re solution à partir du moment où vous avez eu à l'origine les conflits, et tout ce que vous avez à faire est de l'inspecter pour vous assurer qu'il a bien fonctionné, ajoutez les fichiers à l'index et continuez. (Vous pouvez même aller plus loin en activant rerere.autoupdate, et il les ajoutera pour vous, de sorte que la fusion n'échouera même pas). Je suppose, cependant, que vous n'avez jamais activé le rerere, vous devrez donc résoudre le conflit vous-même. *

* Ou, vous pouvez essayer le rerere-train.sh script de git-contrib, qui tente de "Amorcer [la] base de données rerere à partir des validations de fusion existantes" - en gros, il vérifie toutes les validations de fusion, essaie de les fusionner, et si la fusion échoue, il récupère les résultats et les montre à git-rerere. Cela pourrait prendre du temps et je ne l'ai jamais utilisé, mais cela pourrait être très utile.

62
Cascabel

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

Être maître:

# 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 master        

git commit -m "Squashed changes from original_messy_branch"
50
hlidka

Je cherchais une exigence similaire, c'est-à-dire rejeter les commissions 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épart de la branche! Aucun problème de conflit de fusion! Dans ma pratique d'apprentissage, je suis arrivé à cette conclusion à ce stade, Y a-t-il une meilleure approche à cette fin?.

4
user28186

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) voulu rebaser et frapper les conflits de fusion.

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

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

0
abaldwinhunter

Notez que -X et les options de stratégie ont été ignorées lors de l'utilisation dans un rebase interactif.

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

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

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

Signé par: Arnaud Fontaine

Cela signifie -X et la stratégie fonctionnent désormais avec un rebase interactif, ainsi qu'avec un rebase simple, et votre script initial pourrait désormais mieux fonctionner.

0
VonC