# Git avanzado I: reescribir la historia

*Primera entrega de la serie **[Git avanzado](/search?tag=git-avanzado)**. Si vienes saltado desde el nivel **[intermedio](/search?tag=git-intermedio)** o **[básico](/search?tag=git-basico)**, aquí entramos en el terreno donde git deja de ser sistema de control de versiones y empieza a ser editor de historia. Tiempo de lectura estimado: 5 minutos.*

Los tres comandos de hoy permiten modificar commits que ya existen: combinarlos, reordenarlos, cambiarles el mensaje, sacarlos de una rama y meterlos en otra. Son potentes y razonablemente peligrosos: si la historia que estás reescribiendo ya la han visto otros, les vas a desordenar el mundo. La regla general, antes de empezar: **reescribe historia local todo lo que quieras, reescribe historia publicada solo con acuerdo explícito del equipo**.

## `git commit --amend`: editar el último commit

`--amend` no crea un commit nuevo: modifica el anterior. Se usa principalmente para dos cosas.

**Cambiar el mensaje del último commit:**

```bash
git commit --amend -m "Mensaje corregido"
```

O sin `-m`, que te abre el editor con el mensaje actual para que lo edites.

**Añadir algo que se te olvidó:**

```bash
git add fichero-que-olvide.go
git commit --amend --no-edit
```

`--no-edit` conserva el mensaje existente. El fichero se añade al commit anterior como si siempre hubiera estado ahí. Muy cómodo cuando haces commit y te das cuenta de que se te olvidó algo trivial (un archivo generado, una línea de docs, un test).

Cuidado: `--amend` crea un commit **nuevo** por debajo (con nuevo hash), aunque conserva el mismo mensaje y la misma fecha del autor. Si el commit anterior ya estaba pusheado, tras el amend tu rama y la remota divergen. Tendrás que `git push --force-with-lease`. En una rama solo tuya (feature branch abierta solo por ti), no pasa nada. En main o en una rama compartida, problema.

## `git rebase`: mover commits a otra base

`git rebase` toma un conjunto de commits y los reaplica sobre otro punto. Dicho de otro modo: cambia la "base" desde la que parte una rama.

El caso más típico: estás en una rama de feature, `main` ha avanzado mientras trabajabas, y quieres traer tu rama al día sin un merge commit.

```bash
git switch feature/login
git fetch origin
git rebase origin/main
```

Resultado: tus commits se despegan, git aplica los commits nuevos de `origin/main` y luego vuelve a aplicar los tuyos encima. La historia queda lineal, como si hubieras partido de `origin/main` actual desde el principio.

Si hay conflictos durante el rebase, git se para en cada commit que conflicte, te deja marcar los conflictos, y esperas: tras resolver, `git add` y `git rebase --continue`. Si te arrepientes: `git rebase --abort` y vuelves al estado anterior.

La otra modalidad es el rebase interactivo, que es donde rebase se convierte en editor de historia:

```bash
git rebase -i HEAD~5
```

Te abre un editor con los últimos 5 commits y un verbo al lado de cada uno:

```
pick abc1234 Añadir formulario de login
pick def5678 Arreglar typo
pick 9012ghi Validación de email
pick 345jklm WIP trabajando
pick nop6789 Test del formulario
```

Cambias los verbos:

- `pick`: mantener tal cual
- `reword` (r): mantener pero editar el mensaje
- `squash` (s): fusionar con el commit anterior, combinando mensajes
- `fixup` (f): fusionar con el anterior, descartando el mensaje
- `drop` (d): borrar el commit
- `edit` (e): parar en ese commit para modificarlo

Y puedes reordenar las líneas para reordenar los commits. Al guardar y cerrar el editor, git aplica los cambios.

El resultado típico tras un rebase interactivo es una historia limpia: en vez de cinco commits con "WIP", "otra cosa", "ahora sí", tienes dos commits bien titulados que cuentan lo que pasó. Esto es oro cuando alguien hace code review: lee los commits uno a uno y entiende el razonamiento.

**Rebase en ramas compartidas**: solo si has acordado con el equipo que esa rama se reescribe (es común en branches de PR con historial limpio requerido). Para mergear tras rebase propio: `git push --force-with-lease`, nunca `--force` a secas.

## `git cherry-pick`: traer un commit de otra rama

`cherry-pick` coge un commit concreto y lo aplica sobre la rama actual, creando un commit nuevo (mismo contenido, hash distinto).

```bash
git cherry-pick abc123            # un commit concreto
git cherry-pick abc123 def456     # varios
git cherry-pick abc123..def456    # un rango (exclusivo el primero)
git cherry-pick abc123^..def456   # un rango (inclusivo)
```

Cuándo se usa:

**Un fix que hiciste en develop y te hace falta en main ya.** En vez de esperar al próximo merge completo: `git switch main && git cherry-pick abc123`.

**Backport de un fix a una rama de release antigua.** Si mantienes `release/v1` y arreglaste un bug en `main`, cherry-pick ese commit a la rama de release.

**Rescatar commits de una rama que vas a tirar.** Si una rama de experimento tiene dos commits que merecen la pena pero el resto es basura, cherry-pick los dos buenos a otra rama y tira la experimental.

Si hay conflictos, igual que con rebase: resolver, `git add`, `git cherry-pick --continue`. O `--abort` para cancelar.

Flags útiles:

```bash
git cherry-pick -n abc123    # aplica los cambios pero no hace el commit
git cherry-pick -x abc123    # añade al mensaje "(cherry picked from abc123)"
```

El `-x` es útil cuando mantienes varias ramas de release: en el mensaje queda trazabilidad explícita de dónde salió el fix originalmente.

Peligros de cherry-pick:

- Si el commit original modifica luego (rebase, amend), tendrás dos versiones ligeramente distintas del mismo cambio. Trazar eso a mano es un dolor.
- Cherry-pick en serie (traer cincuenta commits uno a uno) es mejor hacerlo con merge o rebase. Cherry-pick es para puntuales.

## Un ejemplo real del día a día

Tienes una rama `feature/carrito`. Llevas seis commits:

```
pick a1 Añadir modelo de Carrito
pick a2 WIP
pick a3 arreglar bug lint
pick a4 Añadir servicio de cálculo
pick a5 Tests servicio cálculo
pick a6 typo
```

Antes del PR, haces rebase interactivo (`git rebase -i main`):

```
pick a1 Añadir modelo de Carrito
fixup a2
fixup a3
pick a4 Añadir servicio de cálculo
fixup a6
pick a5 Tests servicio cálculo
```

Guardas, cierras. Resultado: tres commits limpios:

```
Añadir modelo de Carrito
Añadir servicio de cálculo
Tests servicio cálculo
```

Cada commit es una unidad autocontenida que hace una cosa, testeada, revisable. El reviewer te lo agradece; tu yo de dentro de tres meses buscando en `git log`, también.

## Errores típicos reescribiendo historia

**Hacer force push en main.** Borras trabajo de todos. `--force-with-lease` al menos detecta que había algo nuevo, pero aun así, reescribir main es casi nunca una buena idea.

**Rebase interactivo sin antes hacer backup de la rama.** Si el rebase se te va de las manos, `git reflog` te salva, pero es más tranquilizador hacer antes `git branch backup-feature-carrito` y, si todo explota, `git reset --hard backup-feature-carrito`.

**Cherry-pick sin marcar el origen.** En tres semanas nadie se acordará de por qué existe ese commit. Si vas a cherry-pick entre ramas de release, usa `-x`.

**Amend + force push sin avisar.** Si alguien más tiene esa rama pulleada, al hacer force push les rompes su copia local. Coordinación mínima.

## Lo que viene

Con amend, rebase y cherry-pick puedes dejar la historia de un repo como quieras. Pero tener herramientas para cambiar la historia no sirve de mucho si luego no sabes leerla cuando algo falla. En la **[siguiente entrega](/search?tag=git-avanzado)**, los comandos de investigación: `git bisect`, `git blame` y `git reflog`. Las tres armas para responder a "¿qué demonios pasó aquí?".
