22
Git: how to salir de esta, ramas
Con un GUI
O por consola.
$ git log --graph --format='%C(auto)%h %s%n%C(auto)%d%n' --all
Una forma de visualizar los commits de un repositorio es en un árbol, donde los commits son los nudos de las ramas.
De un commit pueden salir uno o varios commits (por ejemplo, de "todos: concilio del anillo" sólo sale el commit "todos: moria", mientras que de "todos - gandalf: amon hen" salen tres, "aragorn + legolas + gimli: siguiendo a los uruk-hai...", "pipin + merry: camino a isengard" y "frodo + sam: camino a mordor"), y los commits se pueden unir de dos en dos (por ej. "aragorn + legolas + gimli + gandalf: fangorn" es la unión de los commits "aragorn + legolas + gimli: siguiendo a los uruk-ahi" y "gandalf: cayendo con el balrog").
Cuando el commit es la unión de dos commits se llama "merge commit", ya que es el resultado de git merge
o git pull
(sí, porque git pull
hace merges por debajo), mientras que el resto de los commits suelen ser resultado de git commit
, aunque también serlo de git revert
o de git merge --squash
.
Las ramas son referencias a commits, referencias que cambian al realizar acciones como git commit
o git merge
estando en esa rama (por ej. la rama "pippin-merry" estaba en el commit "pippin + merry: camino a insengard", y se modifico automáticamente para referenciar a "pippin-merry: concilio de los ents" cuando se hizo dicho commit).
Múltiples ramas pueden hacer referencia al mismo commit (por ej. las ramas "gandalf" y "aragorn-legolar-gimli" hacen referencia al commit 872cac9
).
Este post va a lidiar solo con casos que pueden surgir al trabajar con ramas locales, es decir, aquellas ramas creadas localmente y que todavía no se han subido a un repositorio remoto.
Para manipular ramas en repositorio remoto con git pull
o sincronizar las ramas locales con las ramas del repositorio remoto git push
, hay que tener en cuenta una serie de cosas que se tratarán en el siguiente post.
¿Cómo obtengo el commit anterior?; ¿Cuál era el commit antes del merge?
HEAD
siempre indica el commit actual y HEAD~
siempre indica el commit anterior.
Refiendose a ramas, master
indica el último commit de la master
y master~
indica el commit anterior.
Y refiriendose a commits, 872cac9
indica el commit 872cac9
y 872cac9~
indica el commit anterior.
Estas referencias facilitan las operaciones más o menos cotidianas como pueden ser el deshacer el commit actual; git reset HEAD~
es mucho más rápido que git log
para obtener el hash del commit anterior y luego hacer git reset <hash>
.
Ya a modo de curiosidad, ~
es una "referencia por ancestro" que indica el commit padre. Se pueden añadir todos los ~
que se quieran para obtener referencias a commits anteriores; HEAD~
referencia al commit "padre"; HEAD~~
, o HEAD~2
referencian al commit "abuelo"; HEAD~3
referencian al "bisabuelo"; etc.
También existen ^1
, o simplemente ^
, y ^2
, para hacer referencias a los commits implicados en merge commits ; si HEAD
es un merge commit, HEAD^
es equivalente a HEAD~
, e indica el commit en el que se estaba cuando se hizo git merge
; HEAD^2
indica el último commit de la rama con la que se hizo merge.
Y se pueden combinar para hacer cosas como HEAD^2~2
.
Corregir un texto incorrecto; se copio la clave del issute tracker que no era.
En un GUI se puede hacer click derecho en el nombre de la rama y click en "Rename".
Si es por consola hay que usar git branch -m
.
# git status para ver la rana actual.
$ git status
En la rama fature
nada para hacer commit, el árbol de trabajo está
limpio
# O git status para ver todas las ramas,
# con un * delante de la rama actual.
$ git branch
dovolop
* fature
master
# Renombrar la rama actual.
$ git branch -m feature
# Renombrar otra rama.
$ git branch -m dovolop develop
$ git branch
develop
* feature
master
Estas acciones solo renombran la rama local y no afectan al nombre de la rama en el repositorio remoto; si hay que renombrar una rama en el repositorio hay que tener en cuenta más detalles que se explicará en el post de repositorios remotos.
Me equivoque al poner el nombre de la rama cuando ejecute el comando; pensaba que sólo era pruebas, pero no...; la he borrado antes de subirla al repositorio remoto; pensaba que ya había subido todo al repositorio remoto, pero faltaba un commit.
Para borrar una rama en un GUI, click derecho en el nombre de la rama y "Delete".
Algunos GUI, tras borrar una rama, dan la opción de recuperarla.
Si el mensaje es "Unmerged commits were discarted", es recomendable prestarle atención, porque puede ser uno de los casos en los que interese recuperar la rama.
Se pueden borrar ramas de dos maneras:
-
git branch --delete <rama>
ogit branch -d <rama>
: Git comprobará si<rama>
esta mergeada en la rama actual, y de no estarla, no permitirá borrar la rama, evitando así que puedan perderse commits. -
git branch --delete --force <rama>
ogit branch -D <rama>
: borra la rama, este o no mergeada en la rama actual.
Como norma general es preferible usar git branch -d
para ir sobre seguro, y git branch -D
solo para esos casos en los que se esta completamente seguro.
Ya en el tema del HOW TO, es posible recrear la rama con git branch <rama> <hash>
, donde <hash>
indicaría el último commit de la rama en el momento de borrarla.
Si no se conoce ese hash y la rama se borro hace poco, git reflog
puede ser una buena opción para buscarlo, ya que muestra un histórico de todos los commits por los que se ha pasado. En Deshacer un git reset hay más detalles sobre git reflog
.
Si la rama se borro hace mucho tiempo, git reflog
puede no ser la mejor opción, ya que el hash que se busca puede estar enterrado entre muchos otros. Documentándome para el post he visto opciones mejores como esta o esta, pero personalmente no me ha surgido el caso de tener que recuperar una rama borrada hace semanas.
¿Y si la rama se ha borrado en un repositorio remoto? Tras recrear la rama en local habría que subirla al repositorio.
Haciendo un ejemplo práctico:
$ git branch -vv
feature/create-home 6748ee2 crear hoja de estilos home.css
feature/setup-project 9e75aa0 añadido .gitignore
* master 9e75aa0 añadido .gitignore
# Como la rama feature/setup-project está mergeada en master
# se borra sin problemas.
$ git branch -d feature/setup-project
Eliminada la rama feature/setup-project (era 9e75aa0)..
# Como la rama feature/create-home no está mergeada en master
# borrarla con -d da error.
$ git branch -d feature/create-home
error: La rama 'feature/create-home' no ha sido fusionada completamente.
Si estás seguro de querer borrarla, ejecuta 'git branch -D feature/create-home'.
# Si se fuerza el borrado si que deja.
$ git branch -D feature/create-home
Eliminada la rama feature/create-home (era 6748ee2)..
# Pero me doy cuenta de mi error :(
# Así que busco el commit.
$ git reflog
...
9e75aa0 (HEAD -> master) HEAD@{6}: checkout: moving from feature/create-home to master
# Y recreo la rama.
$ git branch feature/create-home 6748ee2
$ git branch -vv
feature/create-home 6748ee2 crear hoja de estilos home.css
* master 9e75aa0 añadido .gitignore
"git merge" cada vez hace una cosa distinta; después de hacer "git merge" la aplicación ya no funciona.
git merge
puede hacer cosas distintas dependiendo de las diferencias que haya entre la rama actual y la rama que se quiere mergear.
El siguiente diagrama de flujo resume la lógica que hay por debajo.
Es "fast forward" cuando la rama que se quiere mergear, target
, ha partido del último commit de la rama actual; en este caso git merge
simplemente modificará la referencia de la rama actual para que apunte al mismo commit que la rama target
.
Por poner un ejemplo, supongamos que se quiere mergear la rama feature/create-home
en la rama master
; dado que feature/create-home
parte de master
git merge
por defecto hará fast forward.
$ git status
En la rama master
$ git merge feature/create-home
Actualizando 85b13b4..82975ee
Fast-forward
home.css | 3 +++
home.html | 2 ++
2 files changed, 5 insertions(+)
create mode 100644 home.css
create mode 100644 home.html
Si no es fast forward , y no hay conflictos, git merge
creará un merge commit.
$ git status
En la rama master
$ git merge feature/create-home
# Se abre un editor de texto para poder personalizar el
# mensaje del commit.
# Tras guardar el mensaje:
Merge made by the 'recursive' strategy.
home.css | 3 +++
home.html | 2 ++
2 files changed, 5 insertions(+)
create mode 100644 home.css
create mode 100644 home.html
Si no es fast forward , y hay conflictos... bien, es un momento que a nadie le gusta.
Los conflictos surgen cuando en las dos ramas se han tocado los mismos trozos de código, ya sea editándolos en ambas ramas, modificando el archivo en una y renombrándolo en la otra, etc.
Cuando surjan, Git modificará los archivos con conflictos para indicar dónde están y esperará a que se resuelvan, pero no hará nada más. Será el momento de analizar que ha pasado y resolver los conflictos.
Y siempre se puede ejecutar git reset --hard
para hacer tabla rasa y empezar de cero, porque hay veces en que los conflictos se resuelven y los tests dejan de pasar, o simplemente porque los conflictos son complicados y se prefiere empezar fresco después de una pausa.
Los conflictos se verán así en el archivo
<<<<<<< HEAD
<a href="contact.html">Contacto</a>
=======
<h1> Home </h1>
<p> bienvenido </p>
>>>>>>> feature/create-home
Donde entre <<<<<<<<
y =======
esta lo que hay en la rama actual, y entre ========
y >>>>>>
lo que hay en la rama a mergear.
Para estos casos lo más cómodo es recurrir a un GUI:
Una vez resuelto
Ya se podrá hacer commit:.
$ git merge feature/create-home
CONFLICTO (add/add): Conflicto de merge en home.html
Auto-fusionando home.html
Fusión automática falló; arregle los conflictos y luego realice
un commit con el resultado.
# Resolver conflictos.
$ git commit -a
[master 1814deb] Merge branch 'feature/create-home'
Cuando haya un merge commit de por medio siempre hay que tener cuidado, ya que si bien puede no haber conflictos si que se pueden haber introducir errores.
Si por ejemplo en la rama actual se renombrase la función doThings
a createUser
, junto con todos sus usos, y se mergease una rama que ha introducido un nuevo uso de doThings
en un nuevo archivo, git merge
puede no encontrar conflictos y mergear automáticamente, pero cuando se invoque doThings
en el nuevo archivo se producirá un error, ya que doThings
ha dejado de existir.
Para evitar estos problemas se pueden usar varias estrategias:
- Ejecutar los tests después de cada merge, y si hay algún problema hacer commits con los fixes.
- Crear un hook que impida completar el merge si fallan los tests.
- Usar algunas de las opciones de
git merge
para controlar como se hace el merge:-
--ff-only
para indicar que solo se quiere hacer merge si es fast forward, fallando en caso contrario ( por ej.git merge --ff-only feature/create-home
). Puede ser útil para actualizar ramas importantes comomaster
con total tranquilidad. -
--no-commit
fuerza a que si el merge implica un merge commit, Git se detenga justo antes de hacer el commit, como haría si hubiese conflictos; en ese momento se podrán revisar los cambios, ejecutar los tests, arreglar alguna cosilla su fuese necesario, y finalmente, hacer el commit. Si el merge es fast forward este se hará automáticamente usando la estrategia de fast forward, ignorando el flag--no-commit
.
-
He hecho un commit en la rama que no era; se me olvido crear la rama antes de hacer los commits.
Lo primero sería llevarse los commits a la rama adecuada.
Si la rama en la que se deberían haber hecho los commits no existe, es tan sencilla como crearla en el punto actual:
- Si se usa un GUI, click derecho en la rama actual y crear una nueva rama.
- Si se usa consola
git branch <rama>
.
Si la rama ya estaba creada, basta con ir al ella y hacer git cherry-pick
de los commits que se quieren mover.
Y los segundo y último es restaurar la rama en la que se han hecho los commits por error al punto correcto .
Al final muchas situaciones en Git se resuelven combinando varias acciones.
Saque la rama del hotfix de mi rama de feature en lugar de sacarla de master; saque mi rama de feature de master en lugar de la rama de integración.
Un ejemplo de esta situación sería sacar la rama hotfix
de la rama de integración develop
, en lugar de haberlo hecho de la rama principal master
.
Si no se ha hecho ningún commit bastaría con modificar la rama hotfix
para que parta de master
:
- Si se usa un GUI, click derecho en
master
y click en "Reset current branch to here" o similar. - Si se usa consola
git checkout -B hotfix master
.
Si ya se ha hecho algún commit, la opción que menos comandos requiere es git rebase
, aunque hay que visualizar muy bien lo que se quiere hacer antes de usarlo.
Otra opción algo más laboriosa sería:
- Apuntarse el hash de los commits que han hecho en la rama
hotfix
. - Resetear la rama
hotfix
para que apunte al punto correcto con "Reset current branch to here" si se una GUI ogit checkout -B hotfix master
si se usa consola. - Recuperar los commits con apuntados en el paso 1 con
git cherry-pick
La forma por defecto de git rebase
es git rebase <rama-destino> <rama-a-mover>
, o, si ya se está en <rama-a-mover>
, simplemente git rebase <rama-destino>
; haciendo un símil, sería como "trasplantar la rama".
En el ejemplo, git rebase master hotfix
movería los commits de la rama hotfix
para que salgan del commit G
en lugar del commit E
.
(Como nota aclaratoria, añado un apostrofe para indicar que el commit con hash A'
es una copia A
pero con distinto padre; decir "mover" no es del todo correcto, pero es una simplificación bastante útil).
Por defecto git rebase
mueve todos los commits de <rama-a-mover>
que no tiene <rama-destino>
, incluso aunque alguno de esos commits estén compartidos con otra rama.
En el ejemplo, git rebase master hotfix
movería los commits E y F
, que solo tiene hotfix
, y copiaría los commits C, D y F
, que hotfix
comparte con la rama develop
y que no están en en master
.
Si el algún momento se llegasen a mergear las ramas hotfix
y develop
en master
no habría conflictos porque, aunque los commits C', D' y F'
estén duplicados en la rama develop
, Git detectaría que son lo mismo.
Se puede indicar de una manera más precisa los commits que hay que mover con el comando
git rebase --onto <rama-destino> <desde-la-rama> <rama-a-mover>
En el ejemplo git rebase --onto master develop hotfix
movería solo los commits E y F
, que están en la rama hotfix
y salen de la rama develop
.
Como se decía al principio, git rebase
es lo que menos comandos requiere, pero en contra tiene que lleva su tiempo visualizarlo.
Otra nota sobre git rebase
es que puede implicar resolver conflictos varias veces si las ramas de origen y destino son muy distintas; si eso ocurre, siempre se puede dar marchar atrás con git rebase --abort
y replantarse la estrategia.
Un merge que produce un error; un merge hecho donde en una rama que no era; un merge hecho con la rama que no era.
Si se acaba de hacer el merge y no se ha subido a un repositorio remoto lo más sencillo es revertir la rama al commit en el que estaba antes del merge.
Si se han hecho commits después del merge o ya se han subido al repositorio remoto, se pueden deshacer con git revert
.
Si el merge fue fast forward, habría que hacer un git revert
de cada uno de los commits que se han añadido con el merge, empezando por el más reciente.
Más sobre como revertir commits simples en Deshacer un commit: como revertir un cambio.
Si el merge implico un merge commit se puede usar git reset -m1 <hash>
, donde -m1
indica que se desean deshacer los cambios que vinieron de <rama>
cuando se hizo git merge <rama>
.
También se puede usar, aunque es menos común, git reset -m2 <hash>
para indicar que se desean deshacer las diferencias que había en la rama en la que se hizo git merge <rama>
con respecto de <rama>
, para que tras el git revert
se tenga lo mismo que en <rama>
.
Estos comando generarán un commit con los cambios para deshacer el merge commit.
Con un ejemplo práctico.
# Acabo de hacer un merge y lo quiero deshacer.
# Hago git log -1 para ver la información del último commit.
# 'Merge: 6512051 6748ee2' me dice que
# estaba en el commit 6512051 cuando hice el merge
# de otra rama que a su vez estaba en el commit 6748ee2.
$ git log -1
commit 7a12efcf980753856b67b72b040a73a93cdb3c9b (HEAD -> master)
Merge: 6512051 6748ee2
Merge branch 'feature/create-home'
# Compruebo cuales son las diferencias entre el commit
# 6512051 y el commit en el que estoy.
$ git diff 6512051 HEAD
diff --git a/home.css b/home.css
new file mode 100644
index 0000000..6748ee2
--- /dev/null
+++ b/home.css
...
diff --git a/home.html b/home.html
new file mode 100644
index 0000000..6141d5d
--- /dev/null
+++ b/home.html
...
# Hago el revert del merge indicando con -m1 que quiero
# tomar como referencia lo que había antes del merge.
$ git revert -m1 7a12efcf980753856b67b72b040a73a93cdb3c9b
[master f65033c] Revert "Merge branch 'feature/create-home'"
1 file changed, 2 deletions(-)
# log -1 me indica que hay nuevo commit.
$ git log -1 f65033c
commit f65033c1cb53ab664482d9d7ee3f836a493e249f
Revert "Merge branch 'feature/create-home'"
This reverts commit 7a12efcf980753856b67b72b040a73a93cdb3c9b, reversing
changes made to 651205183ba4da49663163b881901c74e8c98606.
# Compruebo los cambios y no hay diferencias.
$ git diff 6512051 HEAD
¿Qué ocurriría si se mergease de nuevo la rama cuyo merge se ha revertido?
Si no se ha hecho ningún commit adicional en <rama>
, git merge <rama>
dirá que "la rama ya está actualizada" y no hará nada, aunque quizá lo natural sería pensar que volvería a aplicar los cambios de <rama>
.
Si se ha hecho algún commit adicional en <rama>
, git merge <rama>
solo aplicará los cambios de los nuevos commits; la "buena" noticia es que en git merge
notificará unos conflictos bastante extraños que harán ver que algo raro pasa.
Todo eso resulta un poco contraintuitivo, pero git revert
sólo modifica archivos, no modifica el histórico de commits, así que cuando se le pide a Git que mergee commits que ya tiene en su histórico, se limita a ignorarlos porque ya los tiene.
¿Por qué alguien querría mergear de nuevo algo que acaba de revertir?
Si un merge commit causa problemas en producción, una forma rápida de salir del paso es revertir ese merge commit, arreglar el problema en la rama y volver a mergearla.
Pero como se sigue de la pregunta anterior no es algo directo.
En https://github.com/git/git/blob/master/Documentation/howto/revert-a-faulty-merge.txt se explican muy bien dos posibles maneras para hacerlo.
- Resolver los problemas en la rama cuyo merge commit se ha revertido, revertir el commit que revirtió el merge commit y por último mergear la rama con el fix.
- Replicar en una nueva rama todos los commits de la rama cuyo merge commit se ha revertido (los commits tendrían los mismos cambios, pero como tendrían un hash distinto para Git serían distintos), resolver los problemas en la nueva rama y mergearla.
He terminado mi flamante primera feature, los tests están en verde, ha pasado las revisiones de código, y ahora ¿como la integro?
Supongamos que se quiere integrar la rama feature/create-home
en la rama master
.
Algunas de las estrategias más habituales son:
- Merge commit
- Merge squash
- Rebase
- Trunk base development
Crear un merge commit cada vez que se mergee una rama en master: git merge --no-ff <rama>
.
--no-ff
sirve para forzar que se cree un merge commit, aunque se pudiese hacer fast forward.
# Me aseguro de que estoy en la rama master,
# que es donde quiero hacer el merge.
$ git merge status
En la rama master
nada para hacer commit, el árbol de trabajo está limpio
$ git merge --no-ff feature/create-home
Merge made by the 'recursive' strategy.
home.css | 3 +++
home.html | 2 ++
2 files changed, 5 insertions(+)
create mode 100644 home.css
create mode 100644 home.html
Condensar todos los commits de la rama a mergear en un único commit: git merge --squash <rama>
.
Después del merge será necesario un hacer git commit
, cuyo mensaje por defecto será un sumario de todos los commits de <rama>
, aunque se podrá personalizar.
# Me aseguro de que estoy en la rama master,
# que es donde quiero hacer el merge.
$ git merge status
En la rama master
nada para hacer commit, el árbol de trabajo está limpio
$ git merge --squash feature/create-home
Actualizando 85b13b4..82975ee
Fast-forward
Commit de squash -- no actualizando HEAD
home.css | 3 +++
home.html | 2 ++
2 files changed, 5 insertions(+)
create mode 100644 home.css
create mode 100644 home.html
$ git commit
[master 1a5ee55] Squashed commit of the following:
2 files changed, 5 insertions(+)
create mode 100644 home.css
create mode 100644 home.html
$ git log -1
commit 1a5ee55ca09d28b8469385fa6675211ddc2d23ca (HEAD -> master)
Squashed commit of the following:
commit 82975ee0f7f4fcab93e2b0d0d6e9d2f915510881
crear hoja de estilos home.css
commit b9632f136aef8ae41306fc4468aa979800fb330a
crear home.html
A home.css
A home.html
Mover todos los commits de la rama a mergear en la rama actual, o, haciendo un símil, "replantar la rama": git rebase <rama-destino> <rama-a-mover>
.
Si actualmente se está en <rama-a-mover>
, se puede hacer directamente git rebase <rama-destino>
.
Después del merge será necesario un hacer git merge
para añadir los commits en la rama de integración.
# "Replanto" feature/create-home en master.
$ git rebase master feature/create-home
En primer lugar, rebobinando HEAD para después reproducir
tus cambios encima de ésta...
Aplicando: crear home.html
Aplicando: crear hoja de estilos home.css
# Hago checkout en master.
$ git checkout master
Cambiado a rama 'master'
# Hago un merge fast forward de la rama a integrar.
$ git merge --ff-only feature/create-home
Actualizando e157378..d63cc7b
Fast-forward
log.txt | 2 ++
1 file changed, 2 insertions(+)
Algo a tener en cuenta con git rebase
es que puede llegar a ser necesarios resolver conflictos si <rama-a-mover>
parte de un commit antiguo de <rama-destino>
.
Si <rama-a-mover>
tiene varios commits puede llegar a ser necesario resolver conflictos una vez por cada commit.
Hay proyectos en los que por defecto utilizan git rebase
, y excepcionalmente, cuando se encuentran un caso donde la resolución de conflictos se complica, usan git merge
para así solo resolver conflictos una única vez.
Cuando surjan conflictos en un git rebase
seguir las instrucciones que da Git como salida del comando.
Sólo hay una rama, master
, y todos los commits se hacen sobre ella.
Adelantándome al post de repositorios remotos, cuando se quiera subir esa rama al repositorio remoto y surjan conflictos porque la rama local master
ha divergido respecto de la rama master
del repositorio remoto, habrá que decidir si optar por merge commits o por rebase.
¿Por que se llama Trunk base development? Trunk es el nombre de la rama principal en los primeros sistemas de control de versiones.
Cada opción tiene sus pros y sus contras:
-
git merge
preserva todo el histórico de commits tal y surgió, pero si no se sigue un buen flujo de ramas puede resultar en un árbol de commits muy complejo, donde sería difícil determinar cuando se introdujo un bloque de código o un bug. -
git merge --squash
deja un árbol de commits muy sencillo, con una única rama y un único commit por cada feature, pero puede dejar commits muy grandes y se pierde información de los commits individuales. -
git rebase
produce una única rama con todos los commits que se han hecho, pero se pierde la información de en que punto se inicio una rama y los merges con conflictos pueden ser muy dolorosos. -
Trunk base development reduce a 0 la gestión de ramas, pero cuando las ramas divergen hay que optar por
git merge
ogit rebase
.
¿Qué usar? La decisión dependerá de cuanta gente trabaje en el proyecto, de las convenciones del equipo, del tipo de proyecto, de como de grandes sean las features, ...
Esto intenta ser un post agnóstico, así que
"git status" muestra un texto extraño en lugar del nombre de la rama; me he cambiado de rama y ya no encuentro el último commit.
Lo normal es hacer git checkout
a ramas, pero también se puede hacer a tags y a commits. En estos últimos casos se pasa al estado "detached HEAD", o "HEAD desacoplada" si se tiene Git configurado en castellano .
$ git log --graph --format='%C(auto)%h %s%n%C(auto)%d'
* d63cc7b crear hoja de estilos home.css
| (HEAD -> master, feature/create-home)
* 1676ceb crear home.html
|
* e157378 añadido .gitignore
|
* 85b13b4 set up proyecto
|
* 417ceac commit inicial
# git status indica que estoy en la rama master.
$ git status
En la rama master
nada para hacer commit, el árbol de trabajo está limpio
# Al hacer checkout a un commit se muestra un mensaje
# que da respeto.
$ git checkout e157378
Nota: cambiando a 'e157378'.
Te encuentras en estado 'detached HEAD'. Puedes revisar por aquí, hacer
cambios experimentales y hacer commits, y puedes descartar cualquier
commit que hayas hecho en este estado sin impactar a tu rama realizando
otro checkout.
Si quieres crear una nueva rama para mantener los commits que has creado,
puedes hacerlo (ahora o después) usando -c con el comando checkout. Ejemplo:
git switch -c <nombre-de-nueva-rama>
O deshacer la operación con:
git switch -
Desactiva este aviso poniendo la variable de config advice.detachedHead en false
HEAD está ahora en e157378 añadido .gitignore
# git status en lugar de mostrar un nombre de rama
# muestra un mensaje bastante raro....
$ git status
HEAD desacoplada en e157378
nada para hacer commit, el árbol de trabajo está limpio
# git branch, para ver las ramas, también muestra
# el mensaje raro.
$ git branch
* (HEAD desacoplada en e157378)
master
Los checkouts a tag y a commits pueden ser una suerte de máquina del tiempo con la que, sin necesidad de crear o modificar ramas, poder consultar como estaba el código hace unas semanas, o confirmar que los tests funcionaban antes de integrar una feature.
Para volver a ver nombres de ramas normales basta con hacer git checkout
a una rama.
Los problemas pueden venir cuando estando en detached HEAD se hace un commit y después git checkout
a otra rama, ya que ese commit se puede llegar a perder a menos que se conozca su clave hash o se use git reflog
(ver Deshacer un commit: como revertir un cambio para más información sobre git reflog
).
Si el commit es de prueba no habrá problema, ya que desaparecerá sin dejar rastro, pero si es algo con lo que se quiere seguir trabajando siempre es posible crear una rama a partir de él, o recuperarlo con git cherry pick
.
$ git checkout e157378
Nota: cambiando a 'e157378'.
Te encuentras en estado 'detached HEAD'. ...
# Hago algunos cambios en el .gitignore.
$ git commit -am "ignorar más cosas"
[HEAD desacoplado aec5722] ignorar más cosas
1 file changed, 1 insertion(+)
# Cambio de rama por alguna razón y Git da la señal de
# alarma, indicando ademas como se puede recuperar el commit.
$ git checkout master
Peligro: estás saliendo 1 commit atrás, no está conectado
a ninguna rama:
aec5722 ignorar más cosas
Si quieres conservarlo creando una nueva rama, este es un buen momento
para hacerlo:
git branch <nuevo-nombre-de-rama> aec5722
Cambiado a rama 'master'
# Si por alguna razón no hubiese conservado el hash del
# commit, siempre es posible consultarlo en con git reflog.
$ git reflog
d63cc7b (HEAD -> master, feature/create-home) HEAD@{0}: checkout: moving from aec57222a12d12ca6b87f5329ce67c10b5289d0a to master
aec5722 HEAD@{1}: commit: ignorar más cosas
e157378 HEAD@{2}: checkout: moving from master to e157378
d63cc7b (HEAD -> master, feature/create-home) HEAD@{3}: checkout: moving from e15737895b61e4c7e205a8729abad775b6679f5d to master
e157378 HEAD@{4}: checkout: moving from master to e157378
claudio@jupiter2:~/sandbox/git(master)$ git checkout e157378
# Si quiero crear una rama para poder seguir trabajado a
# partir de él,hago como ha dicho Git
$ git branch nuevo-nombre-de-rama aec5722
$ git checkout feature/configure-project
Cambiado a rama 'feature/configure-project'
$ git status
En la rama feature/configure-project
nada para hacer commit, el árbol de trabajo está limpio
Si has llegado hasta aquí, gracias 🙂
Espero que te haya resultado útil, y nos vemos en el próximo post sobre repositorios remotos 👋
Cover: https://pxhere.com/en/photo/149424
Generaciónd de diagramas: https://excalidraw.com/
22