Skip to content

Git Hoja de referencia

Sistema de control de versiones distribuido para rastrear cambios en el código fuente.

01

Configuración e Inicialización

Configuración Global y Local

Git almacena la configuración en tres niveles: sistema (/etc/gitconfig), global (~/.gitconfig para el usuario) y local (.git/config por repositorio). Los niveles inferiores anulan los superiores. Siempre establezca user.name y user.email antes de hacer commits, de lo contrario los commits usarán una identidad predeterminada poco útil. Establecer init.defaultBranch a main evita el valor predeterminado obsoleto master y se alinea con las convenciones modernas.

git
# set user identity (required for commits)
git config --global user.name "Alice Lee"
git config --global user.email "[email protected]"

# set default editor and branch name
git config --global core.editor "code --wait"
git config --global init.defaultBranch main

# view all settings and their origin
git config --list --show-origin

# edit config files directly
git config --global --edit        # ~/.gitconfig
git config --edit                 # repo .git/config

Crear y Clonar Repositorios

git init crea un repositorio vacío añadiendo un directorio oculto .git que almacena todos los datos de versión. git clone copia un repositorio remoto incluyendo su historial completo. Use --depth 1 para un clon superficial cuando solo necesite la última instantánea (p. ej. compilaciones de CI) — reduce drásticamente el tamaño de descarga. --single-branch evita obtener ramas no relacionadas.

git
# create a new repo from scratch in current dir
mkdir my-app && cd my-app
git init

# clone a remote repository
git clone https://github.com/user/repo.git

# clone into a specific folder name
git clone https://github.com/user/repo.git my-folder

# clone a single branch (saves bandwidth)
git clone -b dev --single-branch https://github.com/user/repo.git

# shallow clone: only the latest commit
git clone --depth 1 https://github.com/user/repo.git

Alias y Atajos

Los alias le permiten definir nombres más cortos para comandos usados frecuentemente o secuencias de comandos. Se almacenan en la sección [alias] de su gitconfig. Un alias que comienza con ! se ejecuta como un comando de shell, permitiendo flujos de trabajo complejos. El alias lg anterior produce un gráfico compacto de historial visual que es extremadamente útil para entender la estructura de ramas.

git
# create shortcuts for common commands
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.lg "log --oneline --graph --all"

# now use them
git co main
git lg

# alias that runs an external command (starts with !)
git config --global alias.unstage "reset HEAD --"
git unstage file.txt

Ayuda y Documentación

Git viene con documentación integrada completa. git help <comando> abre la página man en su paginador. El flag -h da un resumen rápido de una pantalla de opciones. Las guías (git help -g) incluyen un tutorial, un glosario de términos y una referencia de flujo de trabajo diario — excelentes para principiantes que aprenden la terminología.

git
# open the full manual for a command
git help commit
git commit --help

# show a concise synopsis
git commit -h

# list all git commands
git help -a

# list all guides (tutorial, glossary, etc.)
git help -g
git help glossary

Patrones .gitignore

.gitignore le dice a Git qué archivos excluir del control de versiones — esencial para artefactos de compilación, dependencias y secretos. Los patrones usan sintaxis glob; una barra diagonal final coincide con directorios. Prefijar con ! niega un patrón, forzando que un archivo sea rastreado. El propio .gitignore debería ser commiteado para que el equipo comparta las reglas de ignorar. Los archivos ya rastreados no se ven afectados por nuevos patrones de ignorar — elimínelos con git rm --cached primero.

git
# .gitignore file — patterns for files to skip
node_modules/
*.log
.env
.env.local
dist/
build/

# but track this specific file even if it matches
!important.log

# ignore all .txt files in build/ but not subdirs
build/*.txt

# ignore everything in a folder except one file
secrets/*
!secrets/template.env
02

Staging y Committing

Estado y Staging

Git usa un modelo de dos pasos: los cambios van al área de staging (índice) antes de ser commiteados. git status muestra qué está modificado, en staging y sin rastrear. git add -p le permite hacer staging de fragmentos individuales de un parche — invaluable para dividir un árbol de trabajo desordenado en commits enfocados y lógicos. Use git restore --staged (el comando moderno) para quitar del staging sin perder sus ediciones.

git
# see current state of working tree
git status
git status -s              # short format
git status -sb             # short + branch info

# stage changes
git add file.txt           # one file
git add src/               # a directory
git add .                  # all changes in repo
git add -p                 # stage interactively by hunk

# unstage a file (keep changes in working dir)
git restore --staged file.txt
git reset HEAD file.txt    # older syntax

Commitear Cambios

Un commit registra una instantánea de los cambios en staging. Escriba mensajes en modo imperativo ('add feature' no 'added feature'). El flag -a solo hace staging de archivos ya rastreados — los archivos nuevos todavía necesitan git add. --amend reescribe el último commit; úselo para corregir un error tipográfico o añadir un archivo olvidado, pero nunca modifique commits que ya haya empujado a una rama compartida.

git
# commit staged changes with a message
git commit -m "feat: add login form"

# multi-line commit message
git commit -m "feat: add login form" -m "Closes #42"

# stage tracked files AND commit in one step
git commit -am "fix: correct typo"

# amend the previous commit (keep message)
git commit --amend --no-edit

# amend and edit the message
git commit --amend -m "feat: add login form (v2)"

Convenciones de Mensajes de Commit

Conventional Commits es una especificación ampliamente adoptada para mensajes de commit estructurados. El prefijo de tipo (feat, fix, docs, etc.) permite la generación automatizada de changelogs y versionado semántico. El marcador ! indica un cambio disruptivo. Una línea en blanco separa el asunto (<=50 caracteres) del cuerpo, y otra línea en blanco separa pies como 'Closes #123' que cierran automáticamente issues en GitHub.

git
# Conventional Commits format
feat: add user registration endpoint
fix: resolve crash on empty cart
docs: update API reference
style: format login component
refactor: extract validation logic
test: add unit tests for parser
chore: upgrade dependencies

# with scope and breaking change marker
feat(api): add pagination to list endpoint
fix!: change default port (BREAKING CHANGE)

# with body and footer
feat: add dark mode

Implement theme toggle using CSS variables.
Closes #128

Ver el Historial con log

git log es la herramienta principal para explorar el historial. --oneline --graph --all es la combinación más útil para entender la estructura de ramas de un vistazo. Puede filtrar por autor, rango de fechas o contenido del mensaje con --grep. --stat muestra qué archivos cambiaron y cuántas líneas, mientras que --patch muestra el diff completo de cada commit.

git
# full history with details
git log
git log --oneline                 # one line per commit
git log --oneline --graph --all   # visual branch graph
git log -n 5                      # last 5 commits

# filter by author, date, or message
git log --author="Alice"
git log --since="2 weeks ago"
git log --grep="fix"

# show files changed in each commit
git log --stat
git log --patch                   # full diffs

Diff y Show

git diff compara instantáneas — sin argumentos muestra los cambios no staged contra el índice. --staged compara el índice contra HEAD. git show muestra los metadatos de un único commit y su parche. La sintaxis HEAD:path le permite ver el contenido de cualquier archivo en cualquier commit sin checkearlo, lo cual es útil para recuperar versiones antiguas o inspeccionar el historial.

git
# compare working tree, index, and commits
git diff                  # unstaged changes
git diff --staged         # staged but uncommitted
git diff HEAD             # all changes vs last commit
git diff main feature     # compare two branches
git diff abc1234 def5678  # compare two commits

# inspect a single commit
git show HEAD             # latest commit diff + metadata
git show abc1234
git show HEAD:file.txt    # view a file at a given commit
03

Ramificación y Fusión

Crear y Cambiar de Rama

Las ramas en Git son punteros ligeros a un commit — crear una es casi instantáneo. git switch (Git 2.23+) es la alternativa moderna y más segura a checkout para cambiar de rama, reservando checkout para restaurar archivos. -d se niega a eliminar una rama no fusionada (protegiendo su trabajo); -D fuerza la eliminación. Siempre elimine las ramas después de fusionar para mantener la lista de ramas limpia.

git
# list branches
git branch                 # local branches
git branch -a              # local + remote
git branch -vv             # with tracking info

# create and switch
git branch feature         # create only
git checkout feature       # switch to it
git checkout -b feature    # create + switch (classic)
git switch -c feature      # create + switch (modern)

# delete branches
git branch -d feature      # safe delete (merged only)
git branch -D feature      # force delete

Fusionar Ramas

Una fusión fast-forward simplemente mueve el puntero de la rama hacia adelante cuando el objetivo no tiene nuevos commits — produciendo historial lineal. --no-ff fuerza un commit de fusión, preservando el hecho de que existió una rama (útil para seguimiento de features). --squash combina todos los commits de la rama en un único cambio en staging que luego commitea una vez — excelente para limpiar un historial de feature ruidoso antes de integrarlo en main.

git
# merge feature into main
git checkout main
git merge feature

# fast-forward merge (default when possible)
#   main just moves forward to feature's commit

# create a merge commit (preserves branch history)
git merge --no-ff feature -m "Merge feature branch"

# squash all feature commits into one
git merge --squash feature
git commit -m "feat: add feature (squashed)"

# abort a merge with conflicts
git merge --abort

Resolver Conflictos de Fusión

Los conflictos ocurren cuando las mismas líneas se cambian de manera diferente en dos ramas. Git inserta marcadores de conflicto (<<<<<<<, =======, >>>>>>>) mostrando ambos lados. Resuelva editando el archivo al estado final deseado, luego git add para marcarlo como resuelto. Haga commit para completar la fusión. git mergetool lanza una herramienta visual de diff. Si está abrumado, git merge --abort regresa al estado previo a la fusión.

git
# a conflict produces markers in the file
# <<<<<<< HEAD
# my changes (current branch)
# =======
# their changes (incoming branch)
# >>>>>>> feature

# edit the file to resolve, then:
git add resolved-file.txt
git commit                # finalize the merge

# use a merge tool
git mergetool

# see which files conflict
git status

# abandon the merge
git merge --abort

Rebasing

Rebase reproduce los commits de su rama encima de otra rama, produciendo historial lineal sin commits de fusión. El rebase interactivo (-i) es una herramienta potente: squash fusiona commits, reword edita mensajes, drop elimina commits, edit pausa para dejarle modificar un commit. NUNCA haga rebase de commits que hayan sido empujados y compartidos — reescribe el historial y rompe los repositorios de los compañeros. Use rebase solo en sus propias ramas locales.

git
# rebase feature onto main (replay feature commits)
git checkout feature
git rebase main

# interactive rebase — rewrite last 5 commits
git rebase -i HEAD~5
# options: pick, reword, squash, fixup, drop, edit

# continue, skip, or abort during a rebase
git rebase --continue
git rebase --skip
git rebase --abort

# rebase while pulling
git pull --rebase origin main

Cherry-Picking y Reflog

Cherry-pick aplica un commit individual de otra rama en su rama actual — útil para backportear un bugfix sin fusionar toda la rama. El reflog es un registro local de cada movimiento de HEAD (commits, checkouts, resets) mantenido durante ~90 días. Es su red de seguridad: incluso después de un reset destructivo, puede encontrar el hash del commit antiguo en el reflog y recuperarlo. Los datos del reflog son solo locales y nunca se empujan.

git
# apply a specific commit onto current branch
git cherry-pick abc1234
git cherry-pick abc1234 def5678   # multiple commits
git cherry-pick abc1234..def5678  # range (exclusive start)

# reflog: record of where HEAD has been
git reflog
git reflog show feature

# recover a 'lost' commit
git reset --hard HEAD@{2}

# view a branch's history of moves
git reflog show main
04

Repositorios Remotos

Gestionar Remotos

Un remoto es una referencia con nombre a otro repositorio, típicamente origin para su fork y upstream para el proyecto original. git remote -v muestra las URLs de fetch y push. Los forks en GitHub usan el remoto upstream para sincronizar con el original: haga fetch desde upstream, merge o rebase, luego push a origin. set-url es útil para cambiar entre autenticación HTTPS y SSH.

git
# list configured remotes
git remote -v
git remote show origin

# add a remote
git remote add origin https://github.com/user/repo.git

# add an upstream remote (for forks)
git remote add upstream https://github.com/original/repo.git

# rename or remove
git remote rename origin upstream
git remote remove origin

# change a remote URL (e.g. HTTPS to SSH)
git remote set-url origin [email protected]:user/repo.git

Fetch, Pull y Push

fetch descarga datos remotos pero deja su árbol de trabajo intacto — seguro para inspeccionar antes de fusionar. pull = fetch + merge (o rebase con --rebase). El primer push de una rama nueva necesita -u para configurar el tracking para que futuros git push/pull funcionen sin argumentos. --force-with-lease es la alternativa segura a --force: solo sobrescribe el remoto si nadie más ha empujado mientras tanto, previniendo el pisoteo accidental del trabajo de los compañeros.

git
# download remote changes without merging
git fetch origin
git fetch --all --prune      # all remotes, drop deleted branches

# fetch + merge in one step
git pull
git pull --rebase            # rebase instead of merge

# push local commits to remote
git push origin main
git push -u origin feature   # push + set upstream tracking
git push                     # subsequent pushes (uses tracking)

# force push (rewrites remote history — DANGER)
git push --force-with-lease  # safer than --force

Tracking y Sincronización de Ramas

Las ramas de tracking enlazan una rama local a una remota para que git pull y git push sepan dónde hacer fetch/push sin especificar. -u (abreviatura de --set-upstream-to) establece esto en el primer push. Cuando los compañeros eliminan ramas remotas, sus referencias locales de remote-tracking se vuelven obsoletas — git remote prune origin las limpia. git fetch --prune hace esto automáticamente durante el fetch.

git
# set upstream for current branch
git branch -u origin/main
git branch --set-upstream-to=origin/main

# create a local branch tracking a remote one
git checkout -b feature origin/feature
git switch feature           # auto-detects tracking branch

# delete a remote branch
git push origin --delete feature

# list all remote-tracking branches
git branch -r
git remote prune origin      # remove stale remote refs

Flujo de Trabajo de Pull Requests

El flujo estándar de GitHub: cree una rama de feature, empújela, abra un Pull Request para revisión y elimine la rama después de fusionar. Para forks, el remoto upstream le permite extraer cambios del repositorio original. Mantener el main de su fork sincronizado con upstream regularmente previene fusiones grandes y dolorosas más adelante. Muchos equipos habilitan 'auto-delete branch on merge' para mantener la lista de ramas ordenada.

git
# typical feature workflow
git checkout -b feature/login
# ... make changes, commit ...
git push -u origin feature/login

# create PR on GitHub, then after merge:
git checkout main
git pull origin main
git branch -d feature/login

# sync a fork with its upstream
git fetch upstream
git checkout main
git merge upstream/main
git push origin main

Repositorios Bare y Mirrors

Un repositorio bare no tiene árbol de trabajo — solo almacena los datos .git. Los repos bare se usan en servidores (como Git autoalojado) como el remoto central al que múltiples personas empujan y del que tiran. --mirror clona todo incluyendo las referencias de remote-tracking y se usa para copias de seguridad o migración de un repositorio entre hosts. --all empuja todas las ramas; --tags empuja todas las etiquetas.

git
# create a bare repo (no working tree) — for servers
git init --bare project.git

# mirror clone (full copy including all refs)
git clone --mirror https://github.com/user/repo.git

# push all branches and tags
git push --all
git push --tags

# push a specific branch to a specific remote
git push origin local-name:remote-name
05

Etiquetas y Releases

Etiquetas Ligera y Anotadas

Las etiquetas marcan commits específicos, típicamente para releases. Las etiquetas ligeras son solo punteros con nombre; las etiquetas anotadas son objetos Git completos que almacenan tagger, fecha y mensaje — recomendadas para releases porque están firmadas y son inmutables. Por convención, v1.0.0 sigue el versionado semántico (major.minor.patch). Use git show <tag> para ver el commit etiquetado y la anotación de la etiqueta.

git
# lightweight tag (just a pointer to a commit)
git tag v1.0.0

# annotated tag (stores metadata + message)
git tag -a v1.0.0 -m "Release 1.0.0"

# tag a specific commit
git tag -a v0.9.0 abc1234 -m "Old release"

# list and inspect tags
git tag
git tag -l "v1.*"
git show v1.0.0

Empujar y Compartir Etiquetas

Las etiquetas son locales hasta que se empujan explícitamente — un error común. --follow-tags empuja solo las etiquetas anotadas alcanzables desde los commits empujados, lo cual es el valor predeterminado más seguro para flujos de trabajo de release. Hacer checkout de una etiqueta le pone en estado 'detached HEAD' (no en una rama) — bien para inspección, pero si quiere hacer cambios, cree una rama primero: git checkout -b fix/v1.0.1 v1.0.0.

git
# tags are NOT pushed by default
git push origin v1.0.0       # push one tag
git push origin --tags       # push all tags

# push tags + branches together
git push origin main --follow-tags

# delete a tag locally and remotely
git tag -d v1.0.0
git push origin --delete v1.0.0

# checkout code at a tag (detached HEAD)
git checkout v1.0.0

Etiquetas Firmadas (GPG)

Las etiquetas y commits firmados usan GPG (o claves SSH en Git más reciente) para probar criptográficamente la identidad del autor. Esto previene la suplantación — crítico para releases de código abierto. Los distribuidores y usuarios pueden verificar una etiqueta con git tag -v. GitHub muestra una insignia 'Verified' en commits y etiquetas firmados. Establezca tag.gpgsign=true para firmar siempre etiquetas automáticamente.

git
# sign a tag with your GPG key
git tag -s v1.0.0 -m "Signed release 1.0.0"

# verify a signed tag
git tag -v v1.0.0

# configure signing key
git config --global user.signingkey ABCD1234
git config --global tag.gpgsign true   # sign all tags

# also sign commits
git commit -S -m "signed commit"
git config --global commit.gpgsign true

Versionado Semántico

El Versionado Semántico (SemVer) da significado a los números de versión: MAJOR para cambios de API incompatibles, MINOR para nuevas funciones compatibles con versiones anteriores, PATCH para correcciones de errores. Los sufijos de pre-release (-alpha, -beta, -rc) señalan estabilidad. Seguir SemVer permite a los usuarios de su biblioteca saber si una actualización es segura — las herramientas automatizadas pueden analizar y comparar cadenas SemVer para detectar cambios disruptivos.

git
# semantic versioning: MAJOR.MINOR.PATCH
v1.0.0   # initial stable release
v1.1.0   # new backward-compatible feature  -> MINOR
v1.1.1   # backward-compatible bug fix       -> PATCH
v2.0.0   # breaking change                   -> MAJOR

# pre-release tags
v2.0.0-alpha.1
v2.0.0-beta.1
v2.0.0-rc.1

# git tag with the version
git tag -a v1.2.0 -m "Add export feature"
git push origin v1.2.0

Describe y Changelog

git describe encuentra la etiqueta más reciente alcanzable desde un commit e informa cuántos commits adelante está — perfecto para generar cadenas de versión de compilación como v1.2.0-3-gabc1234. La sintaxis de rango de log v1.1.0..v1.2.0 muestra los commits en v1.2.0 pero no en v1.1.0, que es exactamente lo que necesita para compilar notas de release o un changelog entre dos releases.

git
# describe the closest tag (great for versioning)
git describe --tags
# output: v1.2.0-3-gabc1234
# meaning: 3 commits after v1.2.0, commit hash abc1234

# use it for build versioning
VERSION=$(git describe --tags --always)
echo "Building $VERSION"

# generate a changelog between two tags
git log v1.1.0..v1.2.0 --oneline
git log v1.1.0..v1.2.0 --pretty=format:"- %s" > CHANGELOG.md
06

Deshacer Cambios

Reset: Soft, Mixed, Hard

reset mueve el puntero de la rama actual. --soft mantiene sus cambios en staging (solo se deshace el commit) — ideal para re-commitear. --mixed (predeterminado) quita del staging pero mantiene los cambios del árbol de trabajo. --hard descarta todo permanentemente — su única recuperación es el reflog. Nunca use --hard en commits que haya empujado, ya que reescribe el historial compartido y causa divergencia.

git
# reset moves HEAD and optionally the index/worktree
git reset --soft HEAD~1     # undo commit, keep staged
git reset --mixed HEAD~1    # undo commit + unstage (DEFAULT)
git reset --hard HEAD~1     # undo commit + discard changes

# reset to a specific commit
git reset --hard abc1234

# reset a single file to HEAD
git reset HEAD file.txt     # unstage
git checkout -- file.txt    # discard working changes

Revert (Deshacer Seguro)

A diferencia de reset (que reescribe el historial), revert añade un nuevo commit que invierte el commit objetivo — seguro para ramas compartidas porque el historial se preserva. Esta es la forma correcta de deshacer un cambio que ya ha sido empujado. Revertir un commit de fusión requiere -m 1 para especificar qué línea padre mantener (1 = la rama en la que fusionó).

git
# revert creates a NEW commit that undoes another
git revert abc1234
git revert HEAD             # undo last commit

# revert a range
git revert HEAD~3..HEAD

# revert without committing (stage the inverse)
git revert --no-commit abc1234

# revert a merge commit
git revert -m 1 abc1234     # -m 1 = keep main branch parent

Restore y Clean

git restore (Git 2.23+) es el comando moderno y enfocado para operaciones de árbol de trabajo, separando las responsabilidades de checkout. --staged quita del staging sin tocar los cambios de trabajo. git clean elimina archivos sin rastrear — siempre ejecútelo con -n (dry run) primero para previsualizar qué se eliminará. -x es agresivo: también elimina archivos gitignored, lo cual es útil para una compilación limpia pero puede borrar secretos o salidas de compilación.

git
# discard unstaged changes in a file
git restore file.txt
git checkout -- file.txt    # older syntax

# unstage a file (keep working changes)
git restore --staged file.txt

# restore a file from a specific commit
git restore --source=abc1234 file.txt

# remove untracked files
git clean -n                # dry run (preview)
git clean -fd               # remove untracked files + dirs
git clean -fdx              # also remove ignored files

Amend y Fixup

--amend reescribe el último commit — útil para corregir un error tipográfico o añadir un archivo olvidado, pero nunca modifique commits empujados. El flujo de trabajo fixup es elegante: cree commits fixup conforme note pequeños problemas, luego git rebase -i --autosquash automáticamente reordena y fusiona en sus commits objetivo. Esto mantiene el historial limpio mientras le permite commitear pequeñas correcciones incrementalmente.

git
# amend the last commit (message + content)
git add forgotten-file.txt
git commit --amend --no-edit

# change only the commit message
git commit --amend -m "better message"

# create a fixup commit (for later autosquash)
git commit --fixup=abc1234

# squash fixups during interactive rebase
git rebase -i --autosquash abc1234~1

Recuperación con Reflog

El reflog es su red de seguridad. Registra cada commit, checkout, reset y rebase — incluso operaciones que 'destruyen' commits. Las entradas persisten durante ~90 días. Si accidentalmente hace reset --hard o elimina una rama, encuentre el hash del commit huérfano en el reflog y haga reset a él o cree una nueva rama apuntando a él. El reflog es solo local, así que esto funciona incluso sin conexión.

git
# reflog records every HEAD movement
git reflog
# abc1234 HEAD@{0}: reset: moving to abc1234
# def5678 HEAD@{1}: commit: feat: add x
# ghi9012 HEAD@{2}: checkout: moving to feature

# recover a 'lost' commit
git reset --hard def5678

# recover a deleted branch
git branch recovered-feature def5678

# reflog for a specific branch
git reflog show feature
07

Stashing y Flujos de Trabajo

Stashing de Cambios

Stash guarda cambios no commiteados para que pueda cambiar de rama o extraer actualizaciones con un árbol limpio. apply mantiene el stash en la lista (útil si quiere aplicarlo a múltiples ramas); pop aplica y elimina. Los stashes son una pila LIFO referenciada por stash@{N}. Use clear con precaución — descarta permanentemente todos los stashes.

git
# save uncommitted changes (reverts working tree to HEAD)
git stash
git stash push -m "wip: login form"   # with a message

# list, show, apply
git stash list
git stash show -p stash@{0}   # show diff
git stash apply               # apply latest, keep stash
git stash pop                 # apply latest, drop stash

# apply a specific stash
git stash apply stash@{2}

# drop a stash
git stash drop stash@{0}
git stash clear               # drop ALL stashes

Stash Parcial y Selectivo

Estos flags dan control fino sobre qué se guarda en el stash. --keep-index es útil cuando ha hecho staging de un commit lógico pero quiere probar solo esos cambios — guarde el resto en stash, ejecute pruebas, luego pop. -u incluye archivos sin rastrear (de lo contrario se dejan en su árbol de trabajo). -p le permite seleccionar fragmentos específicos, reflejando git add -p.

git
# stash only staged changes
git stash --staged

# stash only unstaged changes (keep staged)
git stash --keep-index

# stash interactively by hunk
git stash -p

# stash including untracked files
git stash -u
git stash --include-untracked

# stash everything (even ignored)
git stash -a

Ramas de Stash y Create

git stash branch crea una nueva rama en el commit padre original del stash y aplica el stash allí — perfecto cuando un stash ya no se aplica limpiamente a la rama actual debido a conflictos. También puede exportar un stash como un archivo de parche con show -p para archivo o compartir. Los stashes son locales y nunca se empujan, por lo que no deberían usarse para almacenamiento a largo plazo.

git
# create a branch from a stash (great for conflicts)
git stash branch feature/wip stash@{0}

# create a commit from a stash without applying
git stash store -m "saved wip" stash@{0}

# apply a stash to a different branch
git checkout other-branch
git stash apply stash@{0}

# inspect stash contents
git stash show stash@{0} --stat
git stash show -p stash@{0} > patch.diff

Modelos de Flujo de Trabajo Git

GitHub Flow es el más simple: una rama main, ramas de feature con PRs, despliegue al fusionar — ideal para despliegue continuo. Git Flow (modelo de Vincent Driessen) añade ramas develop, release y hotfix para gestión estructurada de releases — adecuado para productos versionados. Trunk-Based Development usa ramas de muy corta duración y es favorecido por equipos DevOps de alto rendimiento para máxima velocidad de integración.

git
# GitHub Flow (simple, popular)
# main is always deployable; feature branches + PRs
git checkout -b feature/x
git push -u origin feature/x
# open PR, review, merge to main, deploy

# Git Flow (structured, with release branches)
# main: production releases
# develop: integration branch
# feature/*: features off develop
# release/*: release prep
# hotfix/*: urgent fixes off main

# Trunk-Based Development
# short-lived branches (1-2 days), frequent rebase on main

Submódulos

Los submódulos incrustan un repositorio Git dentro de otro — útiles para compartir una biblioteca entre proyectos manteniéndola versionada independientemente. El repositorio padre almacena un puntero a un commit específico del submódulo. Los clones no obtienen el contenido del submódulo por defecto; --recurse-submodules lo hace en un paso. Los submódulos pueden ser engorrosos; para una gestión de dependencias más simple, considere Git subtrees o un gestor de paquetes.

git
# add another repo as a submodule
git submodule add https://github.com/user/lib.git libs/lib

# clone a repo with its submodules
git clone --recurse-submodules https://github.com/user/repo.git

# initialize submodules in an existing clone
git submodule update --init --recursive

# update submodules to their latest remote commit
git submodule update --remote

# record a submodule pointer change
git add libs/lib
git commit -m "chore: bump lib submodule"
08

Inspección y Depuración

Blame y Annotate

git blame (también llamado annotate) muestra el commit y autor de cada línea de un archivo — esencial para entender por qué el código se ve como se ve. -L restringe a un rango de líneas, útil para archivos grandes. -w ignora cambios puros de espacios en blanco, y -C detecta código movido o copiado de otro archivo, dándole un historial más preciso de dónde se originaron realmente las líneas.

git
# show who last changed each line
git blame file.txt
git blame -L 10,20 file.txt        # only lines 10-20
git blame -e file.txt              # show author email
git blame -w file.txt              # ignore whitespace changes
git blame -C file.txt              # detect moved lines across files

# GUI annotation in some editors
git gui blame file.txt

Bisect (Búsqueda Binaria de Bugs)

bisect realiza una búsqueda binaria a través del historial para localizar el commit exacto que introdujo un bug. Marca un commit conocido como bueno y otro como malo; Git hace checkout del punto medio, usted prueba, luego marca bueno o malo, dividiendo el rango cada vez. Con un script, todo el proceso es completamente automatizado — un enorme ahorro de tiempo para regresiones en historiales grandes.

git
# find the commit that introduced a bug
git bisect start
git bisect bad                 # current commit is broken
git bisect good v1.0.0         # v1.0.0 was working

# Git checks out a midpoint; test it, then:
git bisect good                # or git bisect bad

# automate with a script that exits non-zero on bug
git bisect start HEAD v1.0.0 -- npm test

# finish and return to original branch
git bisect reset

# view the bisect log
git bisect log

Buscar Código e Historial

git grep busca archivos rastreados en el árbol de trabajo — más rápido que grep -r porque usa el índice. La opción -S 'pickaxe' encuentra commits que añadieron o eliminaron una cadena específica, invaluable para rastrear cuándo se introdujo una función o bug. -G es similar pero coincide con una regex en cualquier parte del diff. Combinado con filtros --author y --since, puede localizar cualquier cambio en el historial.

git
# search working tree for a string
git grep "TODO"
git grep -n "TODO" -- "*.js"
git grep -i "error" src/

# search across all commits (history)
git log -S "functionName" --oneline    # pickaxe: when added/removed
git log -G "functionName.*\(" --oneline  # regex match in diffs

# search commit messages
git log --grep="fix.*login" --oneline
git log --author="Alice" --since="1 week ago"

FSCK y Objetos Dangling

git fsck (file system check) verifica la integridad de la base de datos de objetos y puede encontrar commits dangling — útil para recuperación cuando el reflog es insuficiente. git gc reorganiza y comprime objetos para ahorrar espacio en disco; --prune=now elimina objetos inalcanzables inmediatamente. Git hace gc automático periódicamente, pero ejecutarlo manualmente puede encoger un repositorio grande. --aggressive recalcula los deltas para máxima compresión.

git
# check repository integrity
git fsck --full
git fsck --unreachable

# find dangling (unreachable) commits & blobs
git fsck --lost-found

# garbage collect and prune old objects
git gc
git gc --prune=now
git gc --aggressive

# count objects and disk usage
git count-objects -v

Archive y Bundle

git archive exporta una instantánea limpia de un commit sin el directorio .git — ideal para distribuir releases o enviar código fuente a alguien que no necesita el historial. git bundle empaqueta un repositorio (o un rango de commits) en un único archivo que puede ser clonado o del que se puede hacer fetch — perfecto para transferir repositorios a través de redes aisladas o por email cuando no puede usar un servidor remoto.

git
# export a snapshot as a tar/zip (no .git history)
git archive --format=zip --output=app.zip HEAD
git archive --format=tar HEAD | gzip > app.tar.gz
git archive -o release.zip v1.0.0

# bundle a repo (with history) into a single file
git bundle create repo.bundle --all
git bundle create patch.bundle main..feature

# clone from a bundle (offline transfer)
git clone repo.bundle new-repo
git fetch patch.bundle feature
09

Técnicas Avanzadas

Hooks

Los hooks son scripts que se ejecutan automáticamente en puntos específicos. Los hooks del lado del cliente (pre-commit, commit-msg, pre-push) aplican políticas locales como linting o pruebas. Los hooks del lado del servidor (pre-receive, post-receive) se ejecutan en el remoto y pueden aplicar protección de ramas o disparar CI/CD. El directorio .git/hooks contiene scripts de ejemplo que terminan en .sample — renombre para activar. Herramientas como Husky o el framework pre-commit gestionan hooks en el propio repositorio para consistencia del equipo.

git
# client-side hooks live in .git/hooks/
#   pre-commit    runs before a commit is created
#   commit-msg    validates the commit message
#   pre-push      runs before pushing to remote

# sample pre-commit hook (.git/hooks/pre-commit)
#!/bin/sh
npm run lint || exit 1
npm test || exit 1

# server-side hooks (on the remote)
#   pre-receive, update, post-receive

# make a hook executable
chmod +x .git/hooks/pre-commit

Worktrees

Los worktrees le permiten tener múltiples directorios de trabajo para un único repositorio, cada uno en una rama diferente — sin clonar. Esto es invaluable cuando necesita trabajar en un hotfix mientras mantiene intacto el árbol de trabajo de su rama de feature, o ejecutar una compilación larga en una rama mientras edita otra. Todos los worktrees comparten la misma base de datos de objetos .git, por lo que el uso de disco es mínimo.

git
# create a second working tree of the same repo
git worktree add ../app-feature feature/x

# list worktrees
git worktree list

# create a worktree with a new branch
git worktree add -b hotfix/y ../app-hotfix main

# remove a worktree
git worktree remove ../app-feature

# prune stale worktree metadata
git worktree prune

Filtrar y Reescribir Historial

Reescribir el historial es necesario cuando se commitearon secretos o archivos grandes. git filter-repo es el reemplazo moderno y rápido de git filter-branch. BFG Repo-Cleaner es una alternativa amigable para eliminar archivos grandes o patrones de contraseñas. Después de reescribir, debe hacer force-push y notificar a todos los colaboradores para re-clonar — los commits antiguos permanecen en sus reflogs hasta que expiren. Siempre rote cualquier secreto filtrado inmediatamente.

git
# remove a file from ALL of history (sensitive data)
git filter-repo --path secrets.env --invert-paths
# (install git-filter-repo; the old filter-branch is deprecated)

# rewrite author info across all commits
git filter-repo --mailmap mailmap.txt

# split a subdirectory into its own repo
git filter-repo --subdirectory-filter libs/lib

# the simpler BFG Repo-Cleaner (Java tool)
bfg --delete-files *.env
bfg --replace-text passwords.txt

Sparse Checkout y Partial Clone

Partial clone (--filter) obtiene commits y árboles pero descarga blobs (contenido de archivos) perezosamente conforme accede a ellos — acelerando drásticamente los clones de repositorios enormes. Sparse checkout limita su árbol de trabajo a directorios específicos, por lo que solo ve las partes en las que trabaja. Juntos hacen manejables los monorepos enormes: el clon es rápido y su árbol de trabajo se mantiene pequeño.

git
# partial clone: download history on demand
git clone --filter=blob:none https://github.com/user/huge-repo.git

# sparse checkout: only certain directories
git clone --no-checkout https://github.com/user/repo.git
cd repo
git sparse-checkout init --cone
git sparse-checkout set src docs
git checkout main

# add a directory to the sparse set later
git sparse-checkout add tests

# disable sparse checkout (fetch everything)
git sparse-checkout disable

Reflog, Refspec y Notes

Los refspecs dan control explícito sobre cómo se mapean las referencias durante fetch/push — útil para flujos de trabajo inusuales como empujar una rama de feature local a la main remota. git notes adjunta metadatos a un commit sin reescribirlo, lo cual es útil para comentarios de revisión o enlaces de CI. Las notas se almacenan en una referencia separada (refs/notes/commits) y deben ser empujadas y obtenidas explícitamente.

git
# refspec: explicit fetch/push mapping
git fetch origin "refs/heads/*:refs/remotes/origin/*"
git push origin "refs/heads/feature:refs/heads/main"

# add a note to a commit (without changing it)
git notes add -m "Reviewed by Alice" abc1234
git notes show abc1234
git log --show-notes

# configure where notes are stored
git config --global notes.displayRef "refs/notes/*"

# push notes to a remote
git push origin refs/notes/commits
10

Mejores Prácticas y Consejos

Higiene de Commits

Los buenos commits son pequeños, atómicos y autónomos: un cambio lógico por commit. Esto hace la revisión de código más fácil, el bisect más rápido y los reverts quirúrgicos. Use git add -p para hacer staging solo de los fragmentos relevantes a una única preocupación. Los mensajes imperativos ('add' no 'added') se leen como instrucciones para el código base. Hacer rebase de ramas de feature sobre el último main antes de fusionar mantiene el historial lineal e inteligible.

git
# commit small, focused, logical units
git add -p               # stage only relevant hunks
git commit -m "fix: handle null user in login"

# write clear, imperative messages
# GOOD: "feat: add password reset endpoint"
# BAD:  "stuff", "fix", "WIP", "asdf"

# one concern per commit
git add login.ts && git commit -m "feat: add login"
git add styles.css && git commit -m "style: format login"

# rebase before merging to keep history clean
git fetch && git rebase origin/main

Protección de Ramas y Revisión de Código

Las reglas de protección de ramas previenen force-pushes, requieren revisiones de PR y condicionan las fusiones a CI exitoso — esencial para la seguridad del equipo. Requerir historial lineal fuerza rebase o squash merges, manteniendo el historial legible. Los commits firmados (GPG o SSH) prueban la autoría y previenen la suplantación. Estos ajustes se configuran en la plataforma de hosting (GitHub/GitLab), no en Git mismo, pero aplican buena higiene de Git en toda la organización.

git
# on GitHub/GitLab: protect the main branch
# - require pull request reviews
# - require status checks (CI) to pass
# - require linear history (no merge commits)
# - dismiss stale reviews on push
# - require signed commits

# enforce via config (GitHub CLI)
gh api repos/:owner/:repo/branches/main/protection \
  -X PUT -f required_status_checks[strict]=true

# sign commits and verify on receive
git config --global commit.gpgsign true
git config --global gpg.format ssh

Ignorar y Gestión de Secretos

Los secretos commiteados son el incidente de seguridad de Git más común. Una vez empujados, asuma que el secreto está comprometido — rótelo inmediatamente, incluso después de eliminarlo del historial, porque los clones y forks lo retienen. La prevención es mejor: .gitignore secretos, use variables de entorno o un gestor de secretos (Vault, AWS Secrets Manager), e instale git-secrets o un hook pre-commit que escanee en busca de claves de API y contraseñas antes de cada commit.

git
# never commit secrets — use .gitignore
echo ".env" >> .gitignore
echo "*.pem" >> .gitignore
echo "secrets/" >> .gitignore

# if you accidentally committed a secret:
# 1. rotate/revoke the secret immediately
# 2. remove from history
git filter-repo --path .env --invert-paths
git push --force-with-lease

# use environment files or secret managers
echo "API_KEY=$API_KEY" > .env.local   # gitignored
# load via dotenv in your app

# use git-secrets to scan pre-commit
git secrets --install
git secrets --register-aws

Consejos de Rendimiento

Los repositorios grandes pueden ser lentos. fsmonitor y untrackedcache hacen git status mucho más rápido cacheando el estado del sistema de archivos. Los clones superficiales y parciales reducen la descarga inicial. Ejecute git gc periódicamente para compactar objetos. --no-verify omite los hooks pre-commit y commit-msg — útil para emergencias pero no debería convertirse en hábito, ya que omite las verificaciones de calidad de su equipo.

git
# speed up status on large repos
git config --global core.fsmonitor true
git config --global core.untrackedcache true

# shallow clone for CI
git clone --depth 1 https://github.com/user/repo.git

# partial clone for huge monorepos
git clone --filter=blob:none https://github.com/user/monorepo.git

# periodic garbage collection
git gc --prune=now

# disable slow hooks temporarily
git commit --no-verify

Errores Comunes

Evite estos errores clásicos: nunca haga force-push a ramas compartidas (--force-with-lease es la opción segura); nunca haga rebase de commits en los que otros puedan haber basado trabajo; nunca commitee en un detached HEAD sin crear primero una rama. Use Git LFS para binarios grandes — de lo contrario inflan el historial permanentemente. Configure los finales de línea (autocrlf) por SO para evitar diffs ruidosos de solo espacios en blanco en equipos multiplataforma.

git
# PITFALL: force-pushing to shared branches
git push --force origin main   # DON'T — use --force-with-lease

# PITFALL: rebasing pushed commits
# rebase only your local, unpushed branches

# PITFALL: committing on detached HEAD
git checkout v1.0.0
# changes here are not on a branch — create one:
git checkout -b fix/v1.0.1

# PITFALL: large binary files bloating history
# use Git LFS for binaries
git lfs install
git lfs track "*.psd"
git add .gitattributes

# PITFALL: CRLF/LF line endings across OS
git config --global core.autocrlf input   # on Linux/Mac
git config --global core.autocrlf true    # on Windows
11

Flujo de Trabajo Git Flow

Modelo de Ramas Git Flow

Git Flow es un modelo estricto de ramificación para proyectos basados en releases. main siempre contiene código de producción; develop contiene trabajo de integración. Las features se ramifican desde develop y se fusionan de vuelta. Los releases se ramifican desde develop, se estabilizan, luego se fusionan tanto a main (con una etiqueta) como a develop. Los hotfixes se ramifican desde main y se fusionan tanto a main como a develop. Este modelo se adapta a proyectos con releases programados (apps de escritorio, software on-premise). Para despliegue continuo, GitHub Flow (main + ramas de feature) es más simple. La herramienta CLI git-flow automatiza la danza de ramas.

git
# Git Flow uses long-lived branches:
# main      → production-ready code
# develop   → latest delivered development changes
# feature/* → new features (branched from develop)
# release/* → release preparation (branched from develop)
# hotfix/*  → urgent production fixes (branched from main)

# Initialize git-flow (interactive setup)
git flow init

# Start a new feature
git flow feature start my-feature
# Creates feature/my-feature from develop

# Finish a feature (merges to develop, deletes branch)
git flow feature finish my-feature

# Publish a feature to remote
git flow feature publish my-feature

# Start a release
git flow release start 1.2.0
# Creates release/1.2.0 from develop

# Finish a release (merges to main AND develop, tags)
git flow release finish 1.2.0

GitHub Flow (Más Simple)

GitHub Flow es el flujo de trabajo más simple: main siempre es desplegable, las ramas de feature son de corta duración y todo se fusiona vía Pull Requests. No hay rama develop ni ramas de release — main se despliega continuamente. Esto funciona bien para apps web con despliegue continuo. La regla clave: nunca commitee directamente a main; siempre use un PR para revisión. Mantenga las ramas de feature pequeñas y de corta duración (días, no semanas). Elimine las ramas después de fusionar para mantener el repositorio limpio. Este modelo prioriza la velocidad y simplicidad sobre la gestión estructurada de releases de Git Flow.

git
# GitHub Flow: only main + feature branches
# 1. Create branch from main
git checkout main
git pull origin main
git checkout -b feature/add-login

# 2. Commit changes
git add .
git commit -m "feat: add login page"

# 3. Push to remote
git push -u origin feature/add-login

# 4. Open Pull Request on GitHub
# 5. Review, discuss, get approval
# 6. Merge to main (via GitHub UI or CLI)
git checkout main
git pull origin main
git branch -d feature/add-login  # delete local branch

# 7. Deploy from main (continuous deployment)

Trunk-Based Development

Trunk-Based Development es el más extremo: los desarrolladores commitean directamente a main (o ramas de muy corta duración fusionadas en 24 horas). Esto habilita la verdadera integración continua — todos integran constantemente. Las features incompletas usan feature flags (desplegadas pero ocultas) en lugar de ramas de larga duración. Esto requiere CI/CD fuerte, pruebas exhaustivas e infraestructura de feature flags. Usado por Google, Facebook y Netflix. Beneficios: no merge hell, feedback rápido, cambios pequeños. Desafíos: requiere disciplina, cobertura de pruebas y gestión de feature flags. Mejor para equipos experimentados con CI/CD robusto.

git
# Trunk-Based: everyone commits to main (trunk)
# Short-lived feature branches optional

# Direct commit to main (small teams)
git checkout main
git pull
# make changes
git add . && git commit -m "fix: typo in header"
git push

# With short-lived branches (larger teams)
git checkout -b quick-fix
git commit -m "fix: validation bug"
git push
# PR merged within 24 hours

# Feature flags enable incomplete code on main
# Code is deployed but hidden behind a flag
if (featureFlag.isEnabled("new-checkout")) {
  showNewCheckout();
} else {
  showOldCheckout();
}

Flujo de Trabajo Fork y Pull

El flujo de trabajo Fork & Pull es estándar para código abierto. Los contribuyentes hacen fork del repositorio (creando su propia copia), empujan ramas a su fork y abren PRs al repositorio original (upstream). El remoto upstream le permite sincronizar su fork con el original. Siempre cree ramas de feature desde un main actualizado. Este flujo de trabajo permite a cualquiera contribuir sin acceso de escritura. Los mantenedores revisan PRs y fusionan. Para mantener su fork sincronizado, haga fetch de upstream y merge/rebase regularmente. Algunos proyectos usan un modelo 'clone, branch, PR' para contribuyentes internos con acceso directo de push a ramas de feature.

git
# For open-source projects (contributors don't have push access)

# 1. Fork the repo on GitHub (UI button)
# 2. Clone YOUR fork
git clone https://github.com/YOUR-USERNAME/project.git
cd project

# 3. Add upstream (original repo)
git remote add upstream https://github.com/ORIGINAL/project.git

# 4. Keep your fork updated
git fetch upstream
git checkout main
git merge upstream/main  # or: git rebase upstream/main
git push origin main

# 5. Create feature branch
git checkout -b feature/my-contribution

# 6. Push to YOUR fork
git push origin feature/my-contribution

# 7. Open Pull Request from your fork to upstream

Convenciones de Nombres de Ramas

Nombres de rama consistentes mejoran la claridad y habilitan automatización. Prefijos comunes: feature, bugfix, hotfix, release, chore, docs, refactor, experiment. Incluir números de ticket (PROJ-123) enlaza ramas con issues y habilita auto-enlazado. Las barras crean jerarquía visual en GUIs de Git. Algunos equipos aplican nombres vía hooks de Git o verificaciones de CI. La convención debería documentarse en CONTRIBUTING.md. Mantenga los nombres descriptivos pero concisos. Evite nombres personales (johns-branch) — describa el trabajo, no el autor. Nombres consistentes hacen la limpieza de ramas y navegación del historial mucho más fácil.

git
# Common naming patterns:
feature/add-user-authentication
feature/PROJ-123-user-profile
bugfix/fix-login-redirect
bugfix/PROJ-456-crash-on-startup
hotfix/security-patch-xss
release/v2.0.0
chore/update-dependencies
docs/api-documentation
refactor/extract-auth-module
experiment/new-algorithm

# Using ticket numbers (Jira, Linear, etc.)
git checkout -b feature/PROJ-123-oauth-login

# Auto-linking in commit messages
git commit -m "PROJ-123: implement OAuth login"
# GitHub auto-links PROJ-123 to the Jira ticket

# Branch naming with slashes creates hierarchy
# in Git GUI tools (GitHub, GitKraken, SourceTree)
12

Rebase en Profundidad

Rebase Interactivo

El rebase interactivo (-i) es la herramienta más potente de edición de historial. Le permite reescribir, reordenar, combinar, dividir o eliminar commits antes de empujar. squash combina un commit en su padre (fusionando mensajes); fixup hace lo mismo pero descarta el mensaje del commit (limpia commits 'WIP'). edit pausa el rebase para que pueda modificar el commit (añadir archivos, cambiar contenido). reword le permite cambiar solo el mensaje. drop elimina un commit. Siempre haga rebase antes de empujar para mantener el historial limpio. Nunca haga rebase de commits que otros ya hayan extraído — reescribe el historial compartido.

git
# Rebase last 5 commits interactively
git rebase -i HEAD~5

# Opens editor with:
# pick a1b2c3d First commit
# pick e4f5g6h Second commit
# pick i7j8k9l Third commit
# pick m0n1o2p Fourth commit
# pick q3r4s5t Fifth commit

# Commands:
# pick   = use commit as-is
# reword = use commit, edit message
# edit   = pause to amend the commit
# squash = combine with previous commit
# fixup  = like squash, discard this message
# drop   = remove commit entirely
# exec   = run a shell command
# reorder = move lines to reorder commits

Squash Commits

Squashing combina múltiples commits en uno, creando historial limpio. Esto es ideal para fusionar una rama de feature: squash 20 commits 'WIP' en un commit significativo 'feat: add login'. El flag --fixup crea un commit especial que --autosquash coloca y fusiona automáticamente con su objetivo — excelente para corregir feedback de revisión sin clutterar el historial. git reset --soft main seguido de un único commit squasha todo a la vez (más simple que el rebase interactivo para squashing total). Muchos equipos configuran las fusiones de PR para auto-squash (opción 'Squash and merge' de GitHub).

git
# Squash the last 3 commits into one
git rebase -i HEAD~3

# In editor, change:
# pick a1b2c3d First
# squash e4f5g6h Second  (or 'fixup' to discard message)
# squash i7j8k9l Third

# Git prompts for combined commit message

# Squash everything since branching from main
git rebase -i main

# Auto-squash fixup commits
git commit --fixup a1b2c3d  # creates fixup! commit
git rebase -i --autosquash main
# Git automatically reorders and squashes fixup commits

# Squash all commits into one on a branch
git reset --soft main
git commit -m "feat: complete feature X"

Rebase vs Merge

Merge preserva el historial completo de la rama (con commits de merge mostrando dónde las features divergieron y se fusionaron). Rebase reproduce los commits encima del objetivo, creando historial lineal sin commits de merge. Merge es más seguro (sin reescritura de historial) y muestra el contexto de la feature. Rebase es más limpio pero reescribe el historial de commits. Estrategia común: haga rebase de su rama de feature sobre el último main antes de fusionar, luego merge (fast-forward o con --no-ff para un commit de merge). Esto da commits limpios Y un commit de merge marcando la feature. Para ramas públicas/compartidas, prefiera merge para evitar conflictos de historial.

git
# MERGE: preserves complete history with merge commits
git checkout main
git merge feature
# Creates a merge commit, history looks like a graph:
# *   merge commit
# |\
# | * feature commit 2
# | * feature commit 1
# * main commit

# REBASE: linear history, no merge commits
git checkout feature
git rebase main
git checkout main
git merge feature  # fast-forward, no merge commit
# History is linear:
# * feature commit 2
# * feature commit 1
# * main commit

# When to use each:
# Merge: preserve context of feature branch, public repos
# Rebase: clean linear history, before pushing feature branch

Resolver Conflictos de Rebase

Los conflictos de rebase ocurren al reproducir commits sobre una base cambiada. Resuelva cada conflicto, git add los archivos resueltos y git rebase --continue. El rebase procesa un commit a la vez, por lo que puede encontrar múltiples conflictos. --skip descarta un commit (úselo si se vuelve vacío después del rebase). --abort cancela todo y regresa al estado previo al rebase — siempre una escapatoria segura. Para conflictos complejos, git mergetool lanza una herramienta visual de merge (VS Code, Beyond Compare, etc.). La diferencia clave con los conflictos de merge: el rebase puede requerir resolver el mismo conflicto múltiples veces (una por commit reproducido).

git
# During rebase, conflicts pause the process
git rebase main
# CONFLICT in file.txt

# 1. Resolve conflicts manually in the file
#    (look for <<<<<<< ======= >>>>>>> markers)

# 2. Stage resolved files
git add file.txt

# 3. Continue the rebase
git rebase --continue

# Skip a commit (if it's empty after rebase)
git rebase --skip

# Abort the entire rebase (return to original state)
git rebase --abort

# Use a merge tool for conflicts
git mergetool

# After resolving, the rebase continues
# with the next commit automatically

Rebase --onto (Avanzado)

git rebase --onto es una forma avanzada para trasplante preciso de commits. La sintaxis: rebase --onto NEW-BASE OLD-BASE BRANCH — toma los commits entre OLD-BASE y BRANCH, y los reproduce sobre NEW-BASE. Esto es útil para cambiar el punto base de una rama (p. ej., su feature se basaba en otra feature que se fusionó; rebase onto main para limpiar). También se usa para eliminar commits específicos del historial (reproducir alrededor de ellos). Esta es una función de usuario avanzado — entienda el rebase regular primero. Siempre tenga una copia de seguridad (reflog) antes de reescritura de historial avanzada.

git
# Scenario: feature branch was based on old-branch,
# but you want to rebase onto main instead

# git rebase --onto new-base old-base branch
git rebase --onto main old-branch feature

# This takes commits from old-branch..feature
# and replays them onto main

# Use case: remove commits from the middle
# Remove commit C from branch (replay A,B,D onto base)
#   base -> A -> B -> C -> D
# git rebase --onto base C D
# Result: base -> A -> B -> D'

# Use case: move a branch to a different parent
git rebase --onto main feature/sub-feature feature/main-feature
# Moves main-feature's unique commits from sub-feature to main
13

Cherry-pick y Bisect

git cherry-pick

cherry-pick aplica un commit específico de una rama a otra. Casos de uso: aplicar un bugfix a una rama de release, copiar un commit que olvidó fusionar, o portar features selectivamente. El commit obtiene un nuevo hash (padre diferente). Cherry-pick puede causar conflictos si la rama objetivo ha divergido. --no-commit hace staging de los cambios sin commitear (útil para combinar múltiples cherry-picks). Evite el cherry-picking excesivo — puede crear commits duplicados cuando las ramas eventualmente se fusionan. Para backporting sistemático, use ramas de release con merges en su lugar.

git
# Apply a specific commit to current branch
git cherry-pick a1b2c3d

# Cherry-pick multiple commits
git cherry-pick a1b2c3d e4f5g6h i7j8k9l

# Cherry-pick a range (exclusive start, inclusive end)
git cherry-pick A..E  # commits B, C, D, E

# Cherry-pick without committing (stage only)
git cherry-pick --no-commit a1b2c3d
# or: -n

# Cherry-pick and edit commit message
git cherry-pick --edit a1b2c3d

# Cherry-pick from another branch
git cherry-pick feature-branch~2

# If conflicts occur:
git add . && git cherry-pick --continue
git cherry-pick --abort  # cancel

git bisect (Búsqueda Binaria)

git bisect realiza una búsqueda binaria a través del historial de commits para encontrar el commit exacto que introdujo un bug. Marca el estado actual como 'bad' y un commit que funciona como 'good'. Git hace checkout del punto medio; usted prueba y marca good/bad. Cada paso divide el espacio de búsqueda a la mitad — encontrar un bug en 1000 commits toma ~10 pasos. bisect reset regresa a su rama original. Esto es invaluable para rastrear regresiones. Combine con git bisect log para guardar/restaurar sesiones de bisect. El commit culpable a menudo revela la causa raíz inmediatamente.

git
# Find which commit introduced a bug
git bisect start

# Mark current (bad) commit
git bisect bad

# Mark a known-good commit (last working version)
git bisect good v1.0.0
# or: git bisect good a1b2c3d

# Git checks out a middle commit
# Test it, then mark:
git bisect good  # if this commit works
git bisect bad   # if this commit is broken

# Repeat until Git identifies the culprit:
# "a1b2c3d is the first bad commit"

# When done:
git bisect reset  # return to original branch

# Skip a commit (can't test, e.g., doesn't build)
git bisect skip

Bisect Automatizado

El bisect automatizado ejecuta un script de prueba para cada commit, eliminando la prueba manual. El script sale con 0 (good), distinto de cero (bad), o 125 (skip — p. ej., fallo de compilación). Git marca automáticamente cada commit y encuentra al culpable. Esto es extremadamente potente con un conjunto de pruebas: git bisect run npm test encuentra el commit que rompe en minutos. También puede restringir la búsqueda a archivos específicos (git bisect start -- path/to/file) para acelerar las cosas. El script puede ser cualquier cosa: una prueba, una verificación de compilación, o un comando curl comprobando una API. Guarde el log de bisect para reproducir o reanudar más tarde.

git
# Auto-bisect with a test script
git bisect start
git bisect bad HEAD
git bisect good v1.0.0

# Git runs this script for each commit
# Exit 0 = good, exit 1 = bad, 125 = skip
git bisect run npm test

# Or a custom script
git bisect run ./scripts/check-bug.sh

# Example check script:
#!/bin/bash
npm run build || exit 125  # skip if build fails
npm test -- --grep "login bug"
# exit 0 if test passes (good), 1 if fails (bad)

# Git automatically finds the bad commit
# without manual testing

# Bisect with a file range (faster)
git bisect start -- src/auth/login.js

git blame y annotate

git blame muestra el autor y commit de cada línea de un archivo — esencial para entender por qué existe el código. -L restringe a un rango de líneas (más rápido, más enfocado). -w ignora cambios puros de espacios en blanco (muestra el autor real del contenido). -M detecta código movido dentro del mismo archivo; -C detecta código copiado de otros archivos (muestra el autor original, no el copiador). blame es para entender, no para culpar — úselo para encontrar contexto del código, luego lea el commit completo con git show. El botón 'Blame' de GitHub proporciona una interfaz visual. Combine con git log -S para encontrar cuándo se añadió texto específico.

git
# Show who last modified each line
git blame file.txt

# Blame specific line range
git blame -L 10,20 file.txt

# Show full commit hash (not abbreviated)
git blame -l file.txt

# Show email instead of name
git blame -e file.txt

# Ignore whitespace changes
git blame -w file.txt

# Detect moved lines within file
git blame -M file.txt

# Detect moved lines from other files
git blame -C file.txt

# Blame with commit message
git blame --show-name --show-email file.txt | head

git revert (Deshacer Seguro)

git revert crea un nuevo commit que deshace un commit anterior — es la forma segura de deshacer cambios en ramas compartidas (a diferencia de reset, que reescribe el historial). revert es ideal para ramas de producción donde no puede reescribir el historial. Revertir un commit de merge requiere -m 1 (padre mainline) — esto deshace el merge manteniendo el historial de la rama. Revertir un revertir reaplica el cambio original (común cuando un revert fue un error). Para múltiples commits, revierta en orden inverso (más reciente primero) para minimizar conflictos. Siempre use revert en ramas compartidas/públicas; use reset solo en ramas locales.

git
# Revert a commit (creates a NEW commit that undoes it)
git revert a1b2c3d

# Revert multiple commits
git revert a1b2c3d e4f5g6h

# Revert a range
git revert A..E

# Revert without committing (stage the reversal)
git revert --no-commit a1b2c3d

# Revert a merge commit
git revert -m 1 a1b2c3d
# -m 1 specifies the mainline parent (parent 1 = the branch
# you merged INTO, parent 2 = the branch you merged FROM)

# If revert conflicts:
git add . && git revert --continue
git revert --abort
14

Reflog y Recuperación

Fundamentos de git reflog

El reflog registra cada cambio en HEAD y los punteros de rama — incluso operaciones que 'destruyen' commits (reset --hard, rebase, eliminación de rama). Esta es su red de seguridad: los commits 'perdidos' son recuperables vía reflog durante ~90 días (predeterminado). reflog es local (nunca empujado) y muestra el historial cronológico de movimientos de puntero. Para recuperar, encuentre el hash del commit en reflog y checkout/reset a él. La sintaxis @{N} referencia entradas: HEAD@{0} es actual, HEAD@{1} es anterior. Si alguna vez piensa que 'perdió' trabajo, revise reflog primero — casi con seguridad sigue ahí.

git
# View the reflog (reference log)
git reflog
# Shows ALL reference changes, including:
# - commits, checkouts, resets, rebases, merges
# Example output:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: add feature
# i7j8k9l HEAD@{2}: checkout: moving from main to feature

# Reflog for a specific branch
git reflog show feature-branch

# Reflog with dates
git reflog --date=iso

# Recover a "lost" commit
git reflog
# Find the commit hash, then:
git checkout a1b2c3d  # or: git reset --hard a1b2c3d

Recuperar Ramas Eliminadas

Las ramas eliminadas y los commits de reset son recuperables a través de reflog. Los objetos commit siguen existiendo en el almacén de objetos de Git hasta la recolección de basura (predeterminado: 90 días para objetos inalcanzables). Para recuperar una rama eliminada, encuentre su commit tip en reflog y cree una nueva rama apuntando a él. Para errores de reset --hard, reflog muestra la posición anterior de HEAD — haga reset de vuelta a ella. Para rebases malos, encuentre la entrada de reflog antes de que el rebase comenzara y haga reset a ella. La lección clave: en Git, casi nada se pierde verdaderamente de inmediato. Siempre revise reflog antes de entrar en pánico.

git
# Accidentally deleted a branch?
git branch -D feature  # force deleted

# Find it in reflog
git reflog
# e4f5g6h HEAD@{2}: commit: work on feature

# Recreate the branch at that commit
git branch feature e4f5g6h

# Or checkout and create
git checkout -b feature e4f5g6h

# Recover after reset --hard
git reset --hard HEAD~3  # oops, went too far
git reflog
# Find the commit before the reset
git reset --hard HEAD@{1}  # undo the reset

# Recover after a bad rebase
git reflog
git reset --hard HEAD@{5}  # before the rebase started

git fsck (Objetos Dangling)

git fsck verifica la integridad del repositorio y encuentra objetos dangling — commits, blobs y árboles no referenciados por ninguna rama o etiqueta. --lost-found los escribe en .git/lost-found/. Este es el último recurso cuando reflog no tiene lo que necesita (las entradas de reflog expiran, o git gc se ejecutó). Los commits dangling a menudo son el resultado de operaciones abortadas o entradas de reflog expiradas. Inspeccione con git show, luego recupere creando una rama. fsck --full verifica la integridad de todos los objetos (útil para detectar corrupción). Ejecute fsck periódicamente en repositorios importantes para detectar problemas temprano.

git
# Find dangling (unreachable) commits
git fsck --lost-found

# Output:
# dangling commit a1b2c3d...
# dangling blob e4f5g6h...

# Inspect a dangling commit
git show a1b2c3d

# Recover it
git branch recovered a1b2c3d

# Full fsck (check repository integrity)
git fsck --full

# Check for dangling objects only
git fsck --no-reflogs --dangling

# When reflog doesn't have what you need
# (e.g., expired, or gc ran), fsck finds
# ALL dangling objects in the object store

git stash en Profundidad

git stash guarda temporalmente cambios no commiteados. push -m añade un mensaje descriptivo (esencial para gestionar múltiples stashes). -u incluye archivos sin rastrear; -a incluye también archivos ignorados. apply reaplica sin eliminar; pop aplica y elimina. stash branch crea una nueva rama desde el stash (útil si el stash conflictúa con la rama actual). Los stashes se almacenan en una pila (LIFO) — referencie por stash@{N}. Show -p muestra el diff. Los stashes persisten a través de reinicios pero son locales (nunca empujados). Limpie los stashes antiguos regularmente; se acumulan. Para trabajo a largo plazo, cree una rama en lugar de hacer stash.

git
# Stash with message
git stash push -m "work in progress on login"

# Stash specific files
git stash push -m "wip" file1.txt file2.txt

# Stash including untracked files
git stash push -u  # or --include-untracked

# Stash including ignored files
git stash push -a  # or --all

# List stashes
git stash list
# stash@{0}: On feature: wip on login
# stash@{1}: On main: quick fix

# Apply a specific stash
git stash apply stash@{1}

# Apply and drop (pop)
git stash pop  # applies stash@{0} and removes it

# Show stash contents
git stash show -p stash@{0}

# Create branch from stash
git stash branch new-branch stash@{0}

# Drop a stash
git stash drop stash@{1}
git stash clear  # drop ALL stashes

Gestión de git tag

Las etiquetas marcan commits específicos como importantes (releases, hitos). Las etiquetas anotadas (-a) almacenan metadatos (tagger, fecha, mensaje) y se recomiendan para releases. Las etiquetas ligeras son solo punteros con nombre (sin metadatos). Las etiquetas firmadas (-s) usan GPG para verificación (importante para releases de seguridad). Las etiquetas NO se empujan por defecto — use --tags para empujarlas. El versionado semántico (v1.2.3) es la convención de nombres estándar. Haga checkout de una etiqueta para un estado detached HEAD (para inspeccionar o compilar un release). Los releases de GitHub se construyen sobre etiquetas — cree una etiqueta, luego publique un release con notas.

git
# Create annotated tag (recommended)
git tag -a v1.0.0 -m "Release 1.0.0"

# Create lightweight tag
git tag v1.0.0

# Tag a specific commit
git tag -a v0.9.0 -m "beta" a1b2c3d

# List all tags
git tag
git tag -l "v1.*"  # filter by pattern

# Push tags to remote
git push origin v1.0.0      # single tag
git push origin --tags       # all tags

# Delete a tag
git tag -d v1.0.0            # local
git push origin --delete v1.0.0  # remote

# Show tag details
git show v1.0.0

# Checkout a tag (detached HEAD)
git checkout v1.0.0

# Signed tags (GPG)
git tag -s v1.0.0 -m "signed release"
15

Worktree y Submódulos

git worktree

git worktree crea directorios de trabajo adicionales desde el mismo repositorio — sin necesidad de clonar. Cada worktree hace checkout de una rama diferente simultáneamente. Esto es perfecto para: trabajar en un hotfix mientras mantiene su rama de feature abierta, ejecutar pruebas en una rama mientras codifica en otra, o tener compilaciones de larga duración en un worktree. Todos los worktrees comparten el mismo directorio .git (objetos, refs), por lo que se mantienen sincronizados y ahorran espacio en disco. No puede hacer checkout de la misma rama en dos worktrees (Git previene esto para evitar conflictos). Los worktrees son más rápidos que clonar para flujos de trabajo multi-rama.

git
# Create a new working directory linked to the repo
git worktree add ../project-hotfix hotfix-branch

# Work in the new directory
cd ../project-hotfix
# This is a full working tree on hotfix-branch
# Shares the same .git, so no cloning needed

# List worktrees
git worktree list
# /path/to/project          main
# /path/to/project-hotfix   hotfix-branch

# Remove a worktree
git worktree remove ../project-hotfix

# Prune stale worktree entries
git worktree prune

# Create worktree at new branch
git worktree add -b feature-x ../project-feature main

Fundamentos de git submodule

Los submódulos incrustan un repositorio Git dentro de otro — útiles para incluir bibliotecas compartidas o dependencias. El repositorio padre almacena un puntero (hash de commit) al submódulo, no su contenido. --recurse-submodules es esencial al clonar (de lo contrario los submódulos están vacíos). Actualizar submódulos (--remote) obtiene los últimos commits; luego debe commitear el nuevo hash en el repositorio padre. Los submódulos son complejos: ramas, conflictos y actualizaciones requieren manejo cuidadoso. Para una gestión de dependencias más simple, considere Git subtrees, gestores de paquetes (npm, pip), o estrategias de monorepo. Use submódulos cuando necesite rastrear un commit externo específico.

git
# Add a submodule to your repo
git submodule add https://github.com/user/lib.git libs/lib

# Clone a repo with submodules
git clone --recurse-submodules https://github.com/user/project.git

# If you forgot --recurse-submodules:
git submodule update --init --recursive

# Pull latest changes in all submodules
git submodule update --remote

# Pull specific submodule
git submodule update --remote libs/lib

# After updating submodules, commit the new hash
git add libs/lib
git commit -m "update lib submodule"

# Execute command in all submodules
git submodule foreach 'git status'

Flujos de Trabajo de Submódulos

Trabajar dentro de un submódulo es como trabajar en un repositorio normal — hace commit y push desde dentro del directorio del submódulo. El repositorio padre rastrea el hash del commit, así que después de cambiar un submódulo, debe hacer commit en el padre también. Al cambiar de rama, el contenido del submódulo puede no coincidir — ejecute git submodule update --init --recursive para sincronizar. Eliminar submódulos requiere tres pasos: deinit (desregistrar), git rm (eliminar del tracking), y eliminación manual de .git/modules. Los flujos de trabajo de submódulos son propensos a errores; siempre comunique con su equipo al actualizar submódulos para evitar desajustes de hash.

git
# Change code inside a submodule
cd libs/lib
# Make changes, commit, and push
git add .
git commit -m "fix: bug in lib"
git push origin HEAD:main

# Back in parent repo, update the pointer
cd ../..
git add libs/lib
git commit -m "chore: update lib submodule"

# Switch branches with submodules
git checkout main
git submodule update --init --recursive

# Delete a submodule
git submodule deinit -f libs/lib
git rm libs/lib
rm -rf .git/modules/libs/lib
git commit -m "remove lib submodule"

# Move a submodule
git mv libs/lib libs/new-lib

Git Hooks

Los hooks de Git ejecutan scripts en puntos específicos del ciclo de vida de Git. Los hooks del lado del cliente (pre-commit, pre-push, commit-msg) aplican estándares locales. pre-commit es ideal para linting/formato; pre-push para ejecutar pruebas; commit-msg para aplicar commits convencionales. Los hooks NO son rastreados por Git (viven en .git/hooks/), así que no se sincronizan entre clones. Para compartir hooks entre un equipo, use una herramienta como Husky (npm), pre-commit (Python), o commitee los hooks a un directorio versionado y cree symlinks. Los hooks del lado del servidor (pre-receive, post-receive) se ejecutan en el remoto y pueden aplicar políticas para todos los contribuyentes.

git
# Hooks live in .git/hooks/ (not tracked by Git)
# Sample hooks are provided with .sample extension

# pre-commit: runs before commit is created
# .git/hooks/pre-commit
#!/bin/bash
npm run lint || exit 1  # block commit if lint fails

# pre-push: runs before pushing
#!/bin/bash
npm test || exit 1  # block push if tests fail

# commit-msg: validate commit message format
#!/bin/bash
# $1 is the path to the commit message file
if ! grep -qE "^(feat|fix|docs|chore):" "$1"; then
  echo "Use conventional commit format: type: message"
  exit 1
fi

# prepare-commit-msg: auto-populate message
#!/bin/bash
echo "# Branch: $(git branch --show-current)" >> "$1"

# Make hooks executable
chmod +x .git/hooks/pre-commit

Git LFS (Large File Storage)

Git LFS reemplaza archivos grandes (binarios, videos, datasets) con punteros de texto en Git, almacenando el contenido real en un servidor LFS separado. Esto mantiene el repositorio ligero — sin LFS, los archivos binarios inflan el repositorio permanentemente (cada versión se almacena). Rastree patrones de archivo con git lfs track, luego commitee .gitattributes. Después de eso, los archivos grandes funcionan transparentemente. git lfs migrate import convierte retroactivamente archivos existentes a LFS (reescribe el historial — coordine con el equipo primero). LFS requiere soporte del servidor (GitHub, GitLab, Bitbucket todos lo soportan). Nota: LFS tiene cuotas de ancho de banda/almacenamiento en plataformas alojadas. Para archivos verdaderamente enormes, considere almacenamiento externo con URLs.

git
# Initialize Git LFS
git lfs install

# Track large file types
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/**"

# This creates/updates .gitattributes
# Commit the .gitattributes file
git add .gitattributes
git commit -m "chore: configure LFS tracking"

# Add large files normally
git add design.psd
git commit -m "add design file"
git push

# View LFS tracked files
git lfs ls-files

# Pull LFS content (if not auto-downloaded)
git lfs pull

# Migrate existing files to LFS (rewrites history)
git lfs migrate import --include="*.psd" --everything

# Check LFS status
git lfs status
16

Stash

Guardar y Pop

git stash guarda cambios no commiteados para que pueda cambiar de rama o extraer actualizaciones con un árbol de trabajo limpio. pop aplica y elimina el stash superior; apply lo mantiene. El stash es una pila LIFO.

git
# Save uncommitted changes (tracked files only)
git stash

# Save including untracked files
git stash -u

# Pop the most recent stash (removes it)
git stash pop

# Apply without removing (keeps stash in list)
git stash apply

Stashes con Nombre

Siempre pase -m para etiquetar stashes — el mensaje predeterminado es la rama y el commit, lo cual rara vez es descriptivo. stash@{N} referencia un stash específico por su índice.

git
# Save with a descriptive message
git stash push -m "WIP: refactor auth flow"

# List all stashes with messages
git stash list

# Apply a specific stash by index
git stash apply stash@{1}

Ramas de Stash

git stash branch crea una nueva rama desde el commit donde se hizo originalmente el stash, luego aplica el stash allí. Esta es la forma más limpia de recuperar cuando un stash ya no se aplica limpiamente.

git
# Create a new branch from a stash
git stash branch feature-branch stash@{0}

# Useful when stash conflicts with current branch
# - Creates branch from the commit where stash was made
# - Applies stash on top
# - Drops stash if apply succeeds

Stash Parcial

Guarde en stash solo lo que necesite con -p (selección interactiva de fragmentos) o listando archivos específicos. --keep-index hace stash de los cambios no staged pero deja los cambios staged en su lugar.

git
# Interactively choose hunks to stash
git stash push -p

# Stash specific files only
git stash push -m "config tweaks" config.yml .env.local

# Stash with keep-index (staged changes stay)
git stash --keep-index

Gestión de Stash

git stash show -p muestra el diff completo de un stash. drop elimina un único stash; clear los borra todos (irreversible). Los stashes son solo locales; nunca se empujan a remotos.

git
# Show what's in a stash
git stash show -p stash@{0}

# Drop a specific stash
git stash drop stash@{0}

# Clear all stashes (irreversible)
git stash clear

# Show stash list with dates
git stash list --date=relative
17

Rebase

Rebase Básico

Rebase mueve los commits de su rama encima de otra rama, produciendo un historial lineal. A diferencia de merge, reescribe los hashes de commit. Nunca haga rebase de commits que hayan sido empujados y compartidos.

git
# Rebase current branch onto main
git checkout feature
git rebase main

# Abort if conflicts are too messy
git rebase --abort

# Continue after resolving conflicts
git rebase --continue

Rebase Interactivo

El rebase interactivo (-i) le permite reescribir el historial antes de compartir: reordenar commits, squashear relacionados en un único commit limpio, reword mensajes, o drop errores. Siempre haga esto en commits no empujados.

git
# Rebase last 5 commits interactively
git rebase -i HEAD~5

# Commands: pick, reword, edit, squash, fixup, drop
# squash  = combine with previous, keep message
# fixup   = combine with previous, discard message
# reword  = change commit message only

Squash y Fixup

--fixup crea un commit marcado como corrección de otro. --autosquash durante el rebase coloca automáticamente los commits fixup! y squash! junto a sus objetivos. Esto agiliza el flujo de trabajo de 'commitear temprano, limpiar después'.

git
# Create a fixup commit targeting an earlier commit
git commit --fixup a1b2c3

# Autosquash during rebase (auto-reorders fixups)
git rebase -i --autosquash HEAD~5

Rebase --onto

--onto es rebase quirúrgico: mueve un rango de commits de una base a otra. Úselo para re-padrear una rama, o para eliminar los primeros commits de una rama.

git
# Move commits from one base to another
# git rebase --onto <new-base> <old-base> <branch>
git rebase --onto main feature-old feature-new

# Drop the first N commits of a branch
git rebase --onto main HEAD~3

Conflictos de Rebase

Durante el rebase, los conflictos se detienen en cada commit. Resuelva, git add, luego --continue para proceder. --skip descarta el commit conflictivo completamente. --abort regresa al estado previo al rebase.

git
# During rebase, conflicts pause the process
git rebase main
# CONFLICT (content): Merge conflict in file.js

# Resolve in editor, then:
git add file.js
git rebase --continue

# Skip a commit that conflicts (drop it)
git rebase --skip

# Give up entirely
git rebase --abort
18

Cherry-Pick

Cherry-Pick un Commit

cherry-pick aplica un commit específico de otra rama en su rama actual, creando un nuevo commit con los mismos cambios. El nuevo commit tiene un hash diferente porque el padre es diferente.

git
# Apply a specific commit to current branch
git cherry-pick a1b2c3d

# Cherry-pick multiple commits
git cherry-pick a1b2c3d e4f5g6h

# Cherry-pick a range (exclusive of start, inclusive of end)
git cherry-pick A..E

Cherry-Pick Sin Commit

--no-commit (-n) hace staging de los cambios cherry-picked sin crear un commit. Esto le permite combinar múltiples cherry-picks en un commit, o modificar los cambios antes de commitear.

git
# Apply changes to working tree without committing
git cherry-pick --no-commit a1b2c3d

# Stage the changes yourself
git add -p
git commit -m "Custom message"

Conflictos de Cherry-Pick

Los conflictos durante cherry-pick pausan la operación. Resuelva, git add, y --continue. --skip abandona el commit actual. --abort cancela el cherry-pick y restaura la rama.

git
git cherry-pick a1b2c3d
# error: could not apply a1b2c3d...

# Resolve conflicts, then:
git add resolved-file.js
git cherry-pick --continue

# Skip this commit
git cherry-pick --skip

# Abort the entire cherry-pick
git cherry-pick --abort

Cherry-Pick desde Otra Rama

El flujo de trabajo clásico de hotfix: corrija un bug en una rama de mantenimiento, luego cherry-pick el mismo commit en main (y otras ramas activas). Esto evita fusionar trabajo de feature no relacionado.

git
# Find the commit hash on another branch
git log --oneline feature-branch

# Switch to target branch
git checkout main

# Cherry-pick the commit
git cherry-pick <hash>

# Useful for hotfixes: fix on a feature branch,
# then cherry-pick the fix to main

Estrategia de Cherry-Pick

-X theirs/ours sesga la resolución de conflictos. -x añade una línea registrando el hash del commit original — esencial para pistas de auditoría al cherry-pickear hotfixes entre ramas.

git
# Use a different merge strategy
git cherry-pick -X theirs a1b2c3d   # prefer their changes on conflict
git cherry-pick -X ours a1b2c3d     # prefer our changes on conflict

# Preserve original commit author
git cherry-pick -x a1b2c3d          # adds "(cherry picked from ...)" to message

# Edit commit message before committing
git cherry-pick -e a1b2c3d
19

Bisect

Bisect Básico

git bisect realiza una búsqueda binaria a través del historial de commits para encontrar qué commit introdujo un bug. Marca el commit actual como malo y un commit bueno conocido como bueno. Después de ~log2(N) pasos, git nombra el commit ofensor.

git
# Start bisecting
git bisect start

# Mark current commit as bad (has the bug)
git bisect bad

# Mark a known-good commit (older)
git bisect good v1.0.0

# Git checks out the midpoint. Test, then mark:
git bisect good   # or
git bisect bad

# Exit bisect mode
git bisect reset

Log y Replay de Bisect

git bisect log registra cada decisión good/bad. Si marca mal un commit (un error común), resetee y reproduzca el log, luego corrija el paso equivocado.

git
# Record what happens during bisect
git bisect log > bisect.log

# If you make a mistake, replay from the log
git bisect reset
git bisect replay bisect.log

# Visualize the bisect state
git bisect visualize

Bisect Automatizado

git bisect run automatiza la búsqueda: git hace checkout de cada candidato, ejecuta su script y marca el commit según el código de salida. Esto es dramáticamente más rápido que la prueba manual.

git
# Auto-bisect: git runs a test script and marks good/bad itself
git bisect start HEAD v1.0.0 --     # bad=HEAD, good=v1.0.0
git bisect run npm test             # exit 0=good, 1-124=bad, 125=skip

# Or with a custom script
git bisect run ./scripts/check-bug.sh

Bisect por Archivo

Pasar una ruta a git bisect start restringe la búsqueda a commits que modificaron ese archivo. Esto omite cientos de commits irrelevantes y se enfoca en el archivo donde probablemente vive el bug.

git
# Limit bisect to changes in a specific file
git bisect start -- path/to/file.js

# Combine with bad/good markers
git bisect bad HEAD
git bisect good v1.0.0

# Git only considers commits that touched that file

Bisect Reset

Siempre ejecute git bisect reset cuando termine — le regresa a su rama original y limpia el estado de bisect. Sin reset, su árbol de trabajo se queda en el último commit probado.

git
# Exit bisect mode and return to original branch
git bisect reset

# Reset to a specific branch/commit instead
git bisect reset <branch>

# View current bisect state
git bisect status
20

Submódulos

Añadir un Submódulo

Los submódulos incrustan un repositorio Git dentro de otro — útiles para vendorizar bibliotecas compartidas. git submodule add registra el submódulo en .gitmodules. Los nuevos clones necesitan --recurse-submodules.

git
# Add a submodule at a specific path
git submodule add https://github.com/user/lib.git libs/lib

# This creates:
# - libs/lib/ (the submodule checkout)
# - .gitmodules (tracks submodule URLs and paths)

# Commit the new submodule
git commit -m "Add lib submodule"

# Clone a repo with submodules included
git clone --recurse-submodules https://github.com/user/repo.git

Actualizar Submódulos

Un submódulo está fijado a un commit específico. git submodule update --remote obtiene el último commit en la rama rastreada y actualiza el puntero. Debe commitear este cambio de puntero en el repositorio padre.

git
# Pull latest changes in all submodules
git submodule update --remote

# Update a specific submodule
git submodule update --remote libs/lib

# Then commit the updated pointer
git add libs/lib
git commit -m "Bump lib submodule"

# Initialize submodules after cloning
git submodule update --init --recursive

Submodule Foreach

foreach ejecuta un comando de shell en cada directorio de submódulo — útil para operaciones masivas como verificar estado, extraer actualizaciones o compilar. --recursive desciende a submódulos anidados.

git
# Run a command in every submodule
git submodule foreach 'git status'

# With recursion into nested submodules
git submodule foreach --recursive 'git checkout main'

# Use --quiet to suppress the "Entering..." messages
git submodule foreach --quiet 'git pull'

Deinit y Remove

Eliminar un submódulo es un proceso de múltiples pasos: deinit lo desregistra, rm -rf .git/modules/... elimina los datos Git del submódulo, y git rm elimina el árbol de trabajo. Olvidar la limpieza de .git/modules deja datos huérfanos.

git
# Deinit a submodule (unregisters, keeps files)
git submodule deinit libs/lib

# Fully remove a submodule
git submodule deinit -f libs/lib
rm -rf .git/modules/libs/lib
git rm -f libs/lib
git commit -m "Remove lib submodule"

Ramas de Submódulos

Por defecto, los submódulos están en detached HEAD. Establecer submodule.<name>.branch permite a --remote rastrear esa rama. Para hacer cambios dentro de un submódulo, cd dentro, checkout una rama, commit y push.

git
# Configure a submodule to track a branch
git config -f .gitmodules submodule.libs/lib.branch main

# Now --remote updates from that branch
git submodule update --remote

# Work inside a submodule like a normal repo
cd libs/lib
git checkout feature-branch
git push
cd ../..
git add libs/lib
git commit -m "Update lib to feature-branch"
21

Hooks

Hooks Comunes

Los hooks de Git son scripts en .git/hooks/ que se ejecutan automáticamente en puntos específicos. Los hooks del lado del cliente se ejecutan en su máquina y pueden bloquear acciones. Los hooks del lado del servidor se ejecutan en el remoto y aplican políticas. Los hooks no se versionan por defecto — use Husky para compartirlos.

git
# Client-side (run on your machine):
#   pre-commit        - runs before commit is created
#   prepare-commit-msg - can edit commit message
#   commit-msg        - validates commit message
#   pre-push          - runs before pushing
#   post-merge        - runs after git merge

# Server-side (run on the remote):
#   pre-receive       - runs before refs are updated
#   update            - runs once per ref
#   post-receive      - runs after refs are updated

Hook pre-commit

pre-commit se ejecuta antes de que se cree el commit; una salida distinta de cero aborta el commit. Usos comunes: lint archivos staged, ejecutar pruebas enfocadas, formatear código. Manténgalo rápido (<5 segundos) o los desarrolladores lo omitirán con --no-verify.

git
#!/bin/sh
# .git/hooks/pre-commit

# Run linter on staged files
npm run lint-staged
if [ $? -ne 0 ]; then
  echo "Linting failed. Fix errors and try again."
  exit 1
fi

# Run tests
npm test -- --findRelatedTests
if [ $? -ne 0 ]; then
  echo "Tests failed."
  exit 1
fi

exit 0

Hook commit-msg

commit-msg recibe la ruta al archivo temporal del mensaje de commit como $1. Puede validar o reescribir el mensaje. Una salida distinta de cero rechaza el commit. Esta es la forma estándar de aplicar Conventional Commits.

git
#!/bin/sh
# .git/hooks/commit-msg

# Enforce Conventional Commits format
pattern='^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.*\))?: .{1,72}$'

if ! grep -qE "$pattern" "$1"; then
  echo "Invalid commit message format."
  echo "Use: <type>(<scope>): <description>"
  exit 1
fi

Configuración de Husky

Husky instala hooks de Git desde un directorio .husky/ versionado, por lo que cada miembro del equipo obtiene los mismos hooks después de npm install. lint-staged ejecuta comandos solo en archivos staged, manteniendo pre-commit rápido.

git
# Install Husky
npm install --save-dev husky
npx husky init

# Add a pre-commit hook
echo 'npm run lint-staged' > .husky/pre-commit

# Add a commit-msg hook
echo 'npx --no-install commitlint --edit $1' > .husky/commit-msg

# package.json
{
  "scripts": { "prepare": "husky" },
  "lint-staged": {
    "*.{js,ts}": ["eslint --fix", "prettier --write"]
  }
}

Hook pre-push

pre-push se ejecuta antes de que se empujen las refs; una salida distinta de cero aborta. Lee los pushes propuestos desde stdin. Usos comunes: bloquear pushes a ramas protegidas, ejecutar el conjunto de pruebas completo.

git
#!/bin/sh
# .husky/pre-push

# Reads stdin: <local ref> <local sha> <remote ref> <remote sha>
while read local_ref local_sha remote_ref remote_sha; do
  if [ "$remote_ref" = "refs/heads/main" ]; then
    echo "Direct push to main is not allowed."
    exit 1
  fi
done

npm test
if [ $? -ne 0 ]; then
  echo "Tests failed. Push aborted."
  exit 1
fi
22

Worktrees

Añadir un Worktree

Un worktree es un directorio de trabajo separado enlazado al mismo repositorio. Puede tener múltiples ramas en checkout simultáneamente en diferentes directorios — sin stash para cambiar de contexto.

git
# Create a worktree at a path, on a new branch
git worktree add ../project-feature feature-branch

# Worktree on an existing branch
git worktree add ../project-hotfix hotfix-branch

# Worktree at a specific commit (detached HEAD)
git worktree add --detach ../project-inspect v1.0.0

# List all worktrees
git worktree list

Flujo de Trabajo de Worktree

Los worktrees brillan para cambio de contexto: llega un bug urgente mientras está profundamente en una feature. En lugar de hacer stash y perder el estado de su IDE, cree un worktree en main, corrija el bug allí y regrese.

git
# Scenario: working on feature, urgent bug comes in
git worktree add ../project-hotfix main

cd ../project-hotfix
git checkout -b fix-urgent
# ... fix the bug, commit, push ...

cd ../project-feature
# Your feature work is untouched

# Clean up when done
git worktree remove ../project-hotfix

Remove y Prune

remove elimina un directorio worktree y sus metadatos administrativos. La rama en la que estaba permanece. Si el worktree tiene cambios no commiteados, remove se niega a menos que pase --force.

git
# Remove a worktree (must be clean or use --force)
git worktree remove ../project-feature

# Force remove even with uncommitted changes
git worktree remove --force ../project-feature

# Prune worktree admin files for deleted directories
git worktree prune

# Show what would be pruned
git worktree prune --dry-run -v

Beneficios de Worktree

Los worktrees resuelven varios puntos de dolor: sin stash para cambios de contexto, builds/pruebas paralelas, node_modules aislados por rama y comparación de ramas lado a lado. La base de datos de objetos compartida significa sobrecarga mínima de disco.

git
# 1. No stashing needed for context switches
# 2. Run long builds/tests in one worktree while
#    continuing work in another
# 3. Each worktree has its own node_modules
# 4. Compare two branches side-by-side in two
#    editor windows
# 5. Shared object database = minimal disk overhead

# Typical layout:
# ~/projects/
#   main-repo/          (main branch)
#   main-repo-feature/  (feature worktree)
#   main-repo-hotfix/   (hotfix worktree)

Worktrees Bloqueados

Bloquee un worktree cuando contiene trabajo que no debería ser disturbado (compilaciones de larga duración, debugger adjunto). Los worktrees bloqueados sobreviven a prune. move reubica un worktree; repair repara los archivos administrativos.

git
# Lock a worktree (prevents it from being pruned)
git worktree lock ../project-feature --reason "Running long build"

# Unlock when done
git worktree unlock ../project-feature

# Move a worktree to a new path
git worktree move ../project-feature ../new-location/project-feature

# Repair after moving manually
git worktree repair ../new-location/project-feature
23

Reflog

Ver Reflog

El reflog registra cada cambio en HEAD y los tips de rama — commits, checkouts, resets, rebases. Es una red de seguridad local: incluso después de una operación destructiva, los commits siguen en el reflog.

git
# Show reflog for HEAD
git reflog
# a1b2c3d HEAD@{0}: commit: Fix bug
# e4f5g6h HEAD@{1}: checkout: moving to feature
# 789abc0 HEAD@{2}: reset: moving to HEAD~1

# Show reflog for a specific branch
git reflog show feature

# Show reflog with dates
git reflog --date=iso

Recuperar Commits Perdidos

Después de un hard reset o rebase, los commits 'perdidos' siguen siendo alcanzables vía reflog. Encuentre el hash en git reflog, luego reset --hard o cherry-pick para recuperar. Por eso Git es seguro — casi nada se pierde verdaderamente.

git
# Find the commit you lost (e.g., after a hard reset)
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# e4f5g6h HEAD@{1}: commit: Important work  <-- this one

# Reset back to the lost commit
git reset --hard e4f5g6h

# Or cherry-pick it onto your current branch
git cherry-pick e4f5g6h

Reflog y Reset

ORIG_HEAD es una referencia de conveniencia que apunta al HEAD previo después de operaciones destructivas (reset, merge, rebase). git reset --hard ORIG_HEAD deshace la última operación de este tipo en un comando.

git
# Undo a git reset --hard
git reflog
git reset --hard HEAD@{1}

# Undo a rebase
git reflog
git reset --hard HEAD@{5}

# Undo a merge
git reset --hard ORIG_HEAD
# ORIG_HEAD is set by merge, rebase, reset, and cherry-pick

Expirar y Limpiar

Las entradas de reflog se acumulan y consumen espacio en disco. expire elimina entradas antiguas; --expire-unreachable apunta solo a entradas no alcanzables desde ninguna referencia. Después de expirar, ejecute git gc --prune=now para eliminar objetos inalcanzables.

git
# Expire reflog entries older than 30 days
git reflog expire --expire=30.days

# Expire entries unreachable from current branches
git reflog expire --expire-unreachable=30.days

# Delete a specific reflog entry
git reflog delete HEAD@{2}

# Dry run (see what would be expired)
git reflog expire --dry-run --expire=now --all

Reflog para Ramas

Cada rama mantiene su propio reflog. Si elimina una rama con git branch -D, los commits siguen en el reflog — encuentre el hash y recree la rama con git branch <nombre> <hash>.

git
# Each branch has its own reflog
git reflog show main
# a1b2c3d main@{0}: commit: Update docs
# e4f5g6h main@{1}: pull origin main: Fast-forward

# Recover a deleted branch
git reflog  # find the hash the branch pointed to
git branch recovered-branch e4f5g6h

# Compare current state with a past reflog entry
git diff HEAD@{1} HEAD
24

LFS

Instalar y Rastrear

Git LFS almacena archivos grandes (imágenes, videos, binarios) fuera del repositorio Git, reemplazándolos con archivos puntero en los commits. git lfs track registra patrones en .gitattributes. Siempre commitee .gitattributes antes de añadir archivos grandes.

git
# Install Git LFS (once per user)
git lfs install

# Track large file types
git lfs track "*.psd"
git lfs track "*.zip"
git lfs track "assets/*"

# View tracking rules
cat .gitattributes

# Commit the .gitattributes file
git add .gitattributes
git commit -m "Configure LFS tracking"

Añadir y Commitear Archivos LFS

Una vez que un patrón de archivo está rastreado, git add hace staging del archivo a través de LFS automáticamente. El commit almacena un pequeño archivo puntero (~130 bytes) en lugar del binario. El contenido real se sube al servidor LFS en el push.

git
# After configuring tracking, add files normally
git add design.psd
git commit -m "Add design file"

# Verify LFS is handling the file
git lfs ls-files
# a1b2c3d4 * design.psd

# The commit contains a pointer, not the actual file
git show HEAD -- design.psd
# version https://git-lfs.github.com/spec/v1
# oid sha256:...
# size 12345678

Clonar y Pull

Clonar un repositorio con LFS descarga primero los archivos puntero, luego el contenido real. Si la descarga LFS falla, git lfs pull reintenta. git lfs fetch descarga objetos sin escribirlos en el árbol de trabajo.

git
# Clone a repo with LFS files (downloads them automatically)
git clone https://github.com/user/repo.git

# If LFS files are missing, fetch them
git lfs pull

# Fetch LFS objects without checking them out
git lfs fetch

# Checkout LFS files for specific paths
git lfs checkout assets/

Migrar a LFS

git lfs migrate import convierte retroactivamente archivos grandes en el historial a punteros LFS. Esto reescribe todos los hashes de commit — cada colaborador debe re-clonar. Ejecute --dry-run primero para ver el impacto.

git
# Convert existing large files to LFS (rewrites history!)
git lfs migrate import --include="*.psd" --include-ref=refs/heads/main

# Migrate all branches
git lfs migrate import --include="*.psd" --everything

# Check what would be migrated (dry run)
git lfs migrate import --include="*.psd" --include-ref=refs/heads/main --dry-run

Gestión de LFS

git lfs status muestra cambios LFS pendientes. git lfs env muestra la configuración. git lfs fsck verifica que todos los objetos LFS referenciados por archivos puntero estén presentes y sin corromper.

git
# View LFS status
git lfs status

# List all LFS files in the repo
git lfs ls-files

# Check LFS configuration
git lfs env

# Untrack a file type (does not remove existing LFS objects)
git lfs untrack "*.zip"

# Verify LFS objects are present and valid
git lfs fsck
25

Integración CI/CD

GitHub Actions Básico

GitHub Actions ejecuta flujos de trabajo en push/PR. actions/checkout obtiene el repositorio; fetch-depth: 0 obtiene el historial completo. npm ci instala desde el lockfile (más rápido, más estricto que install). Cachee dependencias con la clave de cache.

git
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test
      - run: npm run build

Flujos de Trabajo Condicionales

Filtre flujos de trabajo por rama o evento con on: push: branches. Use condiciones if: a nivel de job para omitir jobs según el contexto. needs: crea dependencias entre jobs — deploy solo se ejecuta si test pasa.

git
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh

Git Hooks en CI

Los pipelines de CI a menudo reflejan hooks locales: lint primero (fallo rápido), luego test. needs: lint asegura que las pruebas solo se ejecuten si el linting pasa, ahorrando minutos de CI. Dividir jobs permite runners paralelos para velocidad.

git
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck

  test:
    needs: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test -- --coverage

Secrets y Entorno

Almacene secrets (claves de API, tokens) en la configuración del repositorio y referéncielos vía ${{ secrets.NAME }}. Nunca se muestran en los logs. Los entornos pueden requerir aprobación manual antes del despliegue, añadiendo una puerta de control.

git
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # requires manual approval
    steps:
      - uses: actions/checkout@v4
      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DB_URL: ${{ secrets.DATABASE_URL }}
        run: ./scripts/deploy.sh "$API_KEY" "$DB_URL"

Builds Matriciales

Los builds matriciales ejecutan el mismo job en múltiples combinaciones de SO/lenguaje en paralelo. Esto detecta bugs específicos de plataforma temprano. Use fail-fast: false para ejecutar todas las combinaciones incluso si una falla. Mantenga las matrices razonables para controlar los minutos de CI.

git
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: ['18', '20', '22']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.