Mi Guía definitiva Git para desarrolladores
Mi Guía definitiva Git para desarrolladores
Nunca está de más recordar los comandos básicos de Git y hoy es ese día. En este artículo que escribí para mi equipo en una de las empresas donde trabajé, voy a mostraros cómo trabajábamos con este sistema de control de versiones en un proyecto que implicó a seis personas y casi dos años de desarrollo.
¿Qué hay que saber sobre las ramas?
Suelo trabajar usando GitFlow, donde tenemos una rama main, una develop y otras que podrán ser feature/ o hotfix/.
El flujo es siempre el mismo: desde develop se crean nuevas ramas para trabajar en local y posteriormente fusionarlas de nuevo en develop (remoto). Cuando hayamos alcanzado nuestros objetivos, se fusiona develop en main para pasar a producción.
Si eres nuevo en Git, te recomiendo que leas primero el artículo Introducción a Git.
Convenciones de nombres
Sobre la norma para nombrar los commits, últimamente estoy usando Conventional Commits, que es un estándar para escribir mensajes de confirmación que puedes ver en este enlace. Os pongo el formato y los tipos más comunes:
<tipo>[ámbito opcional]: <descripción con verbo en infinitivo>
feat: añadir nueva funcionalidad
fix: corregir un error
docs: cambios en la documentación
style: cambios que no afectan al significado del código (espacios en blanco, formato, punto y coma que falta, etc.)
refactor: un cambio en el código que no corrige un error ni añade una funcionalidad
perf: un cambio en el código que mejora el rendimiento
test: añadir pruebas faltantes o corregir pruebas existentes
chore: cambios en el proceso de construcción o herramientas auxiliares y bibliotecas como generadores de documentación
Recomendación si no has usado git nunca y que no te dicen
Antes de ejecutar cualquier comando de Git en tu rama y creas que la puedas fastidiar (sobretodo hasta que no te acostumbres a trabajar con este sistema de control de versiones), haz una copia de seguridad de la carpeta del proyecto con el estado actual de la tarea. Así si ocurre algún desastre que no puedas controlar, podremos volver a un estado anterior y de nuevo repetir el proceso correctamente.
Ciclo de vida o estados de los archivos en Git
Los estados más relevantes (hay varios más) son:
- Archivos Untracked (sin rastrear): son archivos que NO viven dentro de Git, solo en el disco duro. Git no tiene registros de su existencia. Por ejemplo, un archivo nuevo que hemos creado y aún no ha sido añadido con
git add
.
touch my_new_file # untracked
- Archivos Staged: son archivos en Staging. Viven dentro de Git y hay registro de ellos porque han sido afectados por el comando
git add
, aunque no sus últimos cambios. Git ya sabe de la existencia de estos últimos cambios, pero todavía no han sido guardados definitivamente en el repositorio porque falta ejecutar el comando git commit.
git add my_new_file # staged
- Archivos Tracked (rastreados o en seguimiento): son los archivos que viven dentro de Git, no tienen cambios pendientes y sus últimas actualizaciones han sido guardadas en el repositorio gracias a los comandos
git add && git commit
.
git commit my_new_file # tracked
Inicialización de un proyecto
El primer paso es que un desarrollador se encargue de inicializar el proyecto. Esto se hace la primera vez siguiendo una serie de pasos. La forma más sencilla de hacerlo es crearnos el proyecto en nuestro servidor de Git (Docker container local/remoto, GitLab,Github, etc) y dercargarlo a nuestro sistema.
Es recomendable instalar el paquete Git Flow. Ejecutamos lo siguiente que deja los valores por defecto:
git flow init -d
git branch -m master main # Cambia si lo deseas el nombre de la rama master a main
Información
Este proceso solo es necesario realizarlo una sola vez por un miembro del equipo.
Trabajando con las ramas
Lo primero que vamos a aprender, va a ser el comando git status
, que nos va a decir el estado actual del repositorio, con los cambios que se deben hacer, eliminaciones o si todo está actualizado (up to date).
- Ver las ramas en local y remotas:
git branch -a
develop
* main
remotes/origin/HEAD -> origin/main
remotes/origin/develop
remotes/origin/main
Información
También podemos usar git branch --no-merged
para ver las ramas que contienen cambios.
- Ir a una rama:
git switch develop
* develop
main
- Para crear una rama y acceder a ella, tenenos dos opciones. Desde develop, ejecutamos una de las opciones siguientes:
# Opción 1
git branch feature/api-token && git switch feature/api-token
# Opción 2
git switch -c feature/api-token
develop
* feature/api-token
main
- Eliminar una rama:
git checkout develop && git branch -D feature/api-token # D = force delete
Información
Si estamos dentro de una rama que queremos eliminar, debemos salirnos primero de ella.
También podemos eliminar ramas remotas:
git push origin --delete feature/api-token
origin es una convención que hace mención al repositorio en remoto.
Si queremos borrar una rama con cambios en staging tal vez no nos permita eliminarla. Hay varios métodos. Yo aconsejo pasar los cambios al stash (una especie de portapapeles o memoria temporal) que "corta" todos los cambios, y eliminar la rama. El otro método es mas drástico y elimina todos los cambios que se hayan hecho en dicha rama desde que se creó:
git stash -u
git checkout develop
git branch -D nombre_de_la_rama
Comandos esenciales de Git
git clone
git clone URL_del_repositorio
git fetch
Este comando descarga el contenido remoto, pero no actualiza el estado de trabajo del repositorio local, por lo que tu trabajo actual no se verá afectado. Similar al comando svn update
.
git fetch
git fetch -a -p # para todas las ramas (all) y elimina (prune) ramas en local que ya no existen en remoto
Si deseas conocer más sobre este comando, lee el artículo: git fetch.
git add
Añadir fichero/s a la zona de staging:
git add README.md
git add .
git add -a # Todos los cambios.
git add -A # Archivos nuevos, modificados y eliminados.
git add --ignore-removal . # Nuevos y modificados.
git add -u # Modificados y eliminados.
Diferencia entre git add .
, git add -a
y git add -A
git add .
: Añade todos los archivos nuevos y modificados en el directorio actual y sus subdirectorios, pero no incluye los archivos eliminados.git add -a
: Añade todos los archivos nuevos, modificados y eliminados en el directorio actual y sus subdirectorios.git add -A
: Añade todos los archivos nuevos, modificados y eliminados en todo el repositorio, tanto en el directorio actual como en cualquier otro lugar del repositorio.
- "José, yo siempre uso git add ."
Luego comentaré sobre el uso de git add .
y por qué no es recomendable.
git stash
Guarda los cambios por un momento para traerlos nuevamente más tarde.
git stash # Es como cortar al portapapeles de Git
git stash -u # También guarda los archivos untracked.
git stash pop # Es como pegar el último stash del portapapeles de Git
git stash save "mensaje" # Guarda cambios con un mensaje
git stash list # muestra lo que tenemos en el stash
git stash pop stash@{2} # Recupera del stash con índice 2
Por defecto, los cambios en stash se convertirán en staged. Si deseas eliminarlos usa el comando git restore --staged
.
Más info de Stash en el siguiente enlace: Git stash.
git pull
Consiste en traer datos del repositorio remoto y luego mezclar los cambios con el repositorio local. Es igual que hacer git fetch && git merge origin/$CURRENT_BRANCH
git pull -p # -p=prune, elimina las referencias a ramas remotas eliminadas
git pull --force # sobreescribe la rama local actual
git push
Subir cambios locales a remoto.
git push # -p=prune, elimina las referencias a ramas remotas eliminadas
git push --force # sobreescribe la rama remota que tenga el mismo nombre
Sobre el parámetro --force_
ten mucho cuidado y úsalo solo cuando sepas exactamente lo que estás haciendo.
git rm
Eliminar ficheros marcados como tracked (rastreados o en seguimiento). Mejor que hacer directamente un rm
en la terminal.
git rm nombre_del_fichero
git merge
Caso 1: NO quieres mantener los cambios locales. Quizás modificaste un archivo para probar, pero ya no necesitas la modificación. Lo único que importa es estar al día con los cambios remotos.
git fetch
git reset --hard HEAD
git merge origin nombre_rama # suele ser develop. @{u} es lo mismo que origin $CURRENT_BRANCH
Caso 2: Te importan los cambios locales:
git fetch
git pull origin develop
# Resuelves conflictos si los hay
git log
Muestra un histórico de todas las ramas:
git log
git log --oneline --decorate --graph
git log --pretty=format:'%h - %an, %ar : %s'
Fichero .gitconfig
~/.gitconfig
o ~/.config/git/config
es donde está la configuración de git en tu equipo. Desde allí, podemos por ejemplo crear alias a listado de comandos que solamos usar para no tener que escribir todo el tiempo lo mismo.
Algunos comandos útiles:
git config --global user.name "John Doe"
git config --global user.email john_doe@dev.com
git config --global core.editor "code --wait" # VSCode como editor por defecto
git config --global -e # Abre fichero .gitconfig para ser editado
git config --global core.autocrlf [true, input] # true para Windows / input para Mac/Linux
git config -h # Ayuda sobre config
Información
Sobre core.autocrlf, si hay desarrolladores trabajando en diferentes SSOO, los caracteres especiales carriage Return (CR) y Line Feed (LF) serán añadidos por los usuarios de Windows, por lo que es encesario que ejecutes git config --global core.autocrlf true
. En macOS/linux no es necesario, pero es aconsejable asignarle el valor input para que trate automáticamente dicho valor.
# Fichero .gitconfig
[alias]
pull_force = !"git fetch --all; git reset --hard HEAD; git merge @{u}"
Ejemplo de flujo diario para trabajar con Git
Vamos a ver un ejemplo de cómo trabajar con Git en un proyecto. Imaginemos que vamos a trabajar en una nueva funcionalidad que implica autentificar una API.
Aunque mi equipo y yo trabajamos con comandos comunes de Git, os pongo también como se haría con GitFlow.
1. Crear rama feature
Creamos una rama paralela a develop donde vamos a trabajar. Generalmente usando GitFlow va a ser una rama feature/, por ejemplo:
git fetch -a -p
git switch develop # En caso de que estemos en otra rama, puedes comprobar donde estas con git branch
# Common mode
git switch -c "feature/#HM-01-auth-api"
# GitFlow mode
git flow feature start HM-01-auth-api
2. Añadir los commits
Hemos trabajado en los cambios necesarios, por lo que vamos a añadirlos al stage y luego haremos el commit:
git status # para saber qué ficheros están modificados, sin rastrear,...
git add . # o mejor los nombres de los ficheros de ese commit
git commit -m "feature/#HM-01-auth-api"
Sobre el uso de git add .
, es considerado mala práctica, ya que podemos introducir sin querer ficheros innecesarios. Además, los commits se suelen hacer según los archivos necesarios para que una lógica funcione y es más sencillo a la hora de hacer una revisión de código. Por ejemplo, si necesitamos en nuestra tarea autentificar una API, la forma correcta sería:
git add auth/api_auth.py app.py
git commit -m "feature/#HM-01-Add new class & modify routes" # Texto del mensaje en inglés y NO acaba en punto
git add readme.md
git commit -m "feature/#HM-01-Update doc" # Verbos en infinitivo: add, merge, bump, change, delete, modify, fix,...
git add test/system/api/auth/test_api_auth.py
git commit -m "feature/#HM-01-Add tests for all routes" # se aconseja que el mensaje no supere los 50 caracteres
3. Comparamos con develop
Otros desarrolladores probablemente avancen en el código, hayan hecho confirmaciones y tu rama no esté actualizada. Es necesario descargar las nuevas modificaciones de la rama develop y elegir los cambios que se quedan.
# Estamos en la rama feature/, si no nos vamos a ella
git fetch -a -p # No es necesario, pero siempre aconsejable que mantengamos las ramas actualizadas
git pull origin develop # IMPORTANTE: Esto descarga los cambios de develop remoto, NO de tu rama local develop.
Ahora pueden pasar dos cosas, que todo esté bien (pasamos al punto 4) o que haya conflictos que necesitan resolverse (merge conflict).
3.1. Merge conflict. Resolver conflictos de tu rama.
Vamos a repasar con git status
los ficheros que debemos resolver y los iremos resolviendo uno a uno. Se recomienda usar alguna aplicación gráfica o un IDE más amigable, ya que ahora nuestro código contiene trazas de la fusión que se podrían olvidar eliminar. Algunas apps para trabajar con Git recomendadas son Sourcetree, GitKraken.
4. Subir cambios al servidor git remoto
Tan solo nos queda subir los cambios para fusionarlos con develop. Hay tres modos de hacerlo: git merge
, git rebase
y de la forma gitflow.
# Common mode
# Estamos en la rama feature/. Si no, nos vamos a ella
git merge origin develop --fast-forward # Opción 1
git merge origin develop --squash # Opción 2
git rebase origin develop && git merge --fast-forward # Opción 3
# GitFlow mode
git flow feature finish HM-01-auth-api
Lo recomendable es usar git merge, aunque explico a continuación las dos opciones:
Con
git merge
combinamos los commits y hacemos un commit nuevo en develop. Si la rama develop es muy activa, esta opción puede "contaminar" bastante el historial.Con
git rebase
, ELIMINAMOS los commits de la rama feature, iguala con develop y pone commits nuevos encima. La fusión se haría congit merge --fast-forward
.Hay que tener claro que git rebase soluciona el mismo problema que git merge: están diseñados para integrar cambios de una rama a otra, pero de forma distinta. git rebase se utiliza principalmente para que en el histórico de Git no se vean las ramas que no sean main y develop del equipo de desarrollo (features, hotfix), ya que puede que no interesen dichas ramas paralelas en el histórico o si hay muchos commits y se quieren reordenar (5 commits pueden fusionarse en 2 por ejemplo), tendríamos la opción de usar un rebase interactivo con
git rebase -i
. Es todo cuestión de estética cuando lancemos elgit log
.Otra regla de oro de git rebase es NO USARLO JAMÁS EN RAMAS PÚBLICAS.
Más información en atlassian.com > merging-vs-rebasing
Tips y otros comandos
eliminar de track un fichero
git rm --cached nombre_fichero
Solucionar un bug en main
Para ello, se crea en local una rama hotfix/xxx
para solucionar el problema y se mergea en main
. En caso de que todo esté correcto, debemos fusionarlo también con develop
. Usando el estilo GitFlow, ejecuta el siguiente comando:
git flow hotfix start HM-01-fix-bug
# Haz tu magia
git flow hotfix finish HM-01-fix-bug
Obtener cambios remotos sin mantener los locales
Deseas eliminar todos los cambios locales uncommitted. Quizás modificaste un archivo para probar, pero ya no necesitas la modificación. Lo único que importa es estar al día con la rama remota.
git fetch -a
git stash -u # Opcional aunque aconsejable
git reset --hard HEAD
git merge --no-ff origin develop # O como alternativa podemos hacer git pull
Eliminar mis cambios
En caso de querer volver al estado anterior:
git reset --soft # Mantiene cambios
git reset --hard # Elimina incluso tu código. Usar con precaucción
Ir a un estado anterior a través de un ID sin mantener los cambios
git log # Vemos el Id del commit al que queremos volver
git reset --hard 23j45b3b45hb345b