Cómo estoy utilizando git (II)
Hoy voy con la segunda parte, que no entró en el artículo de ayer. El
tema iba con las ramas y los repositorios. Hasta ahora todo ha ido
bien, entre otras cosas porque sólo se han hecho las modificaciones
desde un repositorio y así nunca hay conflictos, pero ¿qué ocurre si
hay un conflicto al hacer un commit
?
Conflictos en el repositorio
Para simularlo he modificado en otro ordenador, con otra copia del
repositorio el archivo2.txt
y para generar el conflicto modificaré
el mismo fichero en la copia local. De momento, mi repositorio local
no se ha enterado de los cambios.
~/proyectos/proyecto-nuevo> git status En la rama master Tu rama está actualizada con 'origin/master'. nada para hacer comit, el arbol de trabajo esta limpio ~/proyectos/proyecto-nuevo> git checkout prueba Cambiado a rama 'prueba' Tu rama está actualizada con 'origin/prueba'. ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama está actualizada con 'origin/prueba'. nada para hacer comit, el arbol de trabajo esta limpio
Ninguna de las dos ramas se han dado cuenta que el remoto ha cambiado. Si hago modificaciones terminaré encontrando el problema.
~/proyectos/proyecto-nuevo> echo "Cambios en el repositorio local." >> archivo2.txt ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama está actualizada con 'origin/prueba'. Cambios no rastreados para el commit: (usa "git add <archivo>..." para actualizar lo que será confirmado) (usa "git checkout -- <archivo>..." para descartar los cambios en el directorio de trabajo) modificado: archivo2.txt sin cambios agregados a la confirmación (usa "git add" y/o "git commit -a") ~/proyectos/proyecto-nuevo> git add archivo2.txt ~/proyectos/proyecto-nuevo> git commit -m "Cambios en local pretendiendo que haya conflictos." [prueba 8f8437c] Cambios en local pretendiendo que haya conflictos. 1 file changed, 1 insertion(+) ~/proyectos/proyecto-nuevo> git push To /ruta/a-la/nube/proyecto-nuevo.git ! [rejected] prueba -> prueba (fetch first) error: fallo el push de algunas referencias a '/ruta/a-la/nube/proyecto-nuevo.git' ayuda: Actualizaciones fueron rechazadas porque el remoto contiene trabajo que ayuda: no existe localmente. Esto es causado usuallmente por otro repositorio ayuda: realizando push a la misma ref. Quizás quiera integrar primero los cambios ayuda: remotos (ejem. 'git pull ...') antes de volver a hacer push. ayuda: Vea 'Notes about fast-forwards0 en 'git push --help' para detalles.
Bien, todo parecía ir bien y al hacer el push
para subir los cambios
al remoto, se queja. Rechaza el cambio que he hecho, me dice que hay
cambios que no están en el local y me recomienda que primero
actualice mi copia de trabajo con esos cambios. Sus deseos son
órdenes.
~/proyectos/proyecto-nuevo> git fetch origin remote: Contando objetos: 3, listo. remote: Comprimiendo objetos: 100% (3/3), listo. remote: Total 3 (delta 1), reused 0 (delta 0) Desempaquetando objetos: 100% (3/3), listo. Desde /ruta/a-la/nube/proyecto-nuevo a60c91a..bf825fc prueba -> origin/prueba ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama y 'origin/prueba' han divergido, y tienen 1 y 1 commits diferentes cada una respectivamente. (usa "git pull" para fusionar la rama remota en la tuya) nada para hacer comit, el arbol de trabajo esta limpio
He utilizado el comando habitual de actualizar el repositorio local:
fetch
. Sin embargo, tras hacerlo git
sigue pidiéndome que fusione
la rama remota con la local haciendo un git pull
.
~/proyectos/proyecto-nuevo> git pull Auto-fusionando archivo2.txt CONFLICTO (contenido): Conflicto de fusión en archivo2.txt Fusión automática falló; arregle los conflictos y luego realice un commit con el resultado. ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama y 'origin/prueba' han divergido, y tienen 1 y 1 commits diferentes cada una respectivamente. (usa "git pull" para fusionar la rama remota en la tuya) tienes rutas no fusionadas (arregla los conflictos y corre "git commit" (usa "git merge --abort" para abortar la fusion) rutas no fusionadas: (usa "git add <archivo>..." para marcar una resolucion) ambos modificados: archivo2.txt sin cambios agregados a la confirmación (usa "git add" y/o "git commit -a")
El conflicto se ha manifestado. Podemos volvernos atrás y renunciar al
merge
, en ese caso perderemos nuestros cambios. Lo habitual en estos
casos es mediar en el conflicto de los dos repositorios.
Arreglar conflictos a mano
Todo conflicto me pone nervioso y me hace preguntarme si seré capaz de arreglarlo. Luego recuerdo que todo tiene solución y la solución habitual es enfrentarse al problema: abrimos el archivo en conflicto con nuestro editor favorito (en mi caso utilizo emacs).
Figura 1: Editando el fichero conflictivo con emacs.
Como se puede ver en la imagen. Se marcan los cambios entre dos
líneas, la primera marcada como <<<<<<< HEAD
y la última comienza
con una serie de caracteres >>>>>>>
. Esas dos líneas marcan la
amplitud del conflicto. Lo situado entre la primera y otra línea
marcada con una serie de caracteres «=» son los cambios que tengo en
local y lo que está entre los «=» y la última es lo que está en el
remoto. El hash que muestra es el identificador del commit
donde
están los cambios.
Vale, pero ¿cómo arreglo el conflicto?. En este caso es fácil, todo el texto estaría correcto, por lo que bastaría con borrar las líneas especiales para generar tener el fichero corregido. Cuando es código, podemos elegir una versión u otra o incluso generar código nuevo mezclando las dos versiones, si es necesario.
Después de realizar los cambios necesarios miro a ver cómo está el repositorio.
~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama y 'origin/prueba' han divergido, y tienen 1 y 1 commits diferentes cada una respectivamente. (usa "git pull" para fusionar la rama remota en la tuya) Todos los conflictos resueltos pero sigues fusionando. (usa "git commit" para concluir la fusion) Cambios a ser confirmados: modificado: archivo2.txt ~/proyectos/proyecto-nuevo>
Pues termino la fusión.
~/proyectos/proyecto-nuevo> git commit [prueba 8753c00] Merge branch 'prueba' of /home/notxor/nube/proyecto-nuevo into prueba ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama está adelantada a 'origin/prueba' por 2 confirmaciones. (usa "git push" para publicar tus confirmaciones locales) nada para hacer comit, el arbol de trabajo esta limpio ~/proyectos/proyecto-nuevo> git push Contando objetos: 6, listo. Delta compression using up to 4 threads. Comprimiendo objetos: 100% (6/6), listo. Escribiendo objetos: 100% (6/6), 659 bytes | 659.00 KiB/s, listo. Total 6 (delta 3), reused 0 (delta 0) To /path/a-la/nube/proyecto-nuevo.git bf825fc..8753c00 prueba -> prueba ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama está actualizada con 'origin/prueba'. nada para hacer comit, el arbol de trabajo esta limpio
Bien. Ya está solucionado el conflicto.
* 8753c00 (HEAD -> prueba, origin/prueba) Merge branch 'prueba' of /home/notxor/nube/proyecto-nuevo into prueba |\ | * bf825fc Añadido desde otro ordenador. * | 8f8437c Cambios en local pretendiendo que haya conflictos. |/ * a60c91a (origin/master, master) Añadir el archivo2.txt a la rama de pruebas. * ee63815 Añadir el archivo1.txt a la rama de pruebas. * f1f91e9 Primer commit del repositorio añadiendo el README.md.
Así nos queda el repositorio después de los cambios.
Revertir cambios
Hay ocasiones en las que acabo de subir un cambio y me doy cuenta de que falta algo o habría que corregir algún tema. Hay dos opciones. La primera es hacer un nuevo commit arreglando el entuerto o, la segunda, revertir los cambios.
~/proyectos/proyecto-nuevo> echo "Añadir nueva línea para revertir cambios." >> archivo2.txt notxor@oldulo:~/proyectos/proyecto-nuevo> cat archivo2.txt Esto es el fichero de prueba 2. Cambios en el repositorio local. Esto es una línea creada por otro usuario. Añadir nueva línea para revertir cambios. ~/proyectos/proyecto-nuevo> git add archivo2.txt ~/proyectos/proyecto-nuevo> git commit -m "Añadir cambios para revertirlos después." [prueba b1120fe] Añadir cambios para revertirlos después. 1 file changed, 1 insertion(+)
He añadido un commit a la línea del repositorio, pero quiero volver
atrás. Si es el último commit es sencillo puedo utilizar la opción
--amend
del comando commit
o puedo utilizar el comando revert
.
~/proyectos/proyecto-nuevo> git commit --amend [prueba 4a0256f] Añadir cambios para revertirlos después. Date: Wed Nov 15 10:05:17 2017 +0100 1 file changed, 1 insertion(+)
Nuestro repositorio a estas alturas tendrá la siguiente forma:
* 4a0256f (HEAD -> prueba) Añadir cambios para revertirlos después. * 8753c00 (origin/prueba) Merge branch 'prueba' of /home/notxor/nube/proyecto-nuevo into prueba |\ | * bf825fc Añadido desde otro ordenador. * | 8f8437c Cambios en local pretendiendo que haya conflictos. |/ * a60c91a (origin/master, master) Añadir el archivo2.txt a la rama de pruebas. * ee63815 Añadir el archivo1.txt a la rama de pruebas. * f1f91e9 Primer commit del repositorio añadiendo el README.md.
Bien, vale, no se ve que haya borrado nada. ¿Cómo borro el commit para hacer como si nunca hubiera existido?
Para eso utilizo el comando reset
bien con una opción --soft
si no
quiero perder los cambios locales o con una opción --hard
si quiero
quedarme en el mismo estado que estaba el commit
al que quiero ir.
~/proyectos/proyecto-nuevo> git reset 8753c00 --soft ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama está actualizada con 'origin/prueba'. Cambios a ser confirmados: (usa "git reset HEAD <archivo>..." para sacar del area de stage) modificado: archivo2.txt ~/proyectos/proyecto-nuevo>
En el caso del reset --soft
conservo los cambios que hice en el
repositorio local.
~/proyectos/proyecto-nuevo> git reset 8753c00 --hard HEAD está ahora en 8753c00 Merge branch 'prueba' of /home/notxor/nube/proyecto-nuevo into prueba ~/proyectos/proyecto-nuevo> git status En la rama prueba Tu rama está actualizada con 'origin/prueba'. nada para hacer comit, el arbol de trabajo esta limpio
En este caso, desaparecen todos los cambios que haya hecho en el
repositorio local y lo deja en el estado en el que estaba en el
commit marcado como 8753c00
.
Borrar rama
Algunas veces utilizo una rama sólo para hacer unas pruebas y hechas
esas pruebas según el resultado haremos un merge
o no, con la rama
principal. El caso es que cuando ya no me interesa utilizar esa rama
la quiero borrar.
Primero me voy a la rama principal, no se puede borrar una rama cuando la tiene activa (como si intentas cortar una rama de un árbol, en la que estás sentado).
~/proyectos/proyecto-nuevo> git checkout master Cambiado a rama 'master' Tu rama está actualizada con 'origin/master'. ~/proyectos/proyecto-nuevo> git branch -d prueba warning: borrando la rama 'prueba' que ha sido fusionada en 'refs/remotes/origin/prueba', pero aún no ha sido fusionada a HEAD. Eliminada la rama prueba (era 8753c00)..
La rama ya la fusionamos... ahora vamos a colocar nuestro HEAD
en el
sitio correcto.
~/proyectos/proyecto-nuevo> git reset 8753c00 --hard HEAD está ahora en 8753c00 Merge branch 'prueba' of /path/a-la/nube/proyecto-nuevo into prueba ~/proyectos/proyecto-nuevo> git push Total 0 (delta 0), reused 0 (delta 0) To /path/a-la/nube/proyecto-nuevo.git a60c91a..8753c00 master -> master
Ahora mismo tenemos todos los diales apuntando al mismo lugar o
commit. Lo podemos ver haciendo un log
.
~/proyectos/proyecto-nuevo> git log --oneline --graph --all * 8753c00 (HEAD -> master, origin/prueba, origin/master) Merge branch 'prueba' of /home/notxor/nube/proyecto-nuevo into prueba |\ | * bf825fc Añadido desde otro ordenador. * | 8f8437c Cambios en local pretendiendo que haya conflictos. |/ * a60c91a Añadir el archivo2.txt a la rama de pruebas. * ee63815 Añadir el archivo1.txt a la rama de pruebas. * f1f91e9 Primer commit del repositorio añadiendo el README.md.
¿Cómo? Sigue habiendo una rama origin/prueba
... ¿no la había
borrado? Bueno, sí y no. Había borrado la rama local pero la remota
aún no la he tocado. Falta hacer un push
para normalizar las ramas
en el remoto también.
~/proyectos/proyecto-nuevo> git push origin --delete prueba To /path/a-la/nube/proyecto-nuevo.git - [deleted] prueba
Conclusión
git
es una herramienta con una potencia que apenas he empezado a
rascar superficialmente y merece la pena el esfuerzo en aprender sus
modos y maneras. Venir de otros sistemas de control de versiones
hicieron que me costara al principio. Algunos comandos coinciden en
nombre, pero su función es distinta.
Estos dos artículos son en realidad el pasar a limpio mis apuntes
sobre git
y cómo he entendido yo las cosas. Quiero decir, que puedo
estar equivocado y que algo no sea exactamente como cuento en ellos. Y
que iré actualizando el contenido, para corregirlo o para ampliarlo si
es necesario.
De las cosas que hablo en los dos artículos, la mayoría se pueden
hacer con el comando gitk --all
que muestra un entorno gráfico tk
con el estado de nuestro repositorio y con el gui
(que es otro
entorno gráfico tk
) que nos auxilia en las acciones de actualizar la
información entre los repositorios. Sin embargo, me parece importante
saber qué estás haciendo en cada momento y cómo funciona la
herramienta que hay por debajo.