restaurar - revertir cambios en un archivo git
¿Cómo recupero/vuelvo a sincronizar después de que alguien empuja una rebase o un reinicio a una rama publicada? (3)
Todos hemos escuchado que uno nunca debería rebasar el trabajo publicado, que es peligroso, etc. Sin embargo, no he visto ninguna receta publicada sobre cómo lidiar con la situación en caso de que se publique una rebase.
Ahora, tenga en cuenta que esto solo es realmente factible si el repositorio solo es clonado por un grupo de personas conocido (y preferiblemente pequeño), de modo que quien empuje la rebase o resetee puede notificar a todos los demás que deberán prestar atención la próxima vez ha podido recuperar(!).
Una solución obvia que he visto funcionará si no tienes confirmaciones locales en foo
y se vuelve a configurar:
git fetch
git checkout foo
git reset --hard origin/foo
Esto simplemente descartará el estado local de foo
a favor de su historial según el repositorio remoto.
Pero, ¿cómo se puede lidiar con la situación si uno ha cometido cambios locales sustanciales en esa rama?
Comenzando con git 1.9 / 2.0 Q1 2014, no tendrá que marcar su origen de bifurcación anterior antes de en la bifurcación ascendente reescrita, como se describe en la answer :
Consulte la confirmación 07d406b y confirme d96855f :
Después de trabajar en la rama
topic
creada congit checkout -b topic origin/master
, el historial delgit checkout -b topic origin/master
la raíz del seguimiento remoto puede haberse 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/master
solía apuntar a commitsB3
,B2
,B1
y ahora apunta aB
, y su rama detopic
se inició en la parte superior de la misma cuandoorigin/master
estaba enB3
.Este modo usa el reflog de
origin/master
para encontrarB3
como el punto de horquilla, de modo que eltopic
puede volver a establecer en la parte superior delorigin/master
actualizado de la siguiente manera:
$ fork_point=$(git merge-base --fork-point origin/master topic)
$ git rebase --onto origin/master $fork_point topic
Es por eso que el comando git merge-base
tiene una nueva opción:
--fork-point::
Encuentre el punto en el que una bifurcación (o cualquier historia que conduzca a
<commit>
) bifurcada desde otra bifurcación (o cualquier referencia)<ref>
.
Esto no solo busca el ancestro común de las dos confirmaciones, sino que también tiene en cuenta el reflog de<ref>
para ver si el historial que lleva a<commit>
bifurcó desde una versión anterior de la rama<ref>
.
El comando "
git pull --rebase
" calcula el punto de bifurcación de la bifurcación que se vuelve a calcular utilizando las entradas de reflog de la bifurcación "base
" (generalmente una bifurcación de seguimiento remoto) en la que se basó el trabajo de la bifurcación para hacer frente al caso en el cual la rama "base" ha sido rebobinada y reconstruida.
Por ejemplo, si el historial se viera como:
- la punta actual de la rama "
base
" está enB
, pero la búsqueda anterior observó que su punta solía serB3
y luegoB2
y luegoB1
antes de llegar a la confirmación actual, y- la rama que se vuelve a establecer en la parte superior de la última "base" se basa en la confirmación
B3
,intenta encontrar
B3
pasando por la salida de "git rev-list --reflog base
" (es decir,B
,B1
,B2
,B3
) hasta que encuentra un compromiso que es un antepasado del consejo actual "Derived (topic)
" .Internamente, tenemos
get_merge_bases_many()
que puede calcular esto con one-go.
Quisiéramos una base de fusión entreDerived
y una confirmación de fusión ficticia que resultaría fusionando todos los consejos históricos de "base (origin/master)
".
Cuando existe una confirmación de este tipo, 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 a esto: vea commit 1e0dacd por 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 fija deB
que no es idéntica al parche conB
; -
C*
yD*
son parche idénticos aC
yD
respectivamente y entran en conflicto textualmente si se aplican en el orden incorrecto; -
E
depende textualmente deD
El resultado correcto de git rebase master dev
es que B
se identifica como el punto tenedor de dev
y master
, de modo que C
, D
, E
son las confirmaciones que deben repetirse en el master
; pero C
y D
son parche idénticos con C*
y D*
y, por lo tanto, pueden soltarse, de modo que el resultado final es:
o --- B'' --- C* --- D* --- E <- dev
Si el punto de horquilla no está identificado, entonces al seleccionar B
en una rama que contiene B''
produce un conflicto y si las confirmaciones idénticas a las de parche no se identifican correctamente, la elección de C
en una rama que contiene D
(o D*
equivalente) da como resultado conflicto.
Diría que la recuperación de la sección de rebase de la página de manual de git-rebase cubre casi todo esto.
Realmente no es diferente de la recuperación de tu propia rebase: mueves una rama y vuelves a establecer todas las ramas que la tenían en su historial en su nueva posición.
Volver a sincronizar después de una rebase impuesta no es 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 estaba originalmente la rama remota, luego lo usa para reproducir sus confirmaciones locales a partir de ese momento en la rama remota rebasada.
Rebasar es como violencia: si no resuelve tu problema, solo necesitas más. ☺
Puede hacer esto sin el marcador, por supuesto, si busca el ID de origin/foo
antes de la rebase y lo usa.
Esta es también la forma en que lidias con la situación en la que olvidaste crear un marcador antes de ir a buscar. No se pierde nada; solo tiene que verificar el reflog para la sucursal 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 al que apunta el origin/foo
antes de la recuperación más reciente que cambió su historial.
Entonces puedes simplemente
git rebase --onto origin/foo $commit foo