Guía9 min

git mv para coding agents: index y disco, no /bin/mv

Resumen

git mv renombra o mueve un path tracked y actualiza el index. El destino no puede existir salvo -f; el directorio padre tiene que existir. Un agente nombra paths, corre -n primero y no usa -f, -k ni /bin/mv. Git 2.50.1.

GitHub
Un path tracked se mueve a un destino nuevo; el index queda en R, no D+untracked

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 mv no es /bin/mv. El man (git-mv(1), Git 2.50.1 / Apple Git-155; git-scm.com/docs/git-mv HTTP 200, last-modified 2026-08-31) mueve o renombra un file, directorio o symlink. Primera forma: <source> (tiene que existir) → <destination>. Segunda: varios sources → un <destination-directory> que ya existe. El index se actualiza al terminar; el cambio sigue sin commit.

GitHub Docs (Moving a file to a new location, HTTP 200 2026-09-04): en la UI hace falta permiso de escritura; si no lo tienes, GitHub te ofrece fork + PR. En una rama protegida no editas ni subes desde la web. El ejemplo de línea de comandos usa /bin/mv + git add . y deja deleted: + untracked. Contrato de agentes: git mv, paths nombrados, -n primero, cero -f/-k autónomo.

Esta guía no sustituye rm (sacar del index) ni add (copiar working tree → index). El contrato: qué forma corre un agente, qué flags están prohibidas, y por qué mv de disco no es git mv.

Index y disco no son lo mismo

/bin/mv de un tracked deja D old + ?? new. Git no infiere el rename hasta que alguien stagea ambos lados. git mv escribe R old -> new en el index en un paso.

El directorio padre del destino tiene que existir. Verificado 2026-09-04: git mv src/foo.ts src/lib/foo.ts con src/lib ausente → No such file or directory, exit 128. mkdir primero, o mueve a un dir que ya está.

Destino ocupado: el man se niega (destination exists) salvo -f. -k salta el error y sale 0: un untracked, un overwrite, un path que Git no controla. En headless eso esconde el fallo.

-n lista el rename; el working tree no se toca

Lo que el agente sí / no corre

QuieroComandoTrampa
Renombrar un path trackedgit mv -- old.ts new.ts/bin/mv + git add . del ejemplo de GitHub
Mover a un dir existentegit mv -- a.ts b.ts dest/dest/ no existe
Ver qué haríagit mv -n -- old.ts new.ts-n + -f “por si choca”
Sacar el pathrmgit mv a /tmp “para borrar”

Prohibido en autónomo:

  • -f / --force. El man: even if the destination exists. Verificado: git mv -f other.ts keep.ts pisa keep.ts (sale M keep.ts + D other.ts, no un R limpio). Un agente no pisa un path tracked para “ganar el rename”.
  • -k. Skip de errores. Verificado: git mv -k z.ts z2.ts con z.ts untracked → exit 0 y cero movimiento. El ticket parece hecho.
  • /bin/mv + git add .. GitHub Docs lo enseña para humanos (imágenes que la UI no mueve). Un agente nombra paths (add); no globa el árbol.
  • Destino cuyo padre no existe. Crea el dir vacío a propósito, o para: el man no crea padres.
  • Mover un untracked. Verificado: not under version control, exit 128. Primero add, o no está en el ticket.
  • Submodule git mv autónomo. El man: con gitfile (≥ 1.7.8) actualiza el gitfile, core.worktree y submodule.<name>.path en .gitmodules (salvo -n). BUG documentado: al cambiar entre commits pre/post-move queda un checkout stale en el path viejo y un dir vacío en el nuevo; hay que git submodule update. Borrar el dir viejo solo es seguro con gitfile; si no, tiras la historia del submodule. Un agente no mueve submodules.
  • git mv de un glob sin comillas a un dir “porque son muchos”. Segunda forma: todos los sources van dentro del dir destino. Un typo de destino (file en vez de dir) no es un lote.

-v / --verbose imprime Renaming a to b. En headless déjalo: quieres ver la línea.

Receta (60 segundos)

Solo en un worktree propio:

git status -sb
git mv -n -- src/lib/foo.ts src/lib/bar.ts
git mv -- src/lib/foo.ts src/lib/bar.ts
git diff --cached --stat

Varios files a un directorio existente:

mkdir -p src/core
git mv -n -- src/a.ts src/b.ts src/core
git mv -- src/a.ts src/b.ts src/core

Cierre = commit + PR. Cero push a main.

-f pisa el destino; /bin/mv deja D + untracked

UI de GitHub vs clone local

GitHub Docs: en el filename field, carpeta/ crea un subfolder; ../ sube un nivel. Eso es un commit en GitHub, no en tu worktree. Un agente que “ya lo movió en la UI” deja el clone sucio al revés: el path viejo sigue en disco hasta el próximo fetch/pull.

Si tu rama actual es la default, la misma página pide rama nueva + pull request. Un agente no commitea en main (branch, push, PR).

Algunos files (imágenes) la UI no los mueve: hay que línea de comandos. Eso no autoriza git add .. git mv -- path/old.png path/new.png.

Verificado 2026-09-04 en esta máquina (Git 2.50.1 / Apple Git-155): git mv -n old.ts new.ts imprime Checking rename y no mueve (exit 0). Destino existente sin -f → exit 128. Padre ausente → exit 128. Untracked → exit 128. -k sobre untracked → exit 0 y el file no se mueve.

Checklist

  • Worktree propio. git status -sb. Paths del ticket, no el árbol.
  • git mv -n -- <src> <dst> antes del mv real.
  • Padre del destino existe. Cero -f, cero -k, cero /bin/mv, cero git add ..
  • Untracked → add primero, o no es este comando.
  • Submodule → para. No git mv autónomo.
  • Index staged → commit + PR.

FAQ

¿mv archivo y luego commit? GitHub Docs stagea con git add . tras el mv de disco. Un agente no usa .. Si el disco ya se movió: git add -- old-path new-path (el add de un delete + untracked puede detectar R). Mejor: no uses /bin/mv.

¿git mv dir/? El man mueve el directorio y sus tracked debajo. Sigue siendo un rename en el index. Nómbralo si todo el dir es el ticket; no “por si acaso”.

¿Case rename (foo.tsFoo.ts)? En APFS (case-insensitive) git mv lo registra como R. Verificado 2026-09-04: exit 0, R foo.ts -> Foo.ts. Sigue necesitando commit. No uses -f.

¿Puedo cambiar el contenido en el mismo commit? GitHub Docs sí lo permite (mover + editar). El index entonces deja de mostrar un R puro (D + A si el blob cambió). Un agente: un commit de rename, otro de edit, o paths explícitos en el mismo commit si el ticket lo pide. Cero git add ..

El curso instalar un agente cubre el loop local. Hub: comparativas y decisiones. git mv no es rm: mueve tracked; no lo saca del árbol.