Guía9 min

git patch-id para coding agents: huella del diff, no del SHA

Resumen

git patch-id lee un parche por stdin y emite una huella SHA-1 del diff (sin números de línea). Default --unstable. --stable ignora el orden de archivos. --verbatim no recorta whitespace. Cero autónomo como filtro de historia. Distinto de cherry, apply y diff. Git 2.50.1.

GitHub
Un coding agent calcula la huella de un parche por stdin; el SHA del commit queda aparte

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

git patch-id calcula un identificador del parche, no del commit. El man (git-patch-id(1); git-scm.com/docs/git-patch-id HTTP 200, last-modified 2026-08-31; pie del man local Git 2.50.1.428.g0e8243, 2025-07-22; binario Git 2.50.1 / Apple Git-155): Compute unique ID for a patch. SYNOPSIS: git patch-id [--stable | --unstable | --verbatim]. DESCRIPTION: lee un parche desde stdin y suma SHA-1 de los file diffs ignorando números de línea. Dos parches con el mismo patch ID are almost guaranteed to be the same thing. El usecase del man: look for likely duplicate commits.

Contrato para un coding agent: stdin de un diff, una línea de salida, parar. No es cherry: cherry recorre una rama y marca +/. Aquí no hay upstream ni HEAD. No es apply ni am: no pega nada. No es diff: diff produce el texto; patch-id lo consume.

No toca índice, working tree ni refs. Funciona fuera de un repo: el objeto vive en el texto del parche.

Qué hace (y qué no)

La salida son dos hex de 40 caracteres. El primero es el patch ID. El segundo es el commit ID si el stdin trae el prefijo de objeto que diff-tree / git show / format-patch ponen delante. Si el stdin es un git diff puro, el segundo campo es cuarenta ceros.

Verificado 2026-09-06 (Git 2.50.1 / Apple Git-155) en un repo mínimo base → commit que añade line2 a a.txt:

  • stdin vacío: 0, cero líneas.
  • git show HEAD | git patch-id: 0. Dos hex. El segundo = SHA de HEAD.
  • git diff HEAD~1 HEAD | git patch-id: 0. Mismo primer hex. Segundo = 0000…0.
  • git format-patch -1 --stdout | git patch-id: 0. Igual que show.
  • git diff-tree -p HEAD | git patch-id: 0. Igual que show (el man habla de esta forma).
  • git diff-tree HEAD sin -p (raw): 0, vacío. Sin hunks no hay huella.
  • --foo: 129, unknown option `foo'.
  • Fuera de un repo (/tmp): 0, misma huella. No exige .git.
  • Un extra posicional (git patch-id HEAD con el parche en stdin): 0, ignora el argumento. No hashea HEAD. No leas un path.

Algoritmos (man):

FlagDefaultWhitespaceOrden de file diffs
--unstable (salvo config)se ignorael orden cambia el ID
--stablesi patchid.stable=truese ignorael orden no cambia el ID
--verbatimsi patchid.verbatim=trueno se recortacomo el input

Verificado en el mismo commit de un archivo: --stable y --unstable coinciden. --verbatim diverge. Trailing spaces en la línea añadida: default y --stable siguen iguales; --verbatim cambia. Commit de dos archivos, mismos hunks en orden inverso: default/--unstable divergen; --stable coincide.

El man avisa: --stable no es el valor de Git ≤ 1.9. Una base de datos vieja de patch-ids se vuelve inútil si mezclas algoritmos. Un agente no “elige el más moderno” a ciegas.

El agente lee el parche por stdin y compara huellas; no abre una GUI ni pisa el disco

Lo que el agente sí / no corre

QuieroComandoTrampa
Huella de un commitgit show <sha> | git patch-idgit patch-id <sha> (el SHA se ignora)
Huella de un diff de trabajogit diff -- <path> | git patch-idvolcar el diff al LLM “para que adivine duplicados”
Comparar dos seriesdos pipes, comparar el primer hextratar el segundo hex (commit) como la huella
Duplicados vs upstreamcherry -v <upstream> <head>reinventar cherry a mano con patch-id en un loop
Aplicar el parcheapply --check primeropatch-id no pega
Orden de archivos irrelevante--stable explícitoasumir que el default ya es estable

Prohibido en autónomo:

  • Invocarlo sin stdin de un diff concreto. Verificado: vacío → exit 0 y silencio. Un agente no interpreta “no imprimió nada” como “no hay duplicados”.
  • Pasar un SHA o un path como argumento. Verificado: se ignora. El contrato es pipe, no argv.
  • --verbatim para “ser más fiel”. Cambia la huella con whitespace trivial. El default del man ignora whitespace; --verbatim es la excepción.
  • Cambiar patchid.stable / patchid.verbatim en config global. El flag va en esa invocación.
  • Mezclar --stable y --unstable en la misma tabla de duplicados. Son IDs distintos para el mismo parche reordenado.
  • Meter la línea entera en el contexto del modelo “para decidir un rebase”. Compara hex en el harness. El LLM no es un ==.
  • Encadenar a cherry-pick porque “el ID coincidió”. Equivalencia de parche ≠ el ticket. Un duplicado puede ser WIP, secreto o un revert a medias.
  • Usar diff-tree raw (sin -p) como input. Verificado: vacío.
  • Dump de git show -p de toda la rama a patch-id “por si acaso”. Un commit por pipe, o usa cherry.

Receta (60 segundos)

Solo en un worktree propio. Stdin explícito:

git show <sha> | git patch-id
git diff <tree-ish> <tree-ish> -- <path> | git patch-id --stable

Lee el primer campo. Si vas a comparar dos diffs cuyo orden de archivos puede cambiar (-O, paths distintos), usa --stable en ambos. Si comparas git show contra git diff del mismo cambio, el primer hex coincide y el segundo no: eso no es un bug.

Para “qué de topic ya está en main”, no armes este loop. Corre cherry. cherry ya usa esta huella.

Checklist

  • El input es un parche (show / format-patch / diff-tree -p / diff), no un SHA en argv.
  • Exit 0 con una línea, o 0 vacío (stdin vacío / raw sin hunks). 129 = flag inventado.
  • Comparas el primer hex. El segundo es commit o ceros, no la huella.
  • Mismo algoritmo a ambos lados (--stable o default). Cero mezcla.
  • Cero --verbatim “por precisión”.
  • Cero config global patchid.*.
  • Cero apply / am / cherry-pick porque coincidió.
  • Duplicados de rama → cherry, no este binario en un for.
  • La línea no entra al prompt del modelo.

FAQ

¿Es lo mismo que git cherry? No. cherry lista commits de una rama contra upstream con +/. patch-id es el motor de huella de un solo parche por stdin. SEE ALSO del man de cherry apunta aquí.

¿Por qué git diff \| git patch-id pone ceros a la derecha? Porque ese stdin no trae el nombre de objeto del commit. El man lo documenta para git diff-tree con prefijo. Ceros ≠ error.

¿--stable siempre? No. Es distinto de Git 1.9 / default --unstable. Úsalo cuando el orden de file diffs puede cambiar. No lo pongas default del repo.

¿Puedo hashear un archivo .patch con git patch-id file.patch? No hay argv de path. cat file.patch | git patch-id. Un posicional se ignora y, si stdin está vacío, sales 0 en silencio.

¿Sirve para saber si el working tree está dirty? No. Eso es git diff --quiet. patch-id no mira el disco salvo lo que le pipes.

Fuentes

  • git-patch-id(1) — HTTP 200, last-modified 2026-08-31. Man local Git 2.50.1.428.g0e8243 (2025-07-22).
  • git-cherry(1) — HTTP 200, last-modified 2026-08-31. Equivalencia de parche vs SHA.
  • git-diff(1) — HTTP 200, last-modified 2026-08-31. Productor del texto que patch-id consume.
  • git-diff-tree(1) — HTTP 200, last-modified 2026-08-31. Prefijo de commit + -p.
  • GitHub Docs — About Git — HTTP 200, verificado 2026-09-06.

Hub: comparativas y decisiones. Curso: instalar un agente.

--stable agrupa el mismo cambio aunque los archivos salgan en otro orden; el default no