Soy nuevo en las complejidades de ramificación de Git. Siempre trabajo en una sola rama y confirmar los cambios y luego empujar periódicamente a mi origen remoto.
Hace poco, hice un reset de algunos archivos para sacarlos del commit staging, y más tarde hice un rebase -i
para deshacerme de un par de commits locales recientes. Ahora estoy en un estado que no entiendo muy bien.
En mi área de trabajo, git log
muestra exactamente lo que yo'esperaría-- estoy en el tren correcto con los commits que no quería que se fueran, y los nuevos allí, etc.
Pero acabo de empujar al repositorio remoto, y lo que hay es diferente - un par de commits que he matado en el rebase han sido empujados, y los nuevos confirmados localmente no están allí.
Creo que "master/origin" está separado de HEAD, pero no estoy 100% claro en lo que significa, cómo visualizarlo con las herramientas de línea de comandos, y cómo solucionarlo.
En primer lugar, aclaremos qué es la HEAD y qué significa cuando se desprende.
HEAD es el nombre simbólico de la confirmación actual. Cuando HEAD no está separado (la situación "normal"1: se tiene una rama extraída), HEAD en realidad apunta a la "ref" de una rama y la rama apunta al commit. Por lo tanto, HEAD está "unido" a una rama. Cuando se hace un nuevo commit, la rama a la que apunta HEAD se actualiza para apuntar al nuevo commit. HEAD lo sigue automáticamente ya que sólo apunta a la rama.
git symbolic-ref HEAD
da como resultado refs/heads/master
La rama llamada "master" se comprueba.git rev-parse refs/heads/master
produce 17a02998078923f2d62811326d130de991d1a95a
Ese commit es la punta actual o "head" de la rama master.git rev-parse HEAD
también arroja 17a02998078923f2d62811326d130de991d1a95a
Esto es lo que significa ser una "referencia simbólica". Apunta a un objeto a través de alguna otra referencia.Tenemos HEAD
→ refs/heads/master
→ 17a02998078923f2d62811326d130de991d1a95a
.
Cuando el HEAD se separa, apunta directamente a un commit, en lugar de apuntar indirectamente a uno a través de una rama. Puedes pensar en un HEAD separado como si estuviera en una rama sin nombre.
git symbolic-ref HEAD
falla con fatal: ref HEAD is not a symbolic ref
.git rev-parse HEAD
da como resultado 17a02998078923f2d62811326d130de991d1a95a
.
Como no es una referencia simbólica, debe apuntar directamente al propio commit.Tenemos HEAD
→ 17a02998078923f2d62811326d130de991d1a95a
Lo importante a recordar con un HEAD separado es que si la confirmación a la que apunta no está referenciada (ninguna otra referencia puede alcanzarla), entonces se convertirá en "colgante" cuando se obtenga alguna otra confirmación. Eventualmente, estos commits colgantes serán eliminados a través del proceso de recolección de basura (por defecto, se mantienen durante al menos 2 semanas y pueden mantenerse más tiempo al ser referenciados por el reflog de HEAD).
1 Está perfectamente bien hacer el trabajo "normal" con un HEAD desprendido, sólo tienes que mantenerte al tanto de lo que estás haciendo para evitar tener que pescar la historia caída del reflog.
Los pasos intermedios de un rebase interactivo se hacen con un HEAD separado (en parte para evitar contaminar el reflog de la rama activa). Si terminas la operación de rebase completa, se actualizará tu rama original con el resultado acumulado de la operación de rebase y se volverá a adjuntar el HEAD a la rama original. Mi suposición es que nunca has completado el proceso de rebase; esto te dejará con un HEAD separado que apunta a la confirmación que fue procesada más recientemente por la operación de rebase.
Para recuperarse de su situación, debe crear una rama que apunte a la confirmación a la que apunta actualmente su HEAD separado:
git branch temp
git checkout temp
(estos dos comandos pueden ser abreviados como git checkout -b temp
)
Esto reasignará tu HEAD a la nueva rama temp
.
A continuación, debes comparar el commit actual (y su historial) con la rama normal en la que esperabas estar trabajando:
git log --graph --decorate --pretty=oneline --abbrev-commit master origin/master temp
git diff master temp
git diff origin/master temp
(Probablemente querrá experimentar con las opciones de registro: añadir -p
, dejar fuera --pretty=...
para ver el mensaje de registro completo, etc.)
Si su nueva rama temp
se ve bien, es posible que desee actualizar (por ejemplo) master
para que apunte a ella:
git branch -f master temp
git checkout master
(estos dos comandos pueden ser abreviados como git checkout -B master temp
)
A continuación, puedes eliminar la rama temporal:
git branch -d temp
Por último, probablemente querrá empujar el historial restablecido:
git push origin master
Es posible que tenga que añadir --force
al final de este comando para empujar si la rama remota no puede ser "adelantada" a la nueva confirmación (es decir, que se cayó, o se reescribió alguna confirmación existente, o se reescribió algún pedazo de la historia).
Si estabas en medio de una operación de rebase, probablemente deberías limpiarla. Puedes comprobar si un rebase estaba en proceso buscando el directorio .git/rebase-merge/
. Puedes limpiar manualmente el rebase en curso simplemente borrando ese directorio (por ejemplo, si ya no recuerdas el propósito y el contexto de la operación de rebase activa). Normalmente se usaría git rebase --abort
, pero eso hace algún restablecimiento extra que probablemente quieras evitar (mueve a HEAD de vuelta a la rama original y lo restablece al commit original, lo que deshará parte del trabajo que hicimos arriba).
Mira aquí la explicación básica de la cabeza desprendida:
http://git-scm.com/docs/git-checkout
Línea de comandos para visualizarlo:
git branch
o
git branch -a
obtendrá un resultado como el siguiente:
* (no branch)
master
branch1
El * (sin rama)
muestra que está en la cabeza separada.
Podrías haber llegado a este estado haciendo un git checkout somecommit
etc. y te habría avisado con lo siguiente:
Estás en el estado 'detached HEAD'. Usted puedes mirar, hacer cambios experimentales cambios experimentales y confirmarlos, y puedes descartar cualquier confirmación que hagas en este estado sin afectar a ninguna rama realizando otro checkout.
Si quieres crear una nueva rama para retener los commits que cree, puede hacerlo hacerlo (ahora o más tarde) utilizando -b con el comando checkout de nuevo. Ejemplo:
git checkout -b nuevo_nombre_de_rama
Ahora, para llevarlos a master:
Haz un git reflog
o incluso sólo git log
y anota tus confirmaciones. Ahora git checkout master
y git merge
los commits.
git merge HEAD@{1}
Edita:
Para añadir, usa git rebase -i
no sólo para borrar / matar commits que no necesitas, sino también para editarlos. Simplemente menciona "edit" en la lista de commits y podrás modificar tu commit y luego emitir un git rebase --continue
para seguir adelante. Esto habría asegurado que nunca llegaras a un HEAD desprendido.