·4 min de lectura·Mounaji Studio

Saber si un PR viejo ya está en main, aunque entrara con squash

Con squash o rebase, git branch --merged, el conteo de commits y git cherry dicen que una rama tiene trabajo pendiente cuando ya entró. Cómo preguntarle al contenido con git merge-tree, detectar la rama que haría retroceder la base, y archivar antes de borrar.

engineeringgitgithub

También en: English

Tienes ochenta PRs abiertos y la mitad tiene meses. Muchos ya entraron: alguien los mergeó con squash, o copió los cambios en otro PR, y la rama quedó colgada. Cerrarlos uno por uno leyendo el diff no escala. Preguntarle a git parece fácil, y ahí está la trampa: las tres preguntas de siempre miran commits, y con squash los commits de la rama nunca llegan a la base.

Lo que falla, en un repo de juguete

Una rama feature con dos commits, mergeada con git merge --squash. Su contenido ya está entero en main. Esto contesta git:

git branch --merged main        # * main        → feature no aparece
git rev-list --count main..feature   # 2         → "2 commits pendientes"
git cherry main feature         # + a48b573 / + 89b678e → "2 parches nuevos"

Una rama mergeada con squash ante cuatro preguntas. branch --merged, el conteo de commits y git cherry dicen que falta trabajo; sólo merge-tree, que compara contenido, contesta que ya está adentro.

git cherry compara patch-id, una huella de cada parche. El squash junta varios parches en uno y la huella cambia, así que el original nunca figura como aplicado. En una muestra real de tres ramas squasheadas acertó en una.

La pregunta correcta: ¿mergearla cambia algo?

No importa qué commits tiene la rama; importa qué archivos quedarían si la mergearas. git merge-tree --write-tree (git 2.38+) hace el merge en memoria, sin tocar tu checkout, y devuelve el árbol resultante. Si es igual al árbol de la base, la rama no aporta nada:

git merge-tree --write-tree main feature   # be43f2a…
git rev-parse 'main^{tree}'                # be43f2a…  → idénticos: ya está adentro

Funciona igual con squash, rebase o un cherry-pick a mano, porque no mira la historia: mira el resultado.

Árbol de decisión: si el árbol de merge-tree es igual al de la base, archivar y cerrar. Si no, y cada archivo ya existió en la base, la base avanzó por encima: no mergear. Si algún archivo es nuevo, revisar a mano.

El caso que engaña: la base avanzó por encima

Hay ramas que sí cambian el árbol y aun así no tienen nada nuevo. La rama cambió b por B, se mergeó con squash, y después alguien en main lo retocó a B2. Ahora merge-tree da otro árbol (o un conflicto) y el diff parece trabajo pendiente:

-B2
+B

Línea de tiempo: la rama cambia b por B y entra con squash; después main cambia B por B2. El diff entre main y la rama muestra B2 → B, un retroceso, no trabajo pendiente.

No es trabajo: es una versión vieja de algo que ya entró. Mergearla haría retroceder main. Para separarlo, pregunta por cada archivo que tocó la rama si esa versión exacta existió alguna vez en la base después del punto donde se separaron:

mb=$(git merge-base main rama)
for f in $(git diff --name-only "$mb" rama); do
  blob=$(git rev-parse "rama:$f")
  if git log --format=%H "$mb..main" -- "$f" | while read c; do
       [ "$(git rev-parse "$c:$f" 2>/dev/null)" = "$blob" ] && echo hit; done | grep -q hit
  then echo "ya estuvo en main: $f"
  else echo "NUEVO: $f"; fi
done

Si todos los archivos dicen "ya estuvo", la rama no aporta y no se mergea. Si alguno dice "NUEVO", eso sí hay que mirarlo a mano. Comparar sólo contra la punta de main no alcanza: hay que buscar en la historia.

Antes de borrar, un tag

Una rama squasheada tiene commits que no existen en ningún otro lugar. Si la borras, el contenido sigue en main pero la historia se pierde, y si te equivocaste, también el contenido. Un tag cuesta nada:

Cuatro pasos en orden: crear el tag archivo con la punta de la rama, subirlo, comprobar con ls-remote que existe en el remoto, y recién entonces borrar la rama. Si el tag no quedó, la rama no se toca.

git tag "archivo/2026-10-17/feature" origin/feature
git push origin "archivo/2026-10-17/feature"
git ls-remote --tags origin "archivo/2026-10-17/feature"   # comprobar que quedó
git push origin --delete feature                          # recién ahora

Y una regla para automatizarlo: si no pudiste medir (un error de red, un diff truncado), el veredicto es indeterminado y no se borra. "No sé" nunca puede caer del lado irreversible.

Para empezar hoy: elige el PR abierto más viejo que tengas, corre git merge-tree --write-tree contra su base y compara el árbol. Si da igual, ya sabes qué hacer con los otros setenta y nueve.

Comentarios y correcciones

Chat with us