🔎 Buscar

🌿 Git y open source

Git a fondo: el modelo de grafo, objetos y refs, el flujo diario, branchs, merge y rebase, remotes, reflog, flujos de colaboración y contribución a open source. Teoría, comandos y práctica.

Fundamentos CS📖 Contenido

Git y open source

Git es, junto al sistema operativo y el editor, la herramienta más transversal del ingeniero. La clave para dominarlo no es memorizar comandos: es entender el modelo de datos. Cuando entiendes que un commit es un snapshot enlazado en un grafo y que el reflog te permite volver de cualquier desastre, dejas de tener miedo al rebase.

📌 El modelo mental que lo cambia todo

Git no guarda diferencias de archivos: guarda snapshots (fotos) del estado completo del proyecto. Cada snapshot (commit) apunta a su padre. Un branch es solo una etiqueta que apunta a un commit. Ese es TODO el modelo: objetos + referencias.

El modelo de grafo

Todo en Git es un grafo de commits. Cada commit tiene:

  • El snapshot (los archivos en ese momento).
  • Un puntero a su commit padre (o padres, si es un merge).
  • Metadatos: autor, mensaje, timestamp.
  • Un hash (SHA-1) que lo identifica de forma única.
   C1 ──── C2 ──── C3 ──── C4        ← main
                 \
                  └── C5 ──── C6     ← feature
C1 = primer commit
C2 = "añade login" (padre: C1)
C5 = "añade carrito" (padre: C2, en otra rama)

💡 El hash es el commit: si dos commits tienen el mismo contenido y metadatos, tienen el mismo hash. Por eso Git detecta si algo se ha corrompido o si dos personas hicieron el mismo cambio. Y por eso nunca edites historia que ya compartiste: cambias los hashes y todos los que la tenían se rompen.

Los objetos internos

Git guarda todo en .git/objects/ como cuatro tipos de objetos:

Objeto Qué es
Blob El contenido de un archivo (sin nombre)
Tree Un directorio: lista de blobs y sub-trees con sus nombres
Commit El snapshot: apunta a un tree, a su padre, y tiene metadatos
Tag Una etiqueta a un commit (anotada o simple)
commit (9fceb2...)             tree (a1b2c3...)
│  tree → a1b2c3...            ├── src/
│  parent → 2fe31a...          ├── README.md  → blob (d4f7e1...)
│  autor: Ana                  └── config.yml → blob (0a9b8c...)

⚠️ Concepto clave: los blobs se guardan una sola vez aunque aparezcan en muchos commits. Si dos commits tienen el mismo README.md, comparten el mismo blob. Así Git no duplica contenido — esa es la magia del almacenamiento eficiente.

Referencias (refs)

Un branch es una etiqueta movible que apunta a un commit. HEAD es la referencia especial que indica dónde estás.

HEAD → main → 9fceb2 (último commit de main)
git branch              # lista de ramas (* = la actual)
git branch feature      # crea una rama apuntando a HEAD
git switch feature      # muévete a feature (HEAD → feature)
git switch -c nueva     # crear y moverte a la vez
git log --oneline       # historia como lista
git log --graph --all   # el grafo visual

El flujo diario: add, commit, branch, merge

El staging area (índice)

Git tiene tres estados para cada archivo:

Working dir ──git add──▶ Staging (index) ──git commit──▶ Repositorio
  (modificado)              (listo para commit)           (guardado)
git status                 # ver el estado de todo
git add archivo.txt        # mover al staging
git add .                  # todo lo modificado
git add -p                 # fragmento a fragmento (arte avanzado)
git commit -m "mensaje"    # guardar el snapshot
git commit -am "msg"       # add + commit de archivos ya rastreados
git diff                   # cambios sin stagear
git diff --staged          # cambios ya stageados

💡 Commits atómicos: un commit debe ser una unidad lógica (“añade login”), no “cambios del viernes”. Con git add -p seleccionas qué fragmentos entran en cada commit.

Merge: unir ramas

git switch main
git merge feature          # incorpora feature en main
Antes:   main:    C1 ── C2
         feature:      └── C3 ── C4

Después: main:    C1 ── C2 ── C3 ── C4     (fast-forward)
         o
         main:    C1 ── C2 ────── M
         feature:      └── C3 ── C4 ─┘     (merge commit, sin ff)
  • Fast-forward: si main no avanzó, Git solo mueve la etiqueta (historia lineal).
  • Merge commit: si ambas ramas avanzaron, Git crea un commit de fusión con dos padres.
  • Conflicto: si las dos ramas tocaron las mismas líneas. Lo resuelves a mano y luego git add + git commit.
git merge feature
# CONFLICTO en archivo.txt
# → editas archivo.txt dejando lo correcto
git add archivo.txt
git commit                # cierra el merge

⚠️ Los conflictos no son malos: son Git pidiéndote que decidas. Cuanto más pequeños los commits y más seguido merges/mrebases, menos conflictos.

Rebase: historia lineal

El rebase reescribe la historia: toma tus commits y los re-planta sobre otro punto.

git switch feature
git rebase main           # feature se reconstruye encima de main
Antes:  main:    C1 ── C2
        feature:      └── C3 ── C4

Después (rebase): main:    C1 ── C2
                 feature:         └── C3' ── C4'

💡 Regla de oro: rebase para historia local (no compartida), merge para integrar ramas compartidas. Rebasear lo que ya pusiste en GitHub reescribe historia pública y rompe a los demás.

Rebase interactivo: limpiar historia

git rebase -i HEAD~3       # reescribe los últimos 3 commits
# pick → keep, squash → fundir con el anterior, reword → renombrar

Remotes y colaboración

Un remote es una copia remota del repo (GitHub, GitLab, tu servidor). Tu repo local y el remoto son repos independientes que se sincronizan.

git remote add origin https://github.com/usuario/proyecto.git
git remote -v              # ver remotes

git push origin main       # sube tus commits
git pull origin main       # baja y mergea (fetch + merge)
git fetch origin           # solo baja, sin mergear
git clone url              # copia un repo remoto

El flujo de Pull Request

1. git switch -c feature          # rama nueva
2. ... trabajo ... commits ...
3. git push origin feature        # sube la rama
4. → en GitHub: creas un Pull Request (PR)
5. → code review → ajustes → aprobación
6. → merge del PR
7. git switch main && git pull    # actualizas tu main

💡 Pull request es una petición de revisión: no es solo “código nuevo”, es una conversación (código + comentarios + discusión). La revisión de código (code review) es el mecanismo de calidad #1 del open source y de los equipos.

El reflog: tu red de seguridad

El reflog es el historial de dónde han estado tus refs. Aunque borres una rama o hagas un hard reset, Git recuerda por dónde pasó HEAD — y puedes volver.

git reflog                  # lista: "9fceb2 HEAD@{0}: commit: añade login"
git reset --hard HEAD@{2}   # volver a un estado anterior
git reflog --oneline

💡 Regla de seguridad: casi nada en Git es irrecuperable mientras exista el reflog (y lo que borraste sigue en objetos hasta que el GC los limpie). Antes de entrar en pánico, mira el reflog.

Comandos de emergencia

Situación Comando
Deshacer un commit (manteniendo cambios) git reset --soft HEAD~1
Deshacer un commit (borrando cambios) git reset --hard HEAD~1
Arreglar el último commit git commit --amend
Cambios sin commit, quiero borrarlos git checkout -- archivo
Mover trabajo sin commit a otra rama git stash / git stash pop
Encontrar el commit que rompió algo git bisect
Ver quién tocó cada línea git blame archivo
Ver commits de una rama que faltan en main git log main..feature
Dejar de trackear un archivo git rm --cached archivo
# git bisect: búsqueda binaria del commit culpable
git bisect start
git bisect bad              # el actual está roto
git bisect good <hash>      # este commit estaba bien
# → Git te va moviendo a la mitad hasta aislar el culpable

Hooks y automatización

Los hooks son scripts que Git ejecuta en momentos del flujo (client y server).

# .git/hooks/pre-commit  — corre ANTES de cada commit
#!/bin/sh
if grep -n "TODO" src/; then
    echo "No se permite TODO en el código"; exit 1
fi
Hook Momento
pre-commit Antes de commitear (lint, formato, secretos)
commit-msg Validar el mensaje del commit
pre-push Antes de subir (tests, build)
post-merge Después de un merge (npm install, migraciones)

💡 La forma moderna: pre-commit framework (.pre-commit-config.yaml) que gestiona hooks de forma compartida entre el equipo, o herramientas como husky (JS).

Flujos de trabajo (workflows)

Workflow Cómo funciona Cuándo
GitHub Flow Rama corta por feature → PR → merge a main. Simple. El estándar moderno (entrega continua)
GitFlow main + develop + feature/release/hotfix Releases versionados (legacy, software empaquetado)
Trunk-based Todos trabajan en main, ramas de menos de 1 día, feature flags Equipos con CI fuerte, despliegue continuo

💡 Recomendación: GitHub Flow / trunk-based. Las ramas cortas (1-2 días), PRs pequeños y CI verde son la práctica moderna. GitFlow solo tiene sentido para software con releases versionados.

Contribuir a open source

El flujo real de contribución (por email o PR):

1. Fork del repo (copia en tu cuenta)
2. git clone tu-fork
3. git checkout -b mi-cambio
4. ... cambios + commits ...
5. git push origin mi-cambio
6. PR al repo original (upstream)
7. Discusión + revisiones → merge
git remote add upstream https://github.com/original/repo.git
git fetch upstream
git checkout main
git merge upstream/main       # mantener tu fork al día

⚠️ Etiqueta del open source: lee el CONTRIBUTING.md, abre un issue antes de cambios grandes, sigue los convenios del proyecto, responde a los reviews, y usa el canal correcto (issues vs PRs vs discussions).

Licencias

Cuando creas o usas open source, la licencia define qué pueden hacer otros con tu código.

Licencia Permite Obliga
MIT Uso comercial, modificar, distribuir Mantener el aviso de copyright
Apache 2.0 Igual + patentes Aviso + indicar cambios
GPL Uso, modificar, distribuir Copyleft: derivados también GPL
BSD Igual que MIT Aviso
AGPL Igual que GPL Copyleft incluso vía red

💡 MIT/Apache = permiso de usar sin casi condiciones. GPL/AGPL = copyleft (lo que deriva también debe ser libre). Elige con choosealicense.com.

Cheatsheet

git status / git log --oneline --graph
git add -p && git commit -m "msg"
git switch -c rama && git switch main
git merge rama / git rebase main
git push origin rama / git pull
git fetch && git diff main..origin/main
git stash / git stash pop
git reflog && git reset --hard HEAD@{n}
git bisect start
git blame archivo

Práctica propuesta

  1. Crea un repo, haz 5 commits, crea una rama, haz un merge y luego un rebase. Compara los grafos.
  2. Provoca un conflicto a propósito (dos ramas editando la misma línea) y resuélvelo.
  3. Haz git reset --hard a un commit anterior y recupérate con el reflog.
  4. Reescribe la historia de tus últimos 3 commits con git rebase -i (squash + reword).
  5. Completa Learn Git Branching completo (incluye niveles de remote).
  6. Haz tu primer PR real a un proyecto de first-contributions.
  7. Busca el commit que introdujo un bug en un repo tuyo con git bisect.

Para profundizar

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.