¿Cómo recupero / resincronizo después de que alguien empuja una rebase o un reinicio a una rama publicada?

88

Todos hemos oído que uno nunca debe publicada rebase el trabajo, que es peligroso, etc. Sin embargo, no he visto ninguna de las recetas publicadas de cómo hacer frente a la situación en caso de un rebase se publicó.

Ahora, tenga en cuenta que esto solo es realmente factible si el repositorio solo es clonado por un grupo conocido (y preferiblemente pequeño) de personas, de modo que quien empuje el rebase o reinicio pueda notificar a todos los demás que deberán prestar atención la próxima vez que lo hagan. ir a buscar(!).

Una solución obvia que he visto funcionará si no tiene confirmaciones locales fooy se vuelve a basar:

git fetch
git checkout foo
git reset --hard origin/foo

Esto simplemente descartará el estado local de fooa favor de su historial según el repositorio remoto.

Pero, ¿cómo se maneja la situación si se han realizado cambios locales sustanciales en esa rama?

Aristóteles Pagaltzis
fuente
+1 para la receta del caso simple. Es ideal para la sincronización personal entre máquinas, especialmente si tienen diferentes sistemas operativos. Es algo que debería mencionarse en el manual.
Philip Oakley
La receta ideal para la sincronización personal es git pull --rebase && git push. Si mastersolo trabajas , esto hará casi infaliblemente lo correcto para ti, incluso si has rebasado y empujado al otro extremo.
Aristóteles Pagaltzis
Debido a que estoy sincronizando y desarrollando entre una PC y una máquina Linux, encuentro que usar una nueva rama para cada rebase / actualización funciona bien. También uso la variante git reset --hard @{upstream}ahora que sé que el encantamiento refspec mágico para "olvidar lo que tengo / tuve, usar lo que obtuve del control remoto". Ver mi comentario final en stackoverflow.com/a/15284176/717355
Philip Oakley
Podrá, con Git2.0, encontrar el origen anterior de su rama (antes de que la rama ascendente se reescribiera con a push -f): vea mi respuesta a continuación
VonC

Respuestas:

75

Volver a sincronizarse después de un rebase empujado no es realmente tan complicado en la mayoría de los casos.

git checkout foo
git branch old-foo origin/foo # BEFORE fetching!!
git fetch
git rebase --onto origin/foo old-foo foo
git branch -D old-foo

Es decir. primero configura un marcador para el lugar donde originalmente estaba la rama remota, luego lo usa para reproducir sus confirmaciones locales desde ese punto en adelante en la rama remota rebasada.

Volver a basarse es como la violencia: si no resuelve tu problema, solo necesitas más. ☺

Puede hacer esto sin el marcador, por supuesto, si busca el origin/fooID de confirmación anterior a la rebase y lo usa.

Así es también como maneja la situación en la que olvidó hacer un marcador antes de buscar. No se pierde nada, solo necesita verificar el reflog de la rama remota:

git reflog show origin/foo | awk '
    PRINT_NEXT==1 { print $1; exit }
    /fetch: forced-update/ { PRINT_NEXT=1 }'

Esto imprimirá el ID de confirmación que origin/fooapuntaba antes de la recuperación más reciente que cambió su historial.

Entonces puede simplemente

git rebase --onto origin/foo $commit foo
Aristóteles Pagaltzis
fuente
11
Nota rápida: creo que es bastante intuitivo, pero si no conoces bien awk ... esa git reflog show origin/foofrase de una sola línea solo busca en la salida de la primera línea que dice "fetch: force-update"; eso es lo que registra git cuando una búsqueda hace que la rama remota haga cualquier cosa menos adelantar. (También puede hacerlo a mano; la actualización forzada es probablemente lo más reciente).
Cascabel
2
No es nada como la violencia. La violencia es ocasionalmente divertida
Iolo
5
@iolo Es cierto, el rebase es siempre divertido.
Dan Bechard
1
Al igual que la violencia, casi siempre evita rebasar. Pero tenga una idea de cómo.
Bob Stein
2
Bueno, evite empujar una rebase donde otros se verán afectados.
Aristóteles Pagaltzis
11

Yo diría que la sección de recuperación de la rebase ascendente de la página de manual de git-rebase cubre prácticamente todo esto.

Realmente no es diferente de recuperarse de su propia base de datos: mueve una rama y vuelve a ajustar todas las ramas que la tenían en su historial en su nueva posición.

Cascabel
fuente
4
Ah, así es. Pero aunque ahora entiendo lo que dice, no lo habría hecho antes, antes de descubrir esto por mi cuenta. Y no hay una receta de libro de cocina (quizás con razón en dicha documentación). También expondré que llamar al "caso difícil" es difícil para FUD. Yo sostengo que la historia reescrita es trivialmente manejable a la escala de la mayoría del desarrollo interno. Me molesta la manera supersticiosa en que siempre se trata este tema.
Aristóteles Pagaltzis
4
@Aristotle: Tienes razón en que es muy manejable, dado que todos los desarrolladores saben cómo usar git y que puedes comunicarte de forma eficaz con todos los desarrolladores. En un mundo perfecto, ese sería el final de la historia. Pero muchos proyectos son lo suficientemente grandes como para que una reorganización ascendente realmente sea algo aterrador. (Y luego hay lugares como mi lugar de trabajo, donde la mayoría de los desarrolladores ni siquiera han oído hablar de un cambio de base). Creo que la "superstición" es solo una forma de proporcionar el consejo más seguro y genérico posible. Nadie quiere ser el que cause un desastre en el repositorio de otra persona.
Cascabel
2
Sí, entiendo el motivo. Y estoy completamente de acuerdo con eso. Pero hay un mundo de diferencia entre “no intentes esto si no entiendes las consecuencias” y “nunca debes hacer eso porque es malo”, y solo estoy en desacuerdo con esto. Siempre es mejor instruir que infundir miedo.
Aristóteles Pagaltzis
@Aristóteles: De acuerdo. Intento tender hacia el final de "asegúrese de saber lo que está haciendo", pero especialmente en línea, trato de darle suficiente peso para que un visitante ocasional de Google tome nota. Tienes razón, mucho de eso probablemente debería atenuarse.
Cascabel
11

A partir de git 1.9 / 2.0 Q1 2014, no tendrá que marcar el origen de su rama anterior antes de volver a basarlo en la rama ascendente reescrita, como se describe en la respuesta de Aristóteles Pagaltzis :
consulte la confirmación 07d406b y la confirmación d96855f :

Después de trabajar en la topicrama creada con git checkout -b topic origin/master, es posible que el historial de la rama de seguimiento remoto origin/masterse haya rebobinado y reconstruido, lo que lleva a un historial de esta forma:

                   o---B1
                  /
  ---o---o---B2--o---o---o---B (origin/master)
          \
           B3
            \
             Derived (topic)

donde origin/masterse utiliza para señalar compromete B3, B2, B1y ahora apunta a B, y su topicrama se inició en la parte superior de nuevo cuando origin/masterestaba en B3.

Este modo usa el reflog de origin/masterpara encontrar B3como punto de bifurcación, de modo que se topicpueda volver a basar en la parte superior de la actualizaciónorigin/master mediante:

$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic

Es por eso que el git merge-basecomando tiene una nueva opción:

--fork-point::

Encuentre el punto en el que una rama (o cualquier historial que conduzca a <commit>) se bifurcó desde otra rama (o cualquier referencia) <ref>.
Esto no solo busca el ancestro común de las dos confirmaciones, sino que también tiene en cuenta el re-registro de <ref>para ver si la historia que lleva a <commit>bifurcar desde una encarnación anterior de la rama<ref> .


El git pull --rebasecomando " " calcula el punto de bifurcación de la rama que se está rebasando utilizando las entradas de " base" reflog de la rama (normalmente una rama de seguimiento remoto) en la que se basó el trabajo de la rama, para hacer frente al caso en el que la "base" La rama ha sido rebobinada y reconstruida.

Por ejemplo, si el historial se parece a dónde:

  • la punta actual de la baserama " " está en B, pero la recuperación anterior observó que su punta solía estar B3y luego B2y luego B1 antes de llegar a la confirmación actual, y
  • la rama que se está rebasando sobre la última "base" se basa en la confirmación B3,

se trata de encontrar B3yendo a través de la salida de " git rev-list --reflog base" (es decir B, B1, B2, B3) hasta que encuentra una confirmación de que es un antepasado de la punta actual " Derived (topic)".

Internamente, tenemos get_merge_bases_many()que puede calcular esto con una sola vez.
Querríamos una base de fusión entre Derivedy un compromiso de fusión ficticio que resultaría al fusionar todos los consejos históricos de " base (origin/master)".
Cuando existe tal compromiso, deberíamos obtener un único resultado, que coincida exactamente con una de las entradas de reflog de " base".


Git 2.1 (Q3 2014) agregará hacer que esta característica sea más robusta para esto: vea el compromiso 1e0dacd de John Keeping ( johnkeeping)

Manejar correctamente el escenario donde tenemos la siguiente topología:

    C --- D --- E  <- dev
   /
  B  <- master@{1}
 /
o --- B' --- C* --- D*  <- master

dónde:

  • B'es una versión corregida Bque no es idéntica al parche B;
  • C*y D*son idénticos al parche Cy, Drespectivamente, y entran en conflicto textualmente si se aplican en el orden incorrecto;
  • Edepende textualmente de D.

El resultado correcto de git rebase master deves que Bse identifica como el tenedor-punto de devy master, de manera que C, D, Eson los commit esa necesidad para que se reproduzca en master; pero Cy Dson patch-idéntico C*y D*por lo que puede ser bajado, de modo que el resultado final es:

o --- B' --- C* --- D* --- E  <- dev

Si no se identifica el punto de bifurcación, elegir Buna rama que contenga B'da como resultado un conflicto y si las confirmaciones idénticas al parche no se identifican correctamente, elegir Cuna rama que contenga D(o de manera equivalente D*) da como resultado un conflicto.


El " --fork-point" modo de " git rebase" retrocedió cuando el comando se reescribió en C en la era 2.20, que se corrigió con Git 2.27 (Q2 2020).

Consulte la confirmación f08132f (09 de diciembre de 2019) de Junio ​​C Hamano ( gitster) .
(Combinado por Junio ​​C Hamano - gitster- en commit fb4175b , 27 de marzo de 2020)

rebase: --fork-pointcorrección de regresión

Firmado por: Alex Torok
[jc: renovó la corrección y usó las pruebas de Alex]
Firmado por: Junio ​​C Hamano

" git rebase --fork-point master" solía funcionar bien, como llamaba internamente " git merge-base --fork-point" que sabía cómo manejar el nombre de referencia corto y dwim al nombre de referencia completo antes de llamar a la get_fork_point()función subyacente .

Esto ya no es cierto después de que el comando se reescribió en C, ya que su llamada interna hecha directamente a get_fork_point()no muestra una referencia corta.

Mueva la lógica "dwim the refname a la lógica refname completa" que se usa en "git merge-base" a la get_fork_point()función subyacente , de modo que la otra persona que llama a la función en la implementación de "git rebase" se comporte de la misma manera para corregir esta regresión.

VonC
fuente
1
Tenga en cuenta que un git push --force ahora se puede hacer (git 1.8.5) con más prudencia: stackoverflow.com/a/18505634/6309
VonC