Git push rechazado después de rebase de rama de característica

920

OK, pensé que este era un simple escenario git, ¿qué me estoy perdiendo?

Tengo una masterrama y una featurerama. Realizo algunos trabajos master, otros feature, y luego otros master. Termino con algo como esto (el orden lexicográfico implica el orden de los commits):

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

No tengo ningún problema para git push origin mastermantener masteractualizado el control remoto , ni con git push origin feature(cuando está activado feature) para mantener una copia de seguridad remota para mi featuretrabajo. Hasta ahora, estamos bien.

Pero ahora quiero hacer una nueva base featuresobre los F--Gcommits en master, así que yo git checkout featurey git rebase master. Sigue bien. Ahora tenemos:

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

Problema: en el momento en que quiero hacer una copia de seguridad de la nueva bifurcación featureramificada git push origin feature, el empuje se rechaza ya que el árbol ha cambiado debido a la rebase. Esto solo se puede resolver con git push --force origin feature.

Odio usar --forcesin estar seguro de que lo necesito. Entonces, ¿lo necesito? ¿El rebase implica necesariamente que el próximo pushdebe ser --forcecompleto?

Esta rama de características no se comparte con ningún otro desarrollador, por lo que no tengo ningún problema de facto con el empuje de fuerza, no voy a perder ningún dato, la pregunta es más conceptual.

Yuval Adam
fuente

Respuestas:

682

El problema es que se git pushsupone que la rama remota se puede reenviar rápidamente a su rama local, es decir, que toda la diferencia entre las ramas locales y remotas está en que el local tenga algunas confirmaciones nuevas al final de la siguiente manera:

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

Cuando realiza git rebaseconfirmaciones, D y E se aplican a una nueva base y se crean nuevas confirmaciones. Eso significa que después de rebase tienes algo así:

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

En esa situación, la rama remota no se puede reenviar rápidamente a local. Sin embargo, teóricamente, la rama local se puede fusionar en remota (obviamente no la necesita en ese caso), pero como git pushrealiza solo fusiones de avance rápido, se produce un error.

Y lo que --forcehace la opción es simplemente ignorar el estado de la rama remota y configurarlo para la confirmación que está presionando. Tan git push --force origin feature-branchsimplemente reemplaza origin/feature-brancha local feature-branch.

En mi opinión, la función de rebasamiento de ramas se masteractiva y la fuerza de empujarlos de vuelta al repositorio remoto está bien, siempre y cuando usted sea el único que trabaje en esa rama.

KL-7
fuente
68
Para ser honesto, extraer y fusionar la versión original de la rama de características en la versión rebase elimina un poco la idea de rebase.
KL-7
24
Tal vez no te entendí correctamente, pero si extraes una rama de características, la vuelves a armar en una rama maestra nueva, no puedes retroceder sin fuerza, porque la versión remota de la rama de características no se puede enviar rápidamente a tu nueva versión (modificada) de la rama de características. Eso es exactamente lo que OP describió en su pregunta. Si después de la reorganización, pero antes de presionar, lo hace git pull feature-branch, esta extracción generará una nueva confirmación de fusión (al fusionar versiones remotas y locales de la rama de características). Entonces, o obtienes una fusión innecesaria después de rebase, o presionas --force.
KL-7
66
Ah, creo que lo tengo. Estás describiendo el mismo enfoque que en la respuesta de Mark Longair. Pero sí genera una confirmación de fusión. Puede ser útil en algunos casos, pero uso rebase principalmente en mis propias ramas de características (por push --forcelo tanto, no es un problema) para mantener el historial de commits lineal sin ningún commit de fusión.
KL-7
11
El problema con la "fuerza de empuje" es que, de hecho, puede "perder cosas" (confirmaciones anteriores), algo que normalmente NUNCA debería ser posible en ningún sistema de control de versiones ➪ Por esa razón, al menos una rama "maestra" debería tener la configuración para no aceptar empujes forzados , para limitar el daño potencial. (Nombre cualquiera de los siguientes: empleados gruñones / despedidos, propia idiotez, 'decisiones' cansadas y con exceso de trabajo ...).
Frank Nocke
13
--force-with-leasecomo @hardev sugerido es una gran opción
augustorsouza
466

En lugar de usar -f o --force los desarrolladores deberían usar

--force-with-lease

¿Por qué? Porque comprueba la rama remota en busca de cambios, lo cual es absolutamente una buena idea. Imaginemos que James y Lisa están trabajando en la misma rama de características y Lisa ha empujado una confirmación. James ahora rebasa su rama local y es rechazado cuando intenta presionar. Por supuesto, James piensa que esto se debe a un rebase y usa --force y volvería a escribir todos los cambios de Lisa. Si James hubiera usado la fuerza con arrendamiento, habría recibido una advertencia de que alguien más ha cometido compromisos. No veo por qué alguien usaría --force en lugar de --force-with-lease al presionar después de un rebase.

Hardev
fuente
33
Gran explicación git push --force-with-leaseme ha ahorrado un montón
ckib16 el
55
Este es un comentario útil, pero en realidad no es una respuesta a la pregunta.
Dallin
44
Esta es la respuesta, volver a crear un master / desarrollo crea un problema, esta es exactamente la razón por la cual existe --force-with-lease.
Tamir Daniely
3
Esta debería ser la respuesta aceptada. Resuelve exactamente el problema descrito: forzar a empujar sin forzar si alguien más cometió mientras tanto.
Luzian
3
Creo que tanto la respuesta aceptada como esta abordan la pregunta. La respuesta aceptada explica por qué necesita forzar. Y este explica por qué --force-with-leaseaborda la preocupación de usar--force
Jeff Appareti
48

En su lugar, usaría "checkout -b" y es más fácil de entender.

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

cuando eliminas, evitas empujar una rama existente que contiene una ID de SHA diferente. Solo estoy borrando la rama remota en este caso.

Eddy Hernández
fuente
66
Esto funciona muy bien, especialmente si su equipo tiene un gancho git que rechaza todos los comandos git push --force.
Ryan Thames
1
gracias por eso funcionó bien. Aquí hay más detalles sobre lo que leí para entender mejor. Esto es muy útil cuando no quieres o no puedes hacer un empuje forzado. Eliminación de ramas remotas y refuerzo
RajKon
55
Esto tiene el mismo resultado que push --force, por lo que es solo una forma de evitar un repositorio git evitando --force. Como tal, no creo que sea una buena idea, ya sea que el repositorio lo permita push --force, o por una buena razón lo deshabilita. La respuesta de Nabi es más apropiada si --forceestá deshabilitada en el repositorio remoto, ya que no tiene el riesgo de perder confirmaciones de otros desarrolladores o causar problemas.
Recogida de Logan el
19

Una solución a esto es hacer lo que hace el script de fusión de rebase de msysGit : después del rebase, fusionarse en el antiguo encabezado de featurewith -s ours. Terminas con el gráfico de confirmación:

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

... y tu impulso featureserá un avance rápido.

En otras palabras, puedes hacer:

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

(No probado, pero creo que es correcto ...)

Mark Longair
fuente
27
Creo que la razón más común para usar git rebase(en lugar de fusionarse masternuevamente en su rama de características) es hacer un historial de confirmaciones lineales limpias. Con su enfoque, la historia empeora aún más. Y como el rebase crea nuevas confirmaciones sin ninguna referencia a sus versiones anteriores, ni siquiera estoy seguro de que el resultado de esta fusión sea adecuado.
KL-7
66
@ KL-7: El objetivo principal de esto merge -s ourses que agrega artificialmente una referencia principal a la versión anterior. Claro, la historia no parece limpia, pero el interlocutor parece estar particularmente molesto por tener que forzar el empuje de la featurerama, y ​​esto lo evita. Si quieres hacer una nueva versión, es más o menos uno u otro. :) En general, creo que es interesante que el proyecto msysgit haga esto ...
Mark Longair
@ KL-7: Por cierto, escribí tu respuesta en I + 1, que es claramente la correcta, solo pensé que esto podría ser interesante también.
Mark Longair
Definitivamente es interesante, al menos para mí. Gracias. He visto oursestrategias antes, pero pensé que se aplicaban solo a situaciones de conflicto resolviéndolas automáticamente usando cambios en nuestra sucursal. Resultó que funciona de manera diferente. Y trabajar de esa manera es muy útil si necesita una versión modificada (p. Ej., Para que el mantenedor de repositorios la aplique limpiamente master) pero desea evitar forzar la presión (si muchas otras personas por alguna razón están usando su rama de características).
KL-7
15

Puede ser o no el caso de que solo haya un desarrollador en esta rama, que ahora (después de la modificación) no está en línea con el origen / característica.

Como tal, sugeriría usar la siguiente secuencia:

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

Sí, nueva rama, esto debería resolver esto sin una fuerza, lo que creo que generalmente es un gran inconveniente.

JAR.JAR. frijoles
fuente
3
Lamento decirlo: "Continuar generando sucursales" para evitar forzar la anulación de las existentes no ayuda a "desarrolladores de funciones solitarios" (que pueden anular) ni a varias personas que trabajan en una rama de funciones (necesitan comunicar ese "incremento" de rama y decir para moverse, amigos). - Es más como el control de versiones manual ("thesis_00.doc, thesis_01.doc, ..."), dentro de un sistema de control de versiones ...
Frank Nocke
2
Además, esto no ayuda cuando tiene un PR github abierto en el nombre de una sucursal, tendría que crear un nuevo PR para el nuevo nombre de sucursal que presionó.
gprasant
1
@frankee Medio cierto desde mi experiencia. para un desarrollador solitario, sí, simplemente forzar el empuje es bastante fácil, pero es el hábito que puede morderte más tarde. + un nuevo desarrollador acaba de unirse? o tal vez algún sistema de CI que no esté usando - ¿restablecimiento completo? para un equipo que colabora, creo que comunicar el nombre de la nueva sucursal es bastante fácil, esto también se puede incluir fácilmente en un guión + para un equipo, sugeriría una nueva base local o cuando la sucursal esté lista para fusionarse, no durante el día a día , la confirmación adicional es menos problemática que tratar con conflictos de rebase / fusión como resultado.
JAR.JAR.beans
@gprasant para relaciones públicas, una vez más, creo que sería incorrecto volver a redactar, en realidad me gustaría ver las confirmaciones individuales con las correcciones de relaciones públicas. Un rebase (squash) debe ocurrir solo más tarde como parte de la fusión para dominar y cuando el RP esté listo y listo (por lo que no es necesario abrir ningún RP nuevo).
JAR.JAR.beans
13

Mi forma de evitar el empuje de la fuerza es crear una nueva rama y continuar trabajando en esa nueva rama y, después de un poco de estabilidad, eliminar la vieja rama que se volvió a armar:

  • Rebasar localmente la sucursal desprotegida
  • Ramificación de la rama rebaseada a una nueva rama
  • Empujar esa rama como una nueva rama al control remoto. y eliminar la rama vieja en remoto
Nabi
fuente
1
¿Por qué no te gusta esta opción? Definitivamente es el más limpio, simple y seguro.
cdmo
Debido a que tengo alrededor de 200 sistemas que rastrean el nombre de la sucursal, y tiene que ser un nombre específico para la tarea, y si empiezo a cambiar el nombre de la sucursal cada vez que lo presiono, perderé mi mente.
Tamir Daniely
@TamirDaniely No lo he intentado, pero ¿eliminar el problema elimina la eliminación de la rama anterior (del control remoto) antes de presionar y presionar la nueva rama con el mismo nombre anterior?
Nabi
2
@Nabi Eso es exactamente lo que hace --force-with-lease, excepto que también verifica que no hay nuevos compromisos que no sean tuyos.
Tamir Daniely
12

Otros han respondido tu pregunta. Si rebasas una rama, tendrás que forzarla a empujar esa rama.

Rebase y un repositorio compartido generalmente no se llevan bien. Esto es reescribir la historia. Si otros están usando esa rama o se han bifurcado de esa rama, entonces el rebase será bastante desagradable.

En general, rebase funciona bien para la administración de sucursales locales. La administración remota de sucursales funciona mejor con fusiones explícitas (--no-ff).

También evitamos fusionar master en una rama de características. En su lugar, cambiamos a maestro pero con un nuevo nombre de rama (por ejemplo, agregando un sufijo de versión). Esto evita el problema de rebase en el repositorio compartido.

Bill Door
fuente
55
¿Podría agregar un ejemplo por favor?
Thermech
8

¿Qué hay de malo con un git merge masteren la featurerama? Esto preservará el trabajo que tenía, mientras lo mantiene separado de la rama principal.

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

Editar: Ah, lo siento, no leí tu declaración de problema. Necesitará fuerza mientras realiza a rebase. Todos los comandos que modifican el historial necesitarán el --forceargumento. Esto es a prueba de fallas para evitar que pierda el trabajo (lo antiguo Dy Elo que se perdería).

Así que realizó una git rebaseque hizo que el árbol se vea (aunque parcialmente oculto como Dy Eya no está en una rama con nombre):

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

Entonces, al tratar de impulsar su nueva featurerama (con D'y E'dentro de ella), perdería Dy E.

Bouke
fuente
3
No hay nada de malo en eso, y sé que funcionará. Simplemente no es lo que necesito. Como dije, la pregunta es más conceptual que práctica.
Yuval Adam
4

Para mí, seguir pasos sencillos funciona:

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

Después de hacer todo lo anterior, también podemos eliminar la rama myFeature siguiendo el comando:

git push origin --delete myFeature
Neeraj Khede
fuente
3

Lo siguiente funciona para mí:

git push -f origin branch_name

y no elimina ninguno de mi código.

Pero, si desea evitar esto, puede hacer lo siguiente:

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

entonces puedes elegir todos tus commits para la nueva sucursal. git cherry-pick COMMIT ID y luego empuje su nueva rama.

Sohair Ahmad
fuente
55
-fes un alias para --force, que es lo que la pregunta intenta evitar si es posible.
epochengine
1

Como el OP entiende el problema, solo busca una solución más agradable ...

¿Qué tal esto como práctica?

  • Tenga una rama de desarrollo de características real (donde nunca rebase y fuerce, para que sus compañeros desarrolladores de características no lo odien). Aquí, regularmente toma esos cambios de main con una fusión. Una historia más desordenada , sí, pero la vida es fácil y nadie se interrumpe en su trabajo.

  • Tenga una segunda rama de desarrollo de características, donde un miembro del equipo de características regularmente empuja todos los compromisos de características a, de hecho rebase, de hecho forzado. De manera casi limpia basada en un compromiso maestro bastante reciente. Una vez completada la función, empuje esa rama encima del maestro.

Es posible que ya haya un nombre de patrón para este método.

Frank Nocke
fuente