Git push rejeté après le rebasage de la branche de fonctionnalité

920

OK, je pensais que c'était un simple scénario git, qu'est-ce qui me manque?

J'ai une mastersuccursale et une featuresuccursale. Je fais un peu de travail master, d'autres feature, puis d'autres encore master. Je me retrouve avec quelque chose comme ça (l'ordre lexicographique implique l'ordre des commits):

A--B--C------F--G  (master)
       \    
        D--E  (feature)

Je n'ai aucun problème à git push origin mastermaintenir la télécommande à masterjour, ni à git push origin feature(lorsqu'elle est activée feature) de maintenir une sauvegarde à distance pour mon featuretravail. Jusqu'à présent, nous sommes bons.

Mais maintenant, je veux rebaser featureau-dessus des F--Gcommits sur master, donc moi git checkout featureet git rebase master. Encore bon. Maintenant nous avons:

A--B--C------F--G  (master)
                 \
                  D'--E'  (feature)

Problème: au moment où je veux sauvegarder le nouveau featureramifié avec branchement git push origin feature, le push est rejeté car l'arborescence a changé en raison du rebasage. Cela ne peut être résolu qu'avec git push --force origin feature.

Je déteste utiliser --forcesans être sûr d'en avoir besoin. Alors, en ai-je besoin? Le rebasage implique- t-il nécessairement que le prochain pushsoit --forcecomplet?

Cette branche de fonctionnalité n'est partagée avec aucun autre développeur, donc je n'ai aucun problème de facto avec la poussée de force, je ne vais pas perdre de données, la question est plus conceptuelle.

Yuval Adam
la source

Réponses:

682

Le problème est que cela git pushsuppose que la branche distante peut être rapidement transmise à votre branche locale, c'est-à-dire que toute la différence entre les branches locales et distantes réside dans le fait que local a de nouveaux validations à la fin comme ça:

Z--X--R         <- origin/some-branch (can be fast-forwarded to Y commit)
       \        
        T--Y    <- some-branch

Lorsque vous effectuez des git rebasevalidations, D et E sont appliqués à une nouvelle base et de nouvelles validations sont créées. Cela signifie qu'après rebase, vous avez quelque chose comme ça:

A--B--C------F--G--D'--E'   <- feature-branch
       \  
        D--E                <- origin/feature-branch

Dans cette situation, la succursale distante ne peut pas être transmise rapidement au local. Bien que, théoriquement, la branche locale puisse être fusionnée en distante (évidemment vous n'en avez pas besoin dans ce cas), mais comme elle git pusheffectue uniquement une fusion rapide, elle génère des erreurs et des erreurs.

Et quelle --forceoption ne fait qu'ignorer l'état de la branche distante et la définir sur la validation que vous y insérez. Remplace donc git push --force origin feature-branchsimplement origin/feature-branchavec local feature-branch.

À mon avis, rebaser les branches de fonctionnalités masteret les forcer à revenir au référentiel distant est OK tant que vous êtes le seul à travailler sur cette branche.

KL-7
la source
68
Pour être honnête, extraire et fusionner la version originale de la branche de fonctionnalité dans la branche rebasée élimine un peu l'idée du rebasage.
KL-7
24
Peut-être que je ne vous ai pas bien compris, mais si vous tirez la branche de fonctionnalité, la rebase sur une nouvelle branche principale, vous ne pouvez pas la repousser sans force, car la version distante de la branche de fonctionnalité ne peut pas être transmise rapidement à votre nouvelle version (rebasée) de la branche de fonctionnalité. C'est exactement ce que OP a décrit dans sa question. Si après le rebasage, mais avant de pousser, vous le faites git pull feature-branch, ce pull va générer un nouveau commit de fusion (en fusionnant les versions distantes et locales de la branche de fonctionnalité). Donc, soit vous obtenez une fusion inutile après le rebasage, soit vous poussez avec --force.
KL-7
6
Ah, je pense que je l'ai. Vous décrivez la même approche que dans la réponse de Mark Longair. Mais cela génère un commit de fusion. Cela peut être utile dans certains cas, mais j'utilise principalement le rebase dans mes propres branches de fonctionnalités (ce push --forcen'est donc pas un problème) pour garder l'historique des validations linéaire sans aucune validation de fusion.
KL-7
11
Le problème avec «force-push» est que vous pouvez en effet «perdre du contenu» (commits antérieurs), quelque chose qui normalement ne devrait JAMAIS être possible dans n'importe quel système de contrôle de version ➪ Pour cette raison, au moins une branche «master-ish» devrait avoir les paramètres pour ne pas accepter les poussées de force , pour limiter les dommages potentiels. (Nommez l'un des employés suivants: employés grincheux / licenciés, propre idiotie, «décisions» fatiguées et surchargées de travail ...).
Frank Nocke
13
--force-with-leasecomme l'a suggéré @hardev est une excellente option
augustorsouza
466

Au lieu d'utiliser -f ou --force, les développeurs devraient utiliser

--force-with-lease

Pourquoi? Parce qu'il vérifie les changements de la branche distante, ce qui est absolument une bonne idée. Imaginons que James et Lisa travaillent sur la même branche de fonctionnalité et Lisa a poussé un commit. James rebase maintenant sa branche locale et est rejeté lorsqu'il essaie de pousser. Bien sûr, James pense que cela est dû à un rebase et utilise --force et réécrirait toutes les modifications de Lisa. Si James avait utilisé --force-with-lease, il aurait reçu un avertissement indiquant que quelqu'un d'autre avait commis des commits. Je ne vois pas pourquoi quelqu'un utiliserait --force au lieu de --force-with-lease en poussant après un rebase.

Hardev
la source
33
Grande explication. git push --force-with-leasem'a sauvé un tas.
ckib16
5
C'est un commentaire utile, mais ce n'est pas vraiment une réponse à la question.
Dallin
4
C'est la réponse, le rebasage pour maîtriser / développer crée un problème, c'est exactement pourquoi --force-with-lease existe.
Tamir Daniely
3
Ce devrait être la réponse acceptée. Résout exactement le problème décrit - forcer à pousser sans forcer si quelqu'un d'autre s'est engagé entre-temps.
Luzian
3
Je pense que la réponse acceptée et celle-ci répondent à la question. La réponse acceptée explique pourquoi vous devez forcer. Et celui-ci explique pourquoi --force-with-leaserépond au souci de l'utilisation--force
Jeff Appareti
48

J'utiliserais plutôt "checkout -b" et c'est plus facile à comprendre.

git checkout myFeature
git rebase master
git push origin --delete myFeature
git push origin myFeature

lorsque vous supprimez, vous empêchez d'insérer une branche existante contenant un ID SHA différent. Je supprime uniquement la branche distante dans ce cas.

Eddy Hernandez
la source
6
Cela fonctionne très bien, surtout si votre équipe a un crochet git qui rejette toutes les commandes git push --force.
Ryan Thames
1
merci pour ça ça a bien fonctionné. Voici plus de détails sur ce que j'ai lu pour mieux comprendre. Ceci est très utile lorsque vous ne voulez pas ou ne pouvez pas effectuer une poussée forcée. Suppression de branches distantes et rebasage
RajKon
5
Cela a le même résultat que push --force, donc c'est seulement un moyen de contourner un git repo empêchant --force. En tant que tel, je ne pense pas que ce soit jamais une bonne idée - soit le repo le permet push --force, soit pour une bonne raison, il le désactive. La réponse de Nabi est plus appropriée si elle --forceest désactivée sur le référentiel distant, car elle n'a pas le risque de perdre les commits d'autres développeurs ou de causer des problèmes.
Ramassage Logan
19

Une solution à cela est de faire ce que le script de fusion de rebasage de msysGit fait - après le rebase, fusionnez dans l'ancienne tête de featureavec -s ours. Vous vous retrouvez avec le graphe de validation:

A--B--C------F--G (master)
       \         \
        \         D'--E' (feature)
         \           /
          \       --
           \    /
            D--E (old-feature)

... et votre poussée featuresera une avance rapide.

En d'autres termes, vous pouvez faire:

git checkout feature
git branch old-feature
git rebase master
git merge -s ours old-feature
git push origin feature

(Non testé, mais je pense que c'est vrai ...)

Mark Longair
la source
27
Je crois que la raison la plus courante de l'utilisation git rebase(au lieu de fusionner masterdans votre branche de fonctionnalité) est de rendre l'historique des validations linéaires propres. Avec votre approche, l'histoire devient encore pire. Et comme le rebasage crée de nouveaux commits sans aucune référence à leurs versions précédentes, je ne suis même pas sûr que le résultat de cette fusion sera adéquat.
KL-7
6
@ KL-7: L'intérêt de la merge -s oursest qu'elle ajoute artificiellement une référence parent à la version précédente. Bien sûr, l'histoire ne semble pas propre, mais le questionneur semble être particulièrement dérangé par le fait de forcer la poussée de la featurebranche, et cela contourne cela. Si vous voulez rebaser, c'est plus ou moins l'un ou l'autre. :) Plus généralement, je pense qu'il est intéressant que le projet msysgit fasse cela ....
Mark Longair
@ KL-7: Soit dit en passant, j'ai attribué +1 à votre réponse, qui est clairement la bonne - je pensais que cela pourrait aussi être intéressant.
Mark Longair
C'est vraiment intéressant, du moins pour moi. Je vous remercie. J'ai déjà vu une oursstratégie, mais je pensais qu'elle ne s'applique qu'aux situations de conflit en les résolvant automatiquement en utilisant des changements dans notre branche. Il s'est avéré que cela fonctionne différemment. Et travailler de cette façon est très utile si vous avez besoin d'une version rebasée (par exemple, pour que le responsable du référentiel l'applique proprement master) mais que vous voulez éviter de forcer la poussée (si beaucoup d'autres ppl utilisent votre branche de fonctionnalité pour une raison quelconque).
KL-7
15

Il se peut ou non qu'il n'y ait qu'un seul développeur sur cette branche, qui est maintenant (après le rebase) non aligné avec l'origine / la fonctionnalité.

En tant que tel, je suggère d'utiliser la séquence suivante:

git rebase master
git checkout -b feature_branch_2
git push origin feature_branch_2

Ouais, nouvelle branche, cela devrait résoudre cela sans --force, ce qui, je pense, est généralement un gros inconvénient.

JAR.JAR.beans
la source
3
Désolé de le dire, mais: "Continuer à générer des branches" pour éviter de forcer celles qui existent déjà n'aide pas les "développeurs de fonctionnalités solitaires" (qui peuvent remplacer) ni plusieurs personnes travaillant sur une branche de fonctionnalités (doivent communiquer cette branche "incrémenter" et dire déplacer, les gens). - C'est plus comme du versioning manuel ("thesis_00.doc, thesis_01.doc, ..."), dans un système de versioning ...
Frank Nocke
2
De plus, cela n'aide pas lorsque vous avez un github PR ouvert sur un nom de branche, vous devez créer un nouveau PR pour le nouveau nom de branche que vous avez poussé.
gprasant
1
@frankee moitié vrai de mon expérience. pour un développeur solitaire, oui, juste pousser de force est assez facile, mais c'est l'habitude qui pourrait vous mordre plus tard. + un nouveau développeur vient de rejoindre? ou peut-être un système CI qui n'utilise pas --hard reset? pour une équipe qui collabore, je pense que la communication du nouveau nom de la branche est assez facile, cela peut aussi facilement être scripté + pour une équipe, je suggère de rebaser localement ou lorsque la branche est prête à fusionner, pas pendant le travail quotidien , la validation supplémentaire est moins gênante que la gestion des conflits de rebase / fusion en conséquence.
JAR.JAR.beans
@gprasant for PR, encore une fois, je pense que ce serait une erreur de rebaser, je voudrais en fait voir les validations individuelles avec les correctifs PR. Un rebase (squash) ne devrait se produire que plus tard dans le cadre de la fusion à maîtriser et lorsque le PR est terminé et prêt (donc aucun nouveau PR n'a besoin d'être ouvert).
JAR.JAR.beans
13

Ma façon d'éviter la poussée de force est de créer une nouvelle branche et de continuer le travail sur cette nouvelle branche et après une certaine stabilité, de supprimer l'ancienne branche qui a été rebasée:

  • Redéfinir localement la branche extraite
  • Branchement de la branche rebasée vers une nouvelle branche
  • Pousser cette branche en tant que nouvelle branche à distance. et supprimer l'ancienne branche sur la télécommande
Nabi
la source
1
Pourquoi pas d'amour pour cette option? C'est certainement le plus propre, le plus simple, le plus sûr.
cdmo
Parce que j'ai environ 200 systèmes qui suivent le nom de la branche et que ce doit être un nom spécifique pour la tâche, et si je commence à renommer une branche à chaque poussée, je perdrai la tête.
Tamir Daniely,
@TamirDaniely Je n'ai pas essayé, mais la suppression de l'ancienne branche (à distance) avant de pousser et de pousser la nouvelle branche avec le même ancien nom résout-elle votre problème?
Nabi
2
@Nabi C'est exactement ce que fait --force-with-lease, sauf qu'il vérifie également qu'il n'y a pas de nouveaux commits qui ne sont pas les vôtres.
Tamir Daniely
12

D'autres ont répondu à votre question. Si vous rebasez une branche, vous devrez forcer à pousser cette branche.

Rebase et un référentiel partagé ne s'entendent généralement pas. C'est réécrire l'histoire. Si d'autres utilisent cette branche ou se sont branchés à partir de cette branche, le rebasage sera assez désagréable.

En général, le rebase fonctionne bien pour la gestion des succursales locales. La gestion des succursales à distance fonctionne mieux avec les fusions explicites (--no-ff).

Nous évitons également de fusionner master dans une branche de fonctionnalité. Au lieu de cela, nous rebase à master mais avec un nouveau nom de branche (par exemple en ajoutant un suffixe de version). Cela évite le problème du rebasage dans le référentiel partagé.

Bill Door
la source
5
Pourriez-vous ajouter un exemple s'il vous plaît?
Thermech
8

Quel est le problème avec un git merge mastersur la featurebranche? Cela préservera le travail que vous aviez, tout en le gardant séparé de la branche principale.

A--B--C------F--G
       \         \
        D--E------H

Edit: Ah désolé, je n'ai pas lu votre énoncé de problème. Vous aurez besoin de force pendant que vous exécutez a rebase. Toutes les commandes qui modifient l'historique auront besoin de l' --forceargument. Il s'agit d'une sécurité intégrée pour vous empêcher de perdre du travail (l'ancien Det Eserait perdu).

Vous avez donc effectué un git rebasequi a fait ressembler l'arbre (bien qu'il soit partiellement masqué Det Ene se trouve plus dans une branche nommée):

A--B--C------F--G
       \         \
        D--E      D'--E'

Ainsi, lorsque vous essayez de pousser votre nouvelle featurebranche (avec D'et E'en elle), vous perdrez Det E.

Bouke
la source
3
Il n'y a rien de mal à cela, et je sais que cela fonctionnera. Ce n'est tout simplement pas ce dont j'ai besoin. Comme je l'ai dit, la question est plus conceptuelle que pratique.
Yuval Adam
4

Pour moi, suivre les étapes faciles fonctionne:

1. git checkout myFeature
2. git rebase master
3. git push --force-with-lease
4. git branch -f master HEAD
5. git checkout master
6. git pull

Après avoir fait tout ce qui précède, nous pouvons également supprimer la branche myFeature en suivant la commande:

git push origin --delete myFeature
Neeraj Khede
la source
3

Ce qui suit fonctionne pour moi:

git push -f origin branch_name

et il ne supprime aucun de mon code.

Mais, si vous voulez éviter cela, vous pouvez procéder comme suit:

git checkout master
git pull --rebase
git checkout -b new_branch_name

vous pouvez ensuite sélectionner tous vos commits dans la nouvelle branche. git cherry-pick COMMIT ID puis appuyez sur votre nouvelle branche.

Sohair Ahmad
la source
5
-fest un alias pour --force, ce que la question tente d'éviter si possible.
epochengine
1

Comme l'OP comprend le problème, cherche juste une meilleure solution ...

Que diriez-vous de cela en tant que pratique?

  • Avoir une branche de développement de fonctionnalités réelle (où vous ne rebaserez jamais et ne forcez pas, afin que vos collègues développeurs de fonctionnalités ne vous détestent pas). Ici, récupérez régulièrement ces changements de main avec une fusion. Une histoire plus désordonnée , oui, mais la vie est facile et personne ne s'interrompt dans son travail.

  • Avoir une deuxième branche de développement de fonctionnalités, où un membre de l'équipe de fonctionnalités pousse régulièrement toutes les validations de fonctionnalités, voire rebasées, voire forcées. Donc, presque proprement basé sur un commit maître assez récent. Une fois la fonctionnalité terminée, poussez cette branche sur le maître.

Il existe peut-être déjà un nom de modèle pour cette méthode.

Frank Nocke
la source