Guía9 min

git replace para coding agents: refs locales que mienten el SHA, no reescritura

Resumen

git replace crea refs/replace/<SHA> para que cat-file y log vean otro objeto del mismo tipo. Listar es seguro. Cero -f/--edit/--graft autónomo. Distinto de rebase, filter-repo, reset y update-ref. Git 2.50.1.

GitHub
Un SHA original y un SHA de reemplazo unidos por refs/replace; el agente solo lista, no escribe

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 replace no reescribe historia. El man (git-replace(1); git-scm.com/docs/git-replace 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): Create, list, delete refs to replace objects. SYNOPSIS mezcla [-f] <object> <replacement>, --edit, --graft, --convert-graft-file, -d y [-l]. La ref vive en refs/replace/<SHA-reemplazado> y su contenido es el SHA del objeto que sustituye al original.

DESCRIPTION: Replacement references will be used by default by all Git commands except those doing reachability traversal (prune, pack transfer and fsck). cat-file, log, show y rev-list mentirán el contenido si hay un replace. fsck y el pack transfer no. Para ver el objeto real: git --no-replace-objects … o GIT_NO_REPLACE_OBJECTS=1.

Contrato para un coding agent: no crees replace autónomo. Un SHA que el humano pegó deja de ser ese objeto. -f permite tipos distintos (commit↔blob). --edit abre editor. --graft fabrica un commit con otros padres. Listar (git replace / -l) es lectura. Borrar (-d) solo si el humano pide deshacer su replace, no el tuyo.

No es rebase: rebase crea commits nuevos y mueve la rama. replace deja los SHA originales y pone una máscara local. No es filter-repo (github.com/newren/git-filter-repo, HTTP 200): filter-repo reescribe el grafo. No es reset: reset mueve HEAD. El man avisa el bug: git reset --hard a un commit reemplazado cae en el replacement, no en el original. No es update-ref: update-ref es CAS de una ref; replace es un namespace aparte.

Qué hace (y qué no)

Cero args o -l: lista. Repo sin replaces → stdout vacío, exit 0. Verificado 2026-09-06.

Un SHA solo: fatal: bad number of arguments, usage, exit 129.

Dos SHA del mismo tipo (commit→commit): exit 0. -l imprime el SHA reemplazado. --format=medium: <old> -> <new>. --format=long: <old> (commit) -> <new> (commit).

Tras git replace <c1> <c2>, cat-file -p <c1> muestra el mensaje y tree de c2, con parent apuntando a c1. El SHA en argv no cambió; el contenido sí. git --no-replace-objects cat-file -p <c1> y GIT_NO_REPLACE_OBJECTS=1 git cat-file -p <c1> muestran c1 de verdad. git log --oneline puede listar ambos SHA con el mensaje del replacement.

Sin -f, repetir el mismo par: error: replace ref 'refs/replace/<sha>' already exists, exit 255. Tipos distintos (commit vs blob de hash-object): error: Objects must be of the same type., exit 255. Con -f, el man This restriction can be bypassed: commit→blob sale 0. Un agente no lo usa: cat-file -p de un commit pasa a devolver un blob.

-d <sha>: Deleted replace ref '<sha>', exit 0. Lista otra vez vacía.

--graft <commit> sin padres: crea un commit nuevo (mismos tree/mensaje, cero parent) y lo pone de replacement. Verificado: cat-file -p del SHA original ya no tiene padre; --no-replace-objects sí. Eso no es un rebase: el SHA viejo sigue en el object store.

--edit con GIT_EDITOR=true (sin cambios): error: new object is the same as the old one, exit 255. Flag inventada: unknown option, exit 129.

Flags que un agente no toca

FlagManAgente
(sin args) / -lLista replace refsSí. Lectura
--format=short|medium|longshort = SHA viejo; medium flecha; long + tiposmedium si el humano pide “quién reemplaza a quién”. Cero dump --format=long de cientos
<object> <replacement>Crea refs/replace/<object>Cero autónomo
-f / --forcePisa ref existente; permite tipos distintosCero
-dBorra replace refsSolo si el humano pide deshacer
--editEditor sobre el objeto; crea replacementCero. Pide TTY
--raw--edit en binario (trees)Cero
--graftCommit clon con otros padresCero. Reescribe parentesco visto
--convert-graft-fileMigra $GIT_DIR/info/grafts (deprecado) y borra el archivoCero

El man: There is no other restriction on the replaced and replacement objects. Merge commits can be replaced by non-merge commits and vice versa. Un replace de un merge a un no-merge rompe lo que log --merges y un PR asumen. No lo “simplifiques”.

Flujo seguro

  1. status porcelain. Replace no ensucia el worktree: git status puede verse limpio y cat-file mentir.
  2. Antes de creer un SHA: git replace -l. Vacío = no hay máscara. Si hay filas, git replace --format=medium y para. Pregunta al humano. No borres a ciegas.
  3. Para inspeccionar el objeto real: git --no-replace-objects cat-file -t <sha> y -p. No pongas GIT_NO_REPLACE_OBJECTS en el entorno del humano de forma permanente.
  4. Cero git replace A B para “arreglar” un commit. Cero --graft para “sacar un padre”. Cero --edit para “cambiar el mensaje”. El humano que quiera reescribir usa rebase o filter-repo, en otra rama, con PR.
  5. Cero git reset --hard <sha-reemplazado>: el man lo marca como bug. HEAD cae en el replacement.

Lista corta de refs/replace; el agente no crea la máscara

Checklist:

  • Autónomo: solo -l / --format=medium.
  • Un arg = 129. Dos args = escribe. No adivines.
  • Cero -f. Cero --edit. Cero --graft. Cero --convert-graft-file.
  • cat-file / log con replace activo no son la verdad del object store.
  • --no-replace-objects para ver. No para “arreglar” borrando refs.
  • fsck no usa replace. Un fsck limpio no prueba que no hay máscara.

Qué no es (tabla)

VerboUnidadEscribeVerdad del SHA
replaceun objetorefs/replace/ localel SHA sigue; el contenido no
rebasecommits de una ramanuevos SHA + mueve ramael SHA viejo queda atrás
filter-repohistorialreescribe el grafoSHA nuevos
reset --hardHEAD + index + WTmueve la ramabug si el target está replaced
update-refuna refCAS de SHAno enmascara objetos
notesanotaciónrefs/notes/el commit no cambia

replace no viaja en un push normal a menos que alguien empuje refs/replace/*. En un clone fresco no está. Por eso un agente que “arregló” un commit con replace miente solo en su checkout. CI y el PR ven otra cosa.

BUGS del man (no los “trabajes”): comparar blobs/trees replaced vs replacement no funciona bien; rev-list puede liar pending objects. Si el humano pide diagnóstico, lista y para.

cat-file con --no-replace-objects al lado del SHA enmascarado

FAQ

¿Lo uso para ocultar un secreto que ya está en un commit? No. El objeto original sigue en el store. fsck y --no-replace-objects lo ven. filter-repo + rotar el secreto. replace es teatro local.

¿--graft es el sucesor de info/grafts? Sí, y --convert-graft-file migra y borra el archivo. Un agente no convierte grafts ajenos.

¿Puedo pushear refs/replace para que el equipo vea lo mismo? No autónomo. Es una mentira coordinada. Si el humano insiste, que lo haga a mano y documente. El default de fetch no trae ese namespace.

¿git show-ref lista replace? show-ref ve refs/replace/ si existe. Prefiere git replace -l: es el porcelain del namespace.

¿Es plumbing de notes? No. notes anotan. replace sustituye lo que leen casi todos los comandos.

Si estás armando el agente desde cero, el curso de instalar un agente cubre el loop de tools. El hub de comparativas y decisiones agrupa el resto de verbos Git. Esta guía es la regla cuando un SHA “cambia de contenido” sin cambiar de id: lista, no fabriques la máscara.