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.
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"
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.
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
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:
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.