Pero, ¿qué es git?
Tienes razón, mejor empezamos por el principio.
Git es un sistema de control distribuido para poder hacer seguimiento de cambios en código fuente (y otras cosas). En palabras más mundanas, es un vigilante de las cosas que vamos haciendo (cambios de código, cambios de nombres de ficheros,…) en un carpeta de ficheros en un ordenador.
Git se ha convertido en un estándar de facto al haber sido adoptado por sitios como github o gitlab.
¿Qué significa que sea distribuido?
Significa que el repositorio completo de código, sus cambios, sus mensajes de nuevo código,…, se pueden encontrar en distintos ordenadores.
Normalmente, hay un repositorio central de código. Este repositorio central de código puede alojarse en sitios como github, gitlab o bitbucket y así puedes tener una interfaz web para recibir informes de errores, poder hacer revisiones de código, poder repartir tareas,…
¿Y para qué necesito yo git si me organizo muy bien?
El uso de git no es un capricho, es una necesidad, Algunas posibles razones para su uso:
- A la larga ahorrarás tiempo.
- Podrás rehacer y deshacer funcionalidad teniendo múltiples versiones de tus experimentos junto con seguimiento de lo que ha ido funcionando y lo que no.
- Podrás trabajar en funcionalidad específica en paralelo y solo unir esa nueva funcionalidad si merece la pena.
- Evitarás tener algún esperpento como lo de más abajo, es decir, tendrás una única versión que funciona bajo cierto entorno:
- fichero_codigo.py
- fichero_codigo_20120203-py
- fichero_codigo_20120203_rev1.py
- fichero_codigo_nuevo.py
- …
- Puedes trabajar de forma colaborativa con otros compañeros de tu laboratorio, empresa o de cualquier otra parte del mundo.
- Cuando añades nuevo codigo puedes acompañarlo de mensajes útiles.
- Otros pueden auditar tu código permitiendo que te puedan informar de errores.
- Seguramente tengas una copia en caso de que te roben el PC, se te queme, le caiga agua encima,…
- Puedes integrarlo con muchas otras herramientas para hacer pruebas, comprobar funcionamiento en distintos entornos,…
- Otros pueden usar tu código encontrando nuevos usos en los que no habías caido o reproduciendo tus resultados (reproducibilidad).
- Tienes formas de mantener tu código privado en caso de que lo necesites.
- Tienes control absoluto de lo que quieres integrar y de lo que no en tu proyecto.
- …
¿He logrado convencerte para usar git? Vamos, pues, a instalarlo
Perfecto, vamos a instalarlo primero. Te diré como lo hago yo pero hay otras formas.
Normalmente tengo un entorno conda
de herramientas. Puedes crear uno de la siguiente forma (desde la línea de comandos):
conda create -n tools conda activate tools conda install -c conda-forge git
Lo bueno de lo anterior es que se hace igual en Windows, Linux y/o Mac.
Y ya estaría instalado y se podría usar desde la línea de comandos con el entorno tools
activado.
¿Algunos comandos git indispensables?
Esto no pretende ser un tutorial extenso ni detallado sobre como usar git. Más bien se puede ver como una chuleta de comandos con los cuales resuelves el mayor porcentaje de lo que tienes que hacer con git en tu día a día.
Primero hemos de configurar git para identificarnos y que aparezcamos como los autores cuando estamos interactuando con el repositorio de código.
git config --global user.name "KikoCorreoso" git config --global user.email KikoCorreoso@example.com
En lo anterior he definido el nombre de usuario y el correo de forma global y será el que se use desde la máquina en la que os encontráis.
Muy bien, ya nos hemos identificado pero queremos empezar a toquetear código. Para ello podemos crear un repositorio nuevo o ‘clonar’ (hacer una especie de copia) uno ya existente. Lo podemos hacer de la siguiente forma:
Para un repositorio nuevo:
mkdir mi_repo cd mi_repo git init
Con lo anterior hemos creado una carpeta llamada “mi_repo” y desde dentro de la misma he inicializado un repositorio indicándole a git que quiero que siga los cambios que se produzcan en esa carpeta.
Si el repositorio ya existiera podríamos clonarlo así:
git clone https://github.com/kikocorreoso/pyboqt.git cd pyboqt
Con lo anterior hemos clonado un repositorio ya existente que se encuentra en github.com, en la cuenta de kikocorreoso y con nombre pyboqt.
Eso nos descarga una carpeta con el código a la que podemos acceder. Esa carpeta está sometida al seguimiento de cambios por parte de git.
Si una vez que estás dentro de la carpeta del repositorio haces algún cambio y quieres que git sea consciente de ello puedes hacer:
git add nombre_del_fichero git add * # para añadir todos los ficheros con cambios de una vez
Ahora has añadido nuevos ficheros al índice pero tienes que informar de los cambios y puedes aportar un mensaje descriptivo:
git commit -m "Cambios en la función x para que reciba el parámetro j"
Para ver cómo se encuentra el estátus del repositorio puedes usar:
git status
Y te informará del estado actual de las cosas.
Si ya has añadido cambios en tu repositorio local y los quieres enviar al repositorio remoto (que puede estar en github, por ejemplo) puedes usar:
git push origin master
Es decir, envía los cambios locales (origin) al repositorio remoto (master).
Lo anterior puede que falle si no has informado a git de cual será el remositorio remoto. Lo podrías hacer así:
git remote add origin https://github.com/kikocorreoso/pyboqt.git
Ver más sobre esto aquí.
Para verificar que hemos añadido correctamente el repositorio remoto podemos hacer uso de:
git remote -v
Hasta ahora hemos visto lo más básico. Imagina ahora que quieres añadir una nueva rama de código para comprobar la viabilidad de añadir una nueva funcionalidad o para trabajar en un reformateo de código o para trabajar en un bug,… Podemos hacer:
git branch nombre_de_la_rama
Para cambiar a la nueva rama que hemos creado podemos usar:
git checkout nombre_de_la_rama
Ahora podríamos empezar a trabajar en esta nueva rama sin modificar lo que ya tenemos en la rama original, normalmente llamada master.
Si no nos convence lo que hemos hecho en la nueva rama la podemos eliminar usando:
git branch -d nombre_de_la_rama
Si nos convence pero no hemos terminado aun podemos enviarla al remoto para seguir trabajando en otro momento usando:
git push origin nombre_de_la_rama
Puede que tengas más de una rama. Las puedes ver usando:
git branch
Puedes mandar todas las ramas locales al remoto usando:
git push --all origin
Si has cambiado de opinión con respecto a una rama que ya habías mandado al remoto puedes eliminarla usando:
git push origin --delete nombre_de_la_rama
Muy bien, estamos en otro PC que ya tiene el repositorio y queremos seguir trabajando con nuestro repositorio. Lo podemos actualizar haciendo
git pull
Si creemos que la funcionalidad que estábamos desarrollando en una nueva rama está lista para ser integrada podemos unirla a la rama que tengamos activa, que normalmente será master, usando:
git merge nombre_de_la_rama
Antes de unir nada puedes usar:
git diff
Para ver las diferencias.
En el caso de que ya hayas unido algo y te hayas arrepentido puedes deshacer los cambios usando:
git checkout -- nombre_del_fichero
Puedes acceder a un log de lo que has ido haciendo usando
git log
También puedes guardar ‘versiones’ usando etiquetas que te permiten etiquetar determinados momentos de tu desarrollo. Por ejemplo, si consideras que has llegado a una nueva versión la puedes etiquetar usando:
git tag 1.0.0 id_del_commit
Lo último, id_del_commit, no es estrictamente necesario y usará el último “commit”. Podemos añadir un mensaje también usando:
git tag -a 1.0.0 -m "Final 1.0.0 version"
Esta etiqueta estará en tu repositorio local. Si la quieres mandar también al remoto puedes usar:
git push --tags origin
Para borrar tags en remoto y en local, respectivamente, se puede usar:
git push --delete origin nombre_del_tag # remoto git tag -d nombre_del_tag # local
Puedes borrar cosas para que dejen de estar controladas por git usando, por ejemplo:
git rm nombre_del_fichero
git pull
puede ser un comando destructivo puesto que hace dos cosas en una, git fetch
y git merge
, es decir, descarga datos del repositorio remoto y los intenta unir. Si al hacer la unión aparecen problemas pueden complicarse las cosas.
Es por ello que si no sabes muy bien lo que estás haciendo quizá sea más seguro usar esos dos comandos por separado.
git fetch
descarga la información del repositorio remoto pero no hace nada con ello. Solo la tenemos ahí para inspeccionarla. Normalmente es buena idea hacer eso antes de usar git merge
.
Si se producen problemas con un git merge
puedes leer aquí.
Muy bien, muchas cosas, ¿practicamos?
Vamos a hacer uso de algunos de los comandos anteriores con un repositorio nuevo. El repositorio, en muchas ocasiones lo creamos primero en el sitio donde centralizaremos el desarrollo. No voy a explicar aquí como crear un repositorio nuevo en Github, GitLab, BitBucket u otros servicios:
- https://help.github.com/en/github/getting-started-with-github/create-a-repo
- https://docs.gitlab.com/ee/user/project/repository/#create-a-repository
- https://confluence.atlassian.com/bitbucket/create-a-git-repository-759857290.html
El que yo he creado para este ejemplo se llama pybogit y está en github. Visualmente lo podéis ver como:
Para traernos el repositorio a nuestro PC local uso (siempre considero que estás trabajando desde la línea de comandos y con el entorno tools
activado, mira más arriba, que es donde hemos instalado git):
git clone https://github.com/kikocorreoso/pybogit.git
Visualmente lo podéis ver como:
Ahora que ya lo tenemos en nuestro PC nos movemos a esa carpeta:
cd pybogit
Y ahí podemos crear una carpeta llamada src
y dentro de esa carpeta podemos crear un fichero que se llame mi_modulo.py
:
mkdir src touch src/mi_modulo.py
Lo anterior habrá creado una carpeta y un fichero dentro de ella. Lo podéis hacer de otras formas. En windows, por ejemplo, podéis usar el notepad. Si ahora le preguntamos a git por el estátus del repositorio nos dirá lo siguiente:
git status
En la rama master Tu rama está actualizada con 'origin/master'. Archivos sin seguimiento: (usa "git add …" para incluirlo a lo que se será confirmado) src/ no hay nada agregado al commit pero hay archivos sin seguimiento presentes (usa "git add" para hacerles seguimiento)
El anterior mensaje me dice que me encuentro en la rama master (luego vemos esto) y que tengo cosas en la carpeta que no están bajo el seguimiento de git. Para decirle a git que tenga en cuenta el nuevo fichero puedo hacer, por ejemplo:
git add src/mi_modulo.py
Si volvemos a mirar el estátus nos dirá:
git status
Tu rama está actualizada con 'origin/master'. Cambios a ser confirmados: (usa "git restore --staged …" para sacar del área de stage) nuevo archivo: src/mi_modulo.py
Me indica que he añadido un nuevo fichero al repositorio pero los cambios todavía no han sido confirmados a git. Para confirmarlos hacemos un commit:
git commit -m "commit con mensaje super util"
En la consola podré leer un mensaje parecido a:
[master d296fbe] commit con mensaje super util 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/mi_modulo.py
Si ahora miramos de nuevo el estátus:
git status
Vemos que todos los cambios han sido integrados:
En la rama master Tu rama está adelantada a 'origin/master' por 1 commit. (usa "git push" para publicar tus commits locales) nada para hacer commit, el árbol de trabajo está limpio
Pero me dice que están en mi rama local y que la remota está un ‘cambio’ (commit) por detrás. La situación actual vista gráficamente sería algo así:
En este momento podríamos mandar los cambios al repositorio remoto (siempre es buena idea hacer esto). Lo podemos hacer de la siguiente forma:
git push origin master
Visto visualmente sería algo así:
Y ahora estariamos en esta situación:
Es decir, tenemos la misma información en local y en remoto.
Vamos a hacer un nuevo cambio en el fichero que hemos estado usando. Meto el siguiente código:
def greet(): return "Hello"
Guardo el fichero y si miro el estátus veré que git me dice que:
En la rama master Tu rama está actualizada con 'origin/master'. Cambios no rastreados para el commit: (usa "git add …" para actualizar lo que será confirmado) (usa "git restore …" para descartar los cambios en el directorio de trabajo) modificado: src/mi_modulo.py sin cambios agregados al commit (usa "git add" y/o "git commit -a")
Añadimos y ‘commiteamos’ (sí, la jerga es la que es) y lo mandamos al remoto:
git add * git commit -m "added function greet" git push origin master
La situación actual sería:
Se me ocurre ahora añadir una función experimental. Voy a crear una rama nueva para meter esta función experimental. La rama master podéis verla como el tronco principal del árbol. Siempre partimos con una sola rama principal o master. Pero al árbol le podemos añadir ramas y vamos a aprovechar este momento para hacer eso mismo. Para crear la rama y cambiarnos a ella en un único comando podemos hacer:
git checkout -b nueva_rama
Lo anterior es un atajo para los dos siguiente comandos:
git branch nueva_rama git checkout nueva_rama
Ahora, git considera que estamos trabajando en una nueva rama. Lo podemos ver preguntando nuevamente el estátus (git status
):
En la rama nueva_rama nada para hacer commit, el árbol de trabajo está limpio
y los cambios que hagamos no irán al tronco (la rama master). Hagamos algún cambio añadiendo nuestra funcionalidad experimental. Modificamos el fichero que hemos creado anteriormente añadiendo el siguiente código:
def saludo(): return "Hola"
Añadimos los nuevos cambios a la rama en la que nos encontramos usando:
git add * git commit -m "added experiment to branch"
La situación actual sería algo así:
Podemos mandar esa nueva rama al repositorio remoto usando (cuidado, enviaría TODAS las ramas al remoto, en este caso solo tengo una por lo que me vale):
git push --all origin
Y me sale un mensaje parecido a:
Username for 'https://github.com': kikocorreoso Password for 'https://kikocorreoso@github.com': Enumerando objetos: 7, listo. Contando objetos: 100% (7/7), listo. Compresión delta usando hasta 4 hilos Comprimiendo objetos: 100% (3/3), listo. Escribiendo objetos: 100% (4/4), 355 bytes | 355.00 KiB/s, listo. Total 4 (delta 1), reusado 0 (delta 0) remote: Resolving deltas: 100% (1/1), completed with 1 local object. remote: remote: Create a pull request for 'nueva_rama' on GitHub by visiting: remote: https://github.com/kikocorreoso/pybogit/pull/new/nueva_rama remote: To https://github.com/kikocorreoso/pybogit.git [new branch] nueva_rama -> nueva_rama
Ahora que la rama local está también en el remoto tendríamos algo así:
Y podríamos pedir a nuestros colaboradores que revisasen esa nueva rama, si creen que es útil, si mejor eliminar ese código por la razón que sea,…
Hemos decidido eliminarla. La elimino del local y del remoto. Primero salgo de la rama:
git checkout master
Podemos ver las ramas que tenemos y en la que nos encontramos usando:
git branch
Que nos mostrará algo como:
* master nueva_rama
Con el asterisco indicando que estamos ahora en la rama master. Voy a coger la sierra y a cortar la rama nueva_rama:
git branch -D nueva_rama
La opción -D
es importante que esté en mayúscula. Probadlo primero en minúscula. Cojo la sierra y me la llevo hasta el servidor y corto la rama en el remoto:
git push origin --delete nueva_rama
Ahora podéis mirar las ramas que os quedan y deberíais ver solo la rama master.
Y podríamos seguir probando cosas pero lo voy a dejar aquí para no aburrirte. Espero que si no sabías nada de git este artículo te haya abierto un poco el apetito y te ayude a guiarte mínimamente. Si tienes dudas usa los comentarios.
Tienes este artículo convertido en chuleta gracias a Geocurios (@borlafgis). La puedes descargar de aquí.
Para los que prefieren aprender de forma más visual
Si te gusta más que te cuenten las cosas en lugar de leer añado la siguiente charla de Sofía Denner en la PyConAr2018:
Y aquí tienes una herramienta para aprender de forma visual el uso de diferentes comandos.