Durante los últimos meses, he tenido este blog completamente abandonado. Actualmente, estoy involucrado en un nuevo proyecto que me absorbe completamente. Además, me ha supuesto orientarme hacia una serie de herramientas y tecnologías que había dejado en un segundo plano (y otras que no conocía). La verdad, es que estoy disfrutando mucho con ellas y se me ocurren muchas ideas para futuras entradas.
De momento, me gustaría dedicarle esta entrada a una herramienta con la que he disfrutado especialmente: la herramienta para gestión de versiones GIT.
Desde que comencé a desarrollar (hace ya unos cuantos años), siempre me he apoyado en alguna herramienta de gestión de versiones: Microsoft SourceSafe, CS-RCS y, en mis últimos años, Subversion:
- Cuando trabajaba en solitario, me servía para «protegerme» de mí mismo, como paracaídas ante errores imprevistos.
- Cuando trajaba en equipo, como herramienta para coordinar las distintas tareas en curso y poder revisar el código antes de pasarlo a producción.
- Y, siempre, como medio para llevar un histórico de todo lo que ocurría durante la vida del desarrollo (a veces, con años de extensión).
En mi último proyecto, tuve que asumir el mantenimiento de un desarrollo extenso y complejo en PHP. Sin ninguna documentación ni gestión de versiones. Hablé con mi jefe, y decidimos darle una oportunidad a GIT. Inicialmente, por mi experiencia previa, esperaba un aprendizaje rápido… nada más lejos de la realidad. Sin embargo, os aseguro que el esfuerzo invertido ha merecido la pena con creces.
¿A qué se debe ese esfuerzo de entrada tan grande? En mi opinión, porque trabajar con GIT supone un cambio en el modo de trabajo. Si tuviera que hacer una analogía, diría que trabajar con Subversion es como tocar música clásica (todo ordenado, con partituras, ritmo, un director de orquesta marcando los tempos…) pero trabajar con GIT es como interpretar en una banda de Jazz (sin partituras, cada músico a su bola … pero todo suena de maravilla).
GIT es muy extenso, con muchas tareas y herramientas para gestionar nuestro código y mucha flexibilidad para usarlas. Para mí, al comienzo, fue abrumador. Pero el esfuerzo merece la pena, incluso trabajando en solitario. Por eso, en estas entradas, voy a intentar concentrarme en aquellas tareas que creo que pueden ayudar a resolver los problemas a los que se enfrenta un desarrollador independiente (el «vaquero solitario» del título).
Si alguien encuentra esta entrada y se decide a leerla, le sugiero humildemente que la lea tres veces:
- En la primera lectura, se trata de comprobar si se identifica (o no) con los escenarios que le describo. La clave es pensar «Esto, me ocurre a mí. Ojalá tuviera una herramienta que me ayudara con estos problemas».
- En la segunda lectura, se trata de leer las instrucciones GIT correspondientes a cada escenario. Sólo debe familiarizarse con ellas. Se trata de pensar «Estas instrucciones, parecen razonables, coherentes, fáciles de entender en ese contexto».
- Por último, si ha decidido llegar hasta aquí, le sugiero que repita las instrucciones en su propio entorno de trabajo. Se trata de asimilarlas para convertirlas en parte de su propio conjunto de herramientas.
Sólo dos observaciones:
- Para que no sea demasiado extensa en esta entrada, no explico cómo instalar GIT. Aunque estoy seguro que no tendréis problemas en encontrar buenos titulares al respecto).
- En los ejemplos, me concentro en usar GIT dentro de la consola línux (concretamente, uso Ubuntu).
Bueno, basta de literatura y vamos por fin a jugar con GIT.
Someter un proyecto nuevo (o un proyecto existente) a GIT
Imaginemos que tenemos un proyecto entre manos: nos han pedido que hagamos un listado de obras por autor. Para ello:
- Vamos a crear una carpeta
proyecto_autores
que va a contenter todos nuestros ficheros. - Dentro de esta carpeta, vamos a crear un fichero por autor.
- En cada fichero, introduciremos una obra por línea.
1 2 3 4 5 6 7 |
cd /proyecto_autores usuario@usuario-desktop:~/proyecto_autores$ cat george_orwell.txt rebelión en la granja 1984 usuario@usuario-desktop:~/proyecto_autores$ cat arturo_perez_reverte.txt la novena puerta el maestro de esgrima |
Vamos a inicializar GIT en este proyecto. Para ello, ejecutamos git init
en la carpeta raíz del proyecto:
1 2 3 4 |
usuario@usuario-desktop:~/proyecto_autores$ git init Initialized empty Git repository in /home/usuario/proyecto_autores/.git/ usuario@usuario-desktop:~/proyecto_autores$ ls -ld .git drwxrwxr-x 7 usuario usuario 4096 ene 3 20:03 .git |
Con la instrucción git init .
le estamos pidiendo a GIT «supervisa el contenido de la carpeta xxx» (hay que pasarle como argumento la carpeta donde está la raíz de nuestro proyecto, en este caso la carpeta donde ejecutamos el comando). Cuando ejecutamos esta instrucción, GIT creará una subcarpeta .git
donde guardará toda la información que necesita.
Ya está, nada más. Ni configurar servidores centrales ni instalar servicios adicionales en nuestro servidor. Con esta simple instrucción, ya tenemos supervisada por GIT nuestra carpeta proyecto_autores
y absolutamente todo su contenido (aunque también podemos personalizar esto).
Vamos a pedirle a GIT que nos indique el estado de nuestro proyecto:
1 2 3 4 5 6 7 8 9 10 11 |
usuario@usuario-desktop:~/proyecto_autores$ git status # On branch master # # Initial commit # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # arturo_perez_reverte.txt # george_orwell.txt nothing added to commit but untracked files present (use "git add" to track) |
De momento, vamos a fijarnos en la siguiente información:
Initial commit
quiere decir «estamos en la primera versión».Untracked files
nos lista nuestros dos ficheros. Lo que GIT nos está diciendo es «estos ficheros, no sé qué hacer con ellos».
Pedirle a GIT que incluya ficheros en el proyecto
¿Por qué nos dice que nuestros dos ficheros están Untracked
? Porque en nuestro recién inicializado GIT, por defecto, está absolutamente limpio. No incluye absolutamente nada. Cualquier fichero que incluyamos, GIT lo considerará untracked
hasta que nosotros le digamos qué debe hacer con ellos.
Vamos a indicarle a GIT que nuestros dos ficheros pertenecen a nuestro proyecto y que a partir de ahora debe supervisarlos:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
usuario@usuario-desktop:~/proyecto_autores$ git add arturo_perez_reverte.txt usuario@usuario-desktop:~/proyecto_autores$ git add george_orwell.txt usuario@usuario-desktop:~/proyecto_autores$ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: arturo_perez_reverte.txt # new file: george_orwell.txt # |
Con la instrucción git add
le pedimos a GIT «marca al fichero xxxx para seguirlo».
Una vez añadidos estos dos ficheros, hemos vuelto a pedirle el status a GIT. Ahora, nos dice que los dos ficheros están catalogados como "Changes to be committed"
y han pasado de estado Untracked
a estado new file
.
¿Qué nos quiere decir esto? Para comprenderlo, debemos comprender el modo de trabajo básico con GIT:
- Paso 1: modificamos nuestro proyecto (añadiendo ficheros, borrando ficheros, modificando ficheros,…).
- Paso 2: le pedimos a GIT que marque los ficheros modificados y añadidos para el próximo commit (esto es lo que acabamos de hacer).
- Paso 3: cuando estemos conformes con los cambios realizados, le pedimos a GIT que realice un commit de los cambios marcados en el paso anterior.
Hay terminología de GIT como commit
que prefiero no traducir, prefiero usar la palabra inglesa original para no perder sentido. Pero ¿qué es un commit
? Un commit es la unidad de trabajo de GIT: un conjunto de cambios que han sido enviados en una sola tanda por el programador a GIT. GIT ve el proyecto como una serie de sucesivos commits. Para GIT, lo importante son los commits: qué ficheros se incluyeron en el commit y qué cambios han provocado este commit al proyecto. Esto es muy importante para comprender GIT, aunque su importancia real no la comprenderemos hasta más adelante.
En otras palabras:
- Primero, modificamos una serie de ficheros.
- Segundo, cuando estamos conforme con los cambios, los marcamos para el próximo commit
- Tercero, les damos el visto bueno realizando un commit con ellos.
En este ejemplo concreto:
- Hemos añadido dos ficheros al proyecto.
git status
nos ha avisado «tienes ficheros nuevos untracked». - Con
git add
los hemos marcado para que que los incluya en el próximo commit.git status
nos avisa ahora que «tienes ficheros marcados para el próximo commit».
Y, por último, como todo está correcto, realizamos git commit
:
1 2 3 4 5 6 7 8 9 |
usuario@usuario-desktop:~/proyecto_autores$ git commit -m "primera version" [master (root-commit) ce53954] primera version 2 files changed, 4 insertions(+) create mode 100644 arturo_perez_reverte.txt create mode 100644 george_orwell.txt usuario@usuario-desktop:~/proyecto_autores$ git status # On branch master nothing to commit (working directory clean) usuario@usuario-desktop:~/proyecto_autores$ |
Una vez realizado el commit, le pedimos un git status
y GIT nos responde nothing to commit
(o sea, «sin novedad en el frente, lo tengo todo controlado»).
Ahora, vamos a seguir modificando ficheros
Vamos a añadir dos nuevos títulos a la lista de Pérez Reverte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
usuario@usuario-desktop:~/proyecto_autores$ echo "el club dumas" >> arturo_perez_reverte.txt usuario@usuario-desktop:~/proyecto_autores$ echo "la tabla de flandes" >> arturo_perez.txt usuario@usuario-desktop:~/proyecto_autores$ git status # 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: arturo_perez_reverte.txt # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # arturo_perez.txt no changes added to commit (use "git add" and/or "git commit -a") |
Cuando he añadido el segundo título, me he equivocado (a propósito) con el nombre del fichero destino. Cuando he pedido git status
GIT me responde:
- arturo_perez_reverte.txt está en estado
modified
. - arturo_perez.txt está
untracked
y en estadonew
. - También nos avisa que ambos ficheros están
not staged for commit
(o sea, que aunque están modificados todavía no los hemos marcado para ejecutar el commit con ellos).
En cuanto he visto el status me he dado cuenta que he metido la pata, así que lo voy a arreglar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
cat arturo_perez.txt >> arturo_perez_reverte.txt usuario@usuario-desktop:~/proyecto_autores$ cat arturo_perez_reverte.txt la novena puerta el maestro de esgrima el club dumas la tabla de flandes usuario@usuario-desktop:~/proyecto_autores$ rm arturo_perez.txt usuario@usuario-desktop:~/proyecto_autores$ git status # 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: arturo_perez_reverte.txt # no changes added to commit (use "git add" and/or "git commit -a") |
Ya tengo la tarea controlada: he borrado el fichero incorrecto y mi fichero de Pérez Reverte está correctamente modificado.
Ahora, como todavía es temprano y me siento con energías, voy a trabajar un poquito más:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
usuario@usuario-desktop:~/proyecto_autores$ echo "la piel del tambor" >> arturo_perez_reverte.txt usuario@usuario-desktop:~/proyecto_autores$ echo "homenaje a cataluña" >> george_orwell.txt usuario@usuario-desktop:~/proyecto_autores$ echo "la llamada de la selva" >> jack_london.txt usuario@usuario-desktop:~/proyecto_autores$ echo "colmillo blanco" >> jack_london.txt usuario@usuario-desktop:~/proyecto_autores$ git status # 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: arturo_perez_reverte.txt # modified: george_orwell.txt # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # jack_london.txt no changes added to commit (use "git add" and/or "git commit -a") |
Efectivamente, he trabajado un montón:
- He añadido libros nuevos a las listas de Reverte y Orwell.
- He comenzado con un nuevo autor, Jack London.
Pero GIT me avisa que no sabe qué hacer con el nuevo fichero jack_london.txt
. Así que voy a pedirle a GIT que lo incluya en el próximo commit:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
usuario@usuario-desktop:~/proyecto_autores$ git add jack_london.txt usuario@usuario-desktop:~/proyecto_autores$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: jack_london.txt # # 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: arturo_perez_reverte.txt # modified: george_orwell.txt # no changes added to commit (use "git add" and/or "git commit -a") |
Ahora, me dice que «jack_london.txt» está marcado para el próximo commit (changes to be commited
) y en estado new file
.
Pero GIT también me avisa que arturo_perez_reverte.txt y george_orwell.txt están modified
pero todavía no están marcados para commit (Changes not staged for commit
). Efectivamente, debemos recordar lo que he explicado antes: primero, modificamos; segundo marcamos para el próximo commit; tercero, ejecutamos el commit. Vamos a marcarlos para el commit:
1 2 3 4 5 6 7 8 9 10 11 |
usuario@usuario-desktop:~/proyecto_autores$ git add arturo_perez_reverte.txt usuario@usuario-desktop:~/proyecto_autores$ git add george_orwell.txt usuario@usuario-desktop:~/proyecto_autores$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: jack_london.txt # modified: arturo_perez_reverte.txt # modified: george_orwell.txt # |
Ya tengo preparado mi commit, formado por los dos ficheros modificados y un fichero nuevo. Vamos a realizar el commit:
1 2 3 4 5 6 7 |
usuario@usuario-desktop:~/proyecto_autores$ git commit -m "segunda version" [master 1705dce] segunda version 3 files changed, 6 insertions(+) create mode 100644 jack_london.txt usuario@usuario-desktop:~/proyecto_autores$ git status # On branch master nothing to commit (working directory clean) |
Después de realizar el commit, vuelvo a pedir un git status
y ahora me responde de nuevo «sin novedad en el frente»
Modifica, marca y confirma; modifica, marca y confirma … modifica, marca y confirma
Esta es la pauta de trabajo con GIT:
- Modificar ficheros que necesites.
- Cuando consideres oportuno, marca los ficheros para commit.
- Por último, confirmas las modificaciones ejecutando un
git commit
con los ficheros marcados.
La clave es ¿cuándo es oportuno realizar un commit? ¿qué ficheros incluir en un commit?. Esta es una de las flexibilidades de GIT:
- Puedes modificar los ficheros que quieras, pero puedes marcar para commit sólo los que creas necesario (por ejemplo, puedes marcar para el commit algunos ficheros porque los has estabilizado y dejar el resto de los ficheros modificandos pendientes para un commit posterior).
- Puedes realizar los commits que quieras.
Personalmente, opino que cuantos más commits realices, mejor:
- Procuro agrupar modificaciones de forma que un commit agrupa una serie de modificaciones relacionadas entre sí.
- Le presto mucha atención a los comentarios que añado al commit, porque me dan mucha información histórica del proyecto.
- Si voy a comenzar a modificar el código y no estoy seguro de lo que voy a hacer… realizo un commit para marcar el punto de inicio de esas modificaciones (efectivamente, ya veremos que puedo volver para atrás y restablecer el código al commit que quiera).
Antes de terminar esta entrada, vamos a ver un último comando:
1 2 3 4 5 6 7 8 9 10 |
usuario@usuario-desktop:~/proyecto_autores$ git log --format=short commit 1705dce0530d2a7dbf1063b568fed8cc643bca6e Author: Jose Manuel Jimenez <jmjimenezg@gmail.com> segunda version commit ce539540a100abe82c44d2671bd0cba334cfea8f Author: Jose Manuel Jimenez <jmjimenezg@gmail.com> primera version |
Efectivamente, con git log
le he pedido a GIT que me muestre un listado de los commits realizados. Si los comentarios que yo he proporcionado al ejecutar los commits hubieran sido más descriptivos (por ejemplo: «nuevo autor Jack London» o «Más libros para Pérez Reverte») los mensajes del log hubieran sido mucho más interesante para estudiar la vida de mi proyecto.
¿Conclusiones?
- Ya hemos iniciado un proyecto supervisado por GIT.
- Hemos hecho prácticas de la rutina de trabajo con GIT: modificar, marcar y confirmar:
- Le hemos pedido a GIT que añadida ficheros al proyecto y hemos modificado ficheros.
- Le hemos dado nuestro visto bueno a las modificaciones marcándolas para commit.
- Hemos realizado el commit para confirmar nuestros cambios.
- Por último, le hemos pedido a GIT que nos liste los commits que hemos realizado.
De momento, vamos a dejarlo aquí. No hemos hecho algo especialmente útil. Pero en una próxima entrada seguiremos trabajando en nuestro proyecto. Cometeremos algunos errores y tendremos que solucionar algunos imprevistos y entonces veremos lo realmente útil que puede ser GIT. Espero que esta entrada os haya gustado y que os animéis a leer las próximas