¿Cómo puedo conciliar el HEAD desprendido con el maestro/origen?

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.

Solución

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.
    (Las refs simbólicas se implementaron originalmente como enlaces simbólicos, pero más tarde se cambiaron a archivos planos con interpretación extra para que pudieran ser utilizados en plataformas que no tienen enlaces simbólicos).

Tenemos HEADrefs/heads/master17a02998078923f2d62811326d130de991d1a95a.

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 HEAD17a02998078923f2d62811326d130de991d1a95a

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).

Comentarios (22)

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.

Comentarios (2)

Obtenga su confirmación separada en su propia rama

Simplemente ejecute git checkout -b mynewbranch.

Luego ejecuta git log, y verás que el commit es ahora HEAD en esta nueva rama.

Comentarios (2)