Git es la herramienta de control de versiones más usada del mundo. Está en prácticamente todos los proyectos de software, y dominarlo no es opcional si quieres trabajar en desarrollo profesional. Sin embargo, muchos desarrolladores usan Git a nivel superficial durante años sin entender realmente lo que está pasando por debajo.
Esta guía no es una introducción básica. Es una guía que te explica cómo funciona Git de verdad, qué significa cada operación a nivel conceptual, y cómo usar las funciones más importantes para trabajar de forma profesional en equipos reales.
En este artículo:
- Cómo funciona Git por dentro
- Las tres áreas de trabajo en Git
- Cómo hacer commits que realmente comuniquen algo
- Ramas: la característica más poderosa de Git
- Merge vs Rebase: cuándo usar cada uno
- Trabajar con repositorios remotos
- Flujos de trabajo en equipos profesionales
- Comandos que te salvarán la vida
Cómo funciona Git por dentro
La mayoría de herramientas de control de versiones guardan los cambios como diferencias (deltas): qué líneas se añadieron y cuáles se eliminaron en cada versión. Git funciona de forma diferente. Git guarda instantáneas completas del estado de todos tus archivos en cada commit. Eso lo hace más rápido y más confiable, aunque ocupe algo más de espacio (que compensa con compresión eficiente).
Internamente, Git almacena todo en una base de datos de objetos. Hay cuatro tipos de objetos: blobs (contenido de archivos), trees (estructura de directorios), commits (instantáneas con metadatos) y tags (etiquetas apuntando a commits). Cada objeto se identifica por su hash SHA-1, una cadena hexadecimal de 40 caracteres que es única para ese contenido.
Un commit es básicamente un puntero a un tree (el estado de tu directorio en ese momento), más metadatos: el autor, la fecha, el mensaje y el hash del commit padre. Esto crea una cadena de commits donde cada uno apunta a su antecesor, formando la historia del proyecto.
Las ramas en Git son simplemente punteros a commits. No son copias del código ni carpetas separadas. Una rama es un archivo de texto que contiene el hash del último commit de esa rama. Crear una rama es instantáneo y casi no ocupa espacio. Esto es lo que hace que las ramas en Git sean tan ligeras comparadas con otros sistemas.
Las tres áreas de trabajo en Git
Para entender cómo funciona Git en el día a día, es fundamental tener claro sus tres áreas de trabajo. Mucha confusión viene de no tener esto claro:
Working directory (directorio de trabajo): es simplemente tu sistema de archivos, lo que ves en tu editor. Cuando modificas un archivo, ese cambio existe en el working directory pero Git aún no lo está rastreando como parte del próximo commit.
Staging area (área de preparación o índice): es una zona intermedia única de Git. Cuando ejecutas git add, los cambios pasan del working directory al staging area. Esto te permite preparar exactamente qué cambios quieres incluir en el próximo commit, aunque hayas modificado muchos archivos diferentes.
Repository (.git/): la base de datos interna de Git donde se guardan todos los commits, ramas y objetos. Cuando ejecutas git commit, los cambios del staging area se guardan permanentemente en el repositorio como un nuevo commit.
# Ver el estado de las tres áreas
git status
# Añadir al staging area
git add archivo.js # un archivo específico
git add src/ # un directorio completo
git add . # todos los cambios
# Añadir partes específicas de un archivo (modo interactivo)
git add -p archivo.js # te pregunta qué partes añadir
# Hacer el commit con los cambios del staging area
git commit -m "mensaje del commit"
# Ver diferencias entre áreas
git diff # working directory vs staging
git diff --staged # staging vs último commit
Cómo hacer commits que realmente comuniquen algo
Un commit no es solo guardar tu trabajo. Es un mensaje para tu yo del futuro y para tu equipo explicando qué cambió y por qué. Hay una convención ampliamente adoptada en proyectos profesionales llamada Conventional Commits que estructura los mensajes de commit de forma consistente.
El formato básico es: tipo(ámbito): descripción. Los tipos más comunes son feat (nueva funcionalidad), fix (corrección de bug), docs (documentación), refactor (cambio de código sin cambiar funcionalidad), test (añadir o modificar tests) y chore (tareas de mantenimiento).
# Ejemplos de buenos mensajes de commit
feat(auth): añadir autenticación con JWT
fix(carrito): corregir cálculo incorrecto del total con descuentos
docs(api): actualizar documentación de endpoints de pagos
refactor(usuario): extraer validación a servicio separado
test(producto): añadir tests para filtrado por categoría
# Commit con cuerpo y footer (para cambios importantes)
git commit -m "feat(auth): implementar refresh tokens automáticos
Los tokens de acceso ahora se renuevan automáticamente cuando
están a punto de expirar, sin necesidad de que el usuario vuelva
a hacer login.
Closes #342"
Un buen commit hace una sola cosa. Si en un commit tienes cambios en el sistema de pagos, en el módulo de usuarios y en el de notificaciones, ese commit es demasiado grande. Divide los cambios en commits más pequeños y atómicos. Esto facilita enormemente el review de código, la búsqueda de bugs con git bisect y entender la historia del proyecto.
Ramas: la característica más poderosa de Git
Las ramas permiten trabajar en diferentes características o fixes de forma aislada, sin afectar al código principal. Cuando terminas, fusionas tu rama de vuelta al código principal. Este flujo de trabajo es la base de prácticamente todos los proyectos de software profesionales.
# Crear y cambiar a una nueva rama
git checkout -b feature/sistema-de-notificaciones
# Equivalente moderno (Git >= 2.23)
git switch -c feature/sistema-de-notificaciones
# Ver todas las ramas
git branch # locales
git branch -r # remotas
git branch -a # todas
# Cambiar de rama
git checkout main
git switch main # versión moderna
# Eliminar una rama (después de fusionarla)
git branch -d feature/sistema-de-notificaciones
# Forzar eliminación aunque no esté fusionada
git branch -D feature/experimento-fallido
Una buena práctica es mantener la rama main (o master) siempre en un estado deployable. Nunca desarrolles directamente en main. Crea una rama para cada feature, bug fix o experimento, trabaja ahí, y cuando esté listo y revisado, fusiónalo a main.
Merge vs Rebase: cuándo usar cada uno
Cuando quieres integrar los cambios de una rama en otra, tienes dos opciones principales: merge y rebase. Ambas producen el mismo resultado final en el código, pero crean historiales de commits muy diferentes.
Merge: crea un nuevo commit de fusión que une los historiales de ambas ramas. Preserva el historial exactamente como ocurrió, incluyendo cuándo se creó cada rama y cuándo se fusionó. El historial puede verse algo complejo con muchas ramas entrecruzadas, pero es 100% fiel a la realidad.
# Fusionar feature en main con merge
git checkout main
git merge feature/login
# Si hay conflictos, resuélvelos y luego:
git add archivo-con-conflicto.js
git commit # confirma el merge
Rebase: reescribe el historial de commits de tu rama para que parezca que empezaste a trabajar desde el último commit de la rama destino. El resultado es un historial lineal y limpio, sin commits de merge. La desventaja es que cambia los hashes de los commits, lo que puede causar problemas si otros ya están trabajando sobre esa rama.
# Hacer rebase de tu feature branch sobre main actualizado
git checkout feature/login
git rebase main
# Si hay conflictos durante el rebase:
git add archivo-resuelto.js
git rebase --continue
# Si te arrepientes:
git rebase --abort
La regla de oro del rebase: nunca hagas rebase de ramas que estén en el repositorio remoto y que otras personas ya estén usando. El rebase reescribe la historia, y si alguien más tiene los commits originales, crear conflictos al intentar fusionar es prácticamente inevitable. El rebase es seguro cuando lo usas solo en tu rama local antes de hacer push por primera vez.
Trabajar con repositorios remotos
Un repositorio remoto es una copia del repositorio en un servidor, normalmente GitHub, GitLab o Bitbucket. Permite colaborar con otros desarrolladores y actúa como copia de seguridad centralizada.
# Clonar un repositorio remoto
git clone https://github.com/usuario/proyecto.git
# Ver los remotos configurados
git remote -v
# Añadir un remoto
git remote add origin https://github.com/usuario/proyecto.git
# Subir cambios al remoto
git push origin main
git push origin feature/nueva-funcionalidad
# Primera vez que subes una rama nueva
git push -u origin feature/nueva-funcionalidad
# -u configura el tracking para futuros git push y git pull
# Traer cambios del remoto (sin fusionar)
git fetch origin
# Traer y fusionar (fetch + merge)
git pull origin main
# Traer y rebasar (fetch + rebase) — más limpio
git pull --rebase origin main
Cuando hay conflictos al hacer pull, Git te lo indicará y pausará el proceso. Tienes que abrir los archivos con conflictos, resolverlos manualmente (Git marca las secciones en conflicto con <<<<<<<, ======= y >>>>>>>), añadirlos al staging area y confirmar el merge o continuar el rebase.
Flujos de trabajo en equipos profesionales
Existen varios flujos de trabajo (workflows) estándar para gestionar ramas en equipos. Los más comunes son:
GitHub Flow: el más simple y el más popular actualmente. Tienes una única rama principal (main) y creas ramas de corta duración para cada cambio. Cuando terminas, abres un Pull Request, alguien lo revisa y lo fusiona directamente a main. Ideal para proyectos con despliegue continuo.
Gitflow: más estructurado, con ramas permanentes main y develop, y ramas de feature, release y hotfix. Fue muy popular para proyectos con ciclos de releases definidos, pero se ha vuelto excesivamente complejo para muchos equipos modernos. Hoy en día GitHub Flow suele ser la mejor opción.
El flujo típico en GitHub Flow es: actualizar main con git pull, crear una rama con nombre descriptivo (feature/, fix/, hotfix/), hacer los commits, abrir un Pull Request describiendo los cambios, recibir feedback del equipo, hacer ajustes si es necesario, obtener aprobación y fusionar a main.
Comandos que te salvarán la vida
Estos son los comandos que todo desarrollador debería conocer para situaciones de emergencia:
# Guardar cambios temporalmente sin hacer commit (útil para cambiar de rama)
git stash
git stash pop # recuperar el último stash
git stash list # ver todos los stashes
git stash pop stash@{2} # recuperar un stash específico
# Deshacer el último commit (mantiene los cambios en working directory)
git reset HEAD~1
# Deshacer completamente el último commit (¡borra los cambios!)
git reset --hard HEAD~1
# Modificar el último commit (mensaje o añadir archivos olvidados)
git commit --amend --no-edit # solo añade el staging actual
git commit --amend -m "nuevo mensaje"
# Buscar qué commit introdujo un bug (búsqueda binaria)
git bisect start
git bisect bad # el commit actual tiene el bug
git bisect good v1.0.0 # este commit era correcto
# Git irá mostrando commits para que los marques como good/bad
# Ver quién cambió cada línea de un archivo
git blame archivo.js
# Buscar en todo el historial un texto específico
git log -S "función_buscada" --source --all
# Recuperar un archivo eliminado
git checkout HEAD -- archivo-eliminado.js
# Aplicar un commit específico de otra rama a la rama actual
git cherry-pick abc1234
Git tiene una curva de aprendizaje real, pero una vez que entiendes cómo funciona internamente y el modelo mental detrás de las ramas y el historial, todo encaja. La clave es practicar en proyectos reales. Cada error que cometas y resuelvas en Git te enseñará más que cualquier tutorial.
Si estás empezando con el desarrollo web, te recomiendo también leer nuestra guía sobre qué es Git y para qué sirve para tener el contexto inicial antes de profundizar en los temas avanzados de esta guía. Y si ya dominas Git, el siguiente paso es aprender a integrar tu flujo de Git con un proyecto Angular + Spring Boot en un entorno de equipo real.