DEV Community

loading...
Cover image for Git: how to salir de esta, ramas

Git: how to salir de esta, ramas

Claudio Sánchez
Desarrollador software en mi jornada laboral, y en algunos ratos libres escribo alguna cosilla.
・21 min read

How to

Introducción

Con un GUI

Ramas vistas en un GUI

O por consola.

$ git log --graph --format='%C(auto)%h %s%n%C(auto)%d%n' --all 
Enter fullscreen mode Exit fullscreen mode

Ramas vistas en consola

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.

Orientarse dentro de una rama

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

Renombrar una rama

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
Enter fullscreen mode Exit fullscreen mode

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.

Recuperar una rama borrada por error

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.

GUI restaura rama

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> o git 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> o git 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
Enter fullscreen mode Exit fullscreen mode

Git merge, ese comando problemático

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

diagrama flujo de git merge

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 merge fast forward antes

$ 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
Enter fullscreen mode Exit fullscreen mode

git merge fast forward despues

Si no es fast forward , y no hay conflictos, git merge creará un merge commit.

git merge merge commit antes

$ 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
Enter fullscreen mode Exit fullscreen mode

git merge merge commit después

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
Enter fullscreen mode Exit fullscreen mode

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:

git merge resolver conflictos antes

Una vez resuelto

git merge resolver conflictos despues

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'
Enter fullscreen mode Exit fullscreen mode

git merge merge commit tras conflictos

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 como master 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.

Mover commits

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.

Mover ramas

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:

  1. Apuntarse el hash de los commits que han hecho en la rama hotfix.
  2. Resetear la rama hotfix para que apunte al punto correcto con "Reset current branch to here" si se una GUI o git checkout -B hotfix master si se usa consola.
  3. 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".

git rebase sencillo

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.

git rebase complejo

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>

git rebase onto

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.

Deshacer un merge

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
Enter fullscreen mode Exit fullscreen mode

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

  1. 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.
  2. 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.

https://media.giphy.com/media/OK27wINdQS5YQ/giphy.gif

¿Cómo integrar una rama?

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.

escenario integrar rama

Algunas de las estrategias más habituales son:

  • Merge commit
  • Merge squash
  • Rebase
  • Trunk base development

Merge commit

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
Enter fullscreen mode Exit fullscreen mode

resultado integrar rama con merge commit

Merge squash

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
Enter fullscreen mode Exit fullscreen mode

resultado integrar rama con merge squash

Rebase

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.

escenario integrar con rebase

# "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(+)
Enter fullscreen mode Exit fullscreen mode

escenario integrar con rebase

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.

Trunk base development

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.

Conclusión

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 o git 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

bomba de humo

HEAD detached o desacoplado

"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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode



Si has llegado hasta aquí, gracias 🙂
Espero que te haya resultado útil, y nos vemos en el próximo post sobre repositorios remotos 👋

Créditos

Cover: https://pxhere.com/en/photo/149424
Generaciónd de diagramas: https://excalidraw.com/

Discussion (0)