Usando Git(2): varios working copies


El el tutorial anterior, Usando Git(1), vimos cómo se inicia un proyecto en Git. En esta ocasión continuaremos con dicho proyecto, haciéndolo crecer hacia nuevos retos.

En esta ocasión dispondremos de varios working copies de la aplicación, lo que provocará que necesitemos más órdenes Git. Algunas de ellas van siendo algo avanzadas.

Trataré de introducirlas en un orden lógico, a medida que podemos ir necesitándolas.

Recordando

No viene mal repasar el dibujo que derrocha arte:

-----------   1   ---------
| Working |<----->| Stash |
|  copy   |       ---------
-----------
     ^
     |2
     |
     v
----------   3   ---------   4    ----------
|  Stage |<----->| Local |<------>| Remote |
----------       ---------        ----------

Ya conocemos las 5 zonas:

  • Working copy con los archivos a modificar.
  • Stash o cajón de sastre que aún no hemos utilizado
  • Stage para preparar nuestros commits
  • local, nombre que le dimos a los archivos ocultos de Git en nuestra máquina
  • remote, con los archivos de Git remotos.

Insisto una vez más en no confundier el Stash con el Stage.

En esta ocasión, a penas hablaremos del Stage, pero sí del Stash.

Progresando

Continuamos con el "Proyecto Fantabuloso" que nos va a hacer millonarios. Los ciclos add y commit dentro de ciclos push nos funcionan bien, pero tenemos que viajar. La máquina de viaje, a la que llamaremos Fog en honor a Phileas Fogg, es un poco pesada pero también un poco lenta. Por eso en casa preferimos utilizar Flash, ya que es más rápida.

Primero tenemos que realizar la puesta a punto de Fogg, cosa que ya sabemos hacer:

$ git clone git://example.com/fantabulosum.git
Cloning into 'fantabulosum'...
done.

Muy bien. ¿Y ahora qué? Si modificamos los archivos de Fogg, Flash estará desactualizada y viceversa. Supongamos que hicimos cambios en Flash, pusheamos y tenemos Fogg desactualizada. Existe una orden para traerse los cambios de remote a local:

$ git fetch
remote: Counting objects: 5, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From git://example.com/fantabulosum.git
   59aff13..8e328dc  master     -> origin/master

Git puede parecer demasiado verboso cuando se está comenzando. No hace falta pelearse con toda esa información aún. Simplemente es necesario saber que ya tenemos en local todo lo que está en remote. El problema es que nuestra working copy no tiene todo lo que está en local. Podemos verlo fácilmente:

$ git status
# On branch master
# Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
#
nothing to commit (working directory clean)

Me está diciendo que mi rama está 1 commit por detrás de lo que hay en el remote, y que puede ser "fast-forwardeada". Eso sólo significa que alguien (yo) ha hecho cambios en remote y que aún no los tengo en mi working copy. Vamos, lo que ya sabíamos.

Hay distintas maneras de realizar esa operación, el "fast-forward". "Fast-forward" tan sólo significa que "podemos avanzar sin problemas".

$ git merge origin/master
Updating 8e328dc..d7ff170
Fast-forward
 README |    2 +-

Cuando hicimos el status nos dijo que estábamos por detrás de "origin/master", así que es de suponer que "origin/master" es como se llama al punto en que nos encontramos en remote. Y así es.

Al decir que queremos mezclar la versión que hay en remote, Git se da cuenta de que mezclar los cambios es sencillo y lo hace sin problemas. De esta manera es como si hubiéramos realizado nuestros siempre en Fogg.

Ya podemos continuar con nuestros ciclos add-commit-push tranquilamente.

Conflictos

El escenario es el siguiente: en el tren la cobertura es muy mala y nos hemos visto obligados a continuar trabajando a sabiendas de que hay cambios en remote. Hemos realizado distintos ciclos add-commit, de manera que tenemos algo así:

         A---B---C Flash
        /
   D---E---F---G Fogg

Explico el gráfico: en el punto E pude hacer un fetch y, por tanto, en ese punto Flash y Fogg son iguales. A partir de ahí cada una tiene unos cambios que no tiene la otra.

En cuanto llegamos a casa queremos arreglar este estropicio... Estamos seguros de que nos llevará horas.

Primero nos vamos a una máquina y hacemos push. No hay problemas. Ahora nos vamos a la otra y tratamos de hacer lo mismo:

$ git push
To fantabulosum
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git://example.com/fantabulosum.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. 'git pull')
hint: before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

¡Horror! Pero... ¿qué me está diciendo...? Dice que use pull, pero no sé qué es eso. Vamos a hacer lo que hicimos antes, a ver qué ocurre:

$ git fetch
remote: Counting objects: 8, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (6/6), done.
From git://example.com/fantabulosum.git
   d7ff170..50ef0c0  master     -> origin/master

Hmmm.... parece que no se queja. ¿Y si mezclamos? Bien, en ese caso pueden ocurrir dos cosas:

El mundo es precioso y maravilloso

$ git merge origin/master
[Se nos abre un editor]
Merge made by the 'recursive' strategy.
 README |    4 ++++
 1 file changed, 4 insertions(+)

Durante la operación se ha abierto un editor para que escribamos un comentario. Eso es porque Git ha sido capaz de resolver los conflictos y no necesita nuestra ayuda. Durante esta resolución, necesitó hacer un commit con los cambios de los dos sitios.

Resultado: ya está todo listo para continuar. Hacemos push, nos vamos a la otra máquina, hacemos fetch y status y veremos que está todo preparado para hacer un "fast-forward", por lo que podemos hacer un merge y seguir trabajando como si nada.

El mundo tiene caminos llenos de piedras

$ git merge origin/master
Auto-merging README
CONFLICT (add/add): Merge conflict in README
Automatic merge failed; fix conflicts and then commit the result.

La mezcla automática ha fallado, lo arreglo y hago commit. Pues muy bien. Vamos a ello.

Lo primero será editar el archivo del conflicto. Aunque nos lo dice el mensaje, podemos consultarlo:

$ git status
# On branch master
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#   both added:         README
#
no changes added to commit (use "git add" and/or "git commit -a")

veamos lo que contiene el archivo README. Veremos que será algo como esto:

<<<<<<< HEAD
B
B
B
=======
A
A
A
A
>>>>>>> origin/master

¿Qué significa esto? Las líneas de "===" separan dos zonas, la de mis cambios en local (HEAD) de los cambios en remote (origin/master). Basta con editarlo como me guste a mí y borrar esas marcas. Una vez hecho, realizo el add-commit

Una vez realizado, podemos volver a intentar un merge. Si hay suerte, habremos terminado o Git será capaz de realizar los cambios por nosotros. Si no hay suerte... Habremos encontrado otra piedra, pero ya sabemos cómo resolver los conflictos.

Continuando

Hemos resuelto el problema. Ahora sabemos cómo trabajar desde dos máquinas sin importarnos perder la cobertura. Git comienza a demostrarnos el por qué de su fama.

¡Alto! ¡Un bug!

Mierda. Estábamos tan enfrascados en una característica nueva que no nos dimos cuenta y cometimos un error. Tenemos cambios en la working copy que no queremos perder, pero tampoco queremos que vayan en esta revisión (y recordemos que aún no sabemos qué coño es eso de las "ramas").

¿Qué podemos hacer?

Bien, pues basta con guardarlo en un cajón. Y nuestro cajón se llama stash

$ git stash
Saved working directory and index state WIP on master: 4421e44 merge
HEAD is now at 4421e44 merge

Ahora podemos arreglar el bug, que era cosa de poco. Realizamos nuestro ciclo add-commit-push y... ¿dónde lo habíamos dejado? Ah, sí, en el stash:

$ git stash pop
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   README
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (38f9609d4842369156d00d3e9632be475e14a2de)

¿¿¿Cómo??? ¡¡Nos ha restaurado el espacio de trabajo!!

Al restaurar, igual que en un merge, puede haber conflictos. Pero eso ya no nos asusta, ¿verdad?

Hay un par de órdenes muy interesantes, como stash list, para ver qué tenemos en el stash:

$ git stash list
stash@{0}: WIP on master: 4421e44 merge

o stash show, para ver qué está modificado:

$ git stash show
 README |    3 +++
 1 file changed, 3 insertions(+)

Suficiente por ahora

Y ya somos capaces de valernos por nosotros mismos. Nuestro "Proyecto Fantabuloso" progresa y no hemos necesitado ramas.

Veamos lo que hemos aprendido:

  • Traer información del repositorio: git fetch
  • Mezclar la working copy y local: git merge origin/master
  • Resolver conflictos
  • Meter en el stash: git stash
  • Sacar del stash: git stash pop

Más información

De nuevo recomiendo la web de Git o el libro progit, de Scott Chacon.


Comentarios

Comments powered by Disqus