Guía9 min

git submodule para coding agents: gitlink 160000, no un clone extra

Resumen

git submodule inspecciona y actualiza un repo montado. El superproyecto guarda un gitlink 160000, no el árbol. status: - no init, + SHA distinto, U conflicto. update --init clona al SHA del índice en detached HEAD. Un agente no hace add, deinit --all, --remote ni foreach a ciegas. Git 2.50.1.

GitHub
Un superproyecto apunta a un commit fijo de otro repo; el árbol del submódulo no vive en el índice

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 submodule no es un segundo clone. El man (git-submodule(1), Git 2.50.1 / Apple Git-155; git-scm.com/docs/git-submodule HTTP 200, last-modified 2026-08-31; man Git 2.54.0, 2026-04-19) inspecciona, inicializa o actualiza un repositorio montado. gitsubmodules(7) (HTTP 200, last-modified 2026-08-31): el superproyecto guarda un gitlink (modo 160000) con el SHA que espera, más una entrada en .gitmodules. El working tree del submódulo vive aparte; su .git suele ser un archivo que apunta a $GIT_DIR/modules/<name>/.

Sin argumentos, git submodule es status. No clona, no commitea, no mueve HEAD del superproyecto.

Esta guía no sustituye clone (--recurse-submodules sin pathspec inicializa todos) ni rm (quitar el gitlink de verdad). El contrato: qué forma corre un agente, qué prefijo de status significa, y por qué no rellena vendor/ con archivos.

Tres letras, un path

PrefijoSignificado (man status)Qué hace el agente
(espacio)checkout = SHA del índicenada
-no inicializadoupdate --init -- <path>
+checkout ≠ SHA del índiceno “arreglar” con --remote
Uconflicto de merge en el submóduloparar

--cached: imprime el SHA del superproyecto, no el HEAD del submódulo. --recursive: entra a submódulos anidados.

Verificado 2026-09-04 (Git 2.50.1 / Apple Git-155): add stagea 160000 <SHA> 0 <path> (git ls-files -s). Tras el commit, git ls-tree HEAD <path> = 160000 commit <SHA>. Tras deinit <path>, status imprime - + el SHA del gitlink. deinit sin pathspec → Use '--all' if you really want to deinitialize all submodules, 128.

status: espacio alineado, - no init, + SHA distinto

Lo que el agente sí / no corre

QuieroComandoTrampa
¿Hay submódulos?git submodule statusls vendor/ y “falta el código”
Checkout al SHA del índicegit submodule update --init -- <path>--recurse-submodules en clone
SHA registradogit submodule status --cachedgit log dentro de vendor/
Quitar el checkout localgit submodule deinit -- <path>deinit --all / rm -rf vendor
Quitar el gitlink del reporm del pathdeinit “para borrar el submódulo”

Prohibido en autónomo:

  • add. Stagea gitlink + .gitmodules y clona. Verificado: path ya en el índice → already exists in the index, 128. --force (man): bypasea ignore y nombres duplicados (childchild1). Un agente no añade dependencias al árbol.
  • deinit sin pathspec o --all. El man: sin pathspec erra a propósito. Verificado: 128. --all con working tree sucio → contains local modifications; use '-f', 128. -f tira cambios locales.
  • update --remote. El man: usa el remote-tracking branch, no el SHA del gitlink; fetchea antes. Verificado: un commit extra en origin → checkout nuevo y status + (el índice sigue el SHA viejo). Eso sucia el superproyecto.
  • update --force. El man (checkout): git checkout --force en el submódulo; descarta cambios locales aunque el SHA ya coincida.
  • update --rebase / --merge. HEAD del submódulo no queda detached; un conflicto lo deja a medias. Default = checkout detached.
  • foreach con un comando inventado. El man: shell en cada checkout; $name, $sm_path, $sha1, $toplevel. No-cero corta. Verificado: foreach 'false'run_command returned non-zero, 128. foreach 'false || :'0 y sigue. Un agente no evalúa shell a ciegas.
  • set-url / set-branch. Mutan .gitmodules / config y sync el remote. No es lectura.
  • absorbgitdirs. El man: recursivo por default. Mueve el .git del submódulo a $GIT_DIR/modules/.
  • Copiar submodule.<name>.update=!comando a .gitmodules. gitmodules(5): !command no está permitido ahí (seguridad). init tampoco copia un update custom de .gitmodules a .git/config.
  • Rellenar el directorio vacío del gitlink con archivos o con otro clone. El índice espera un commit, no un tree.

update --init sin pathspec respeta submodule.active si está configurado; si no, inicializa todos. Un agente nombra el path.

Receta (60 segundos)

Solo en un worktree propio:

git status -sb
git submodule status
git submodule update --init -- vendor/child
git submodule status

Si status enseña -, faltaba init. Si enseña espacio, el checkout ya es el SHA del índice: no toques. Si enseña +, el working tree del submódulo no es el gitlink: no “alinees” con --remote ni commitees el gitlink salvo que el ticket lo pida.

Tras update --init, el submódulo queda en detached HEAD en el SHA registrado. Verificado: rev-parse --abbrev-refHEAD. No es una rama para push.

update --init clona al SHA del gitlink; HEAD queda detached

Superproyecto vs submódulo

gitsubmodules(7): dos usos (historia independiente, o partir un repo grande). En ambos el superproyecto fija una versión. Traer “lo último de main” es otro commit del gitlink, no un pull dentro de vendor/.

gitmodules(5) (HTTP 200, last-modified 2026-08-31): keys obligatorias path y url. update permitido en el archivo: checkout, rebase, merge, none — no !command. ignore=all|dirty|untracked|none cambia qué muestran status y diff; no cambia el gitlink.

Quitar un submódulo del historial no es deinit. El man de deinit: If you really want to remove a submodule from the repository and commit that use git-rm(1). deinit solo desregistra checkout local y la sección en .git/config.

Checklist

  • Worktree propio. git status -sb. Primero git submodule status.
  • -update --init -- <path>. Espacio → nada. + / U → parar.
  • Cero add, cero deinit --all, cero -f, cero --remote, cero foreach autónomo.
  • Cero rellenar el path del gitlink con archivos.
  • Borrar el submódulo del repo es rm. Clonar el superproyecto es clone.

FAQ

¿ls vendor/child vacío significa que faltan archivos? No. Tras deinit el directorio se limpia; el gitlink sigue en el índice. update --init -- vendor/child restaura el checkout.

¿--recurse-submodules en clone alcanza? El man de clone: sin pathspec inicializa todos (submodule.active=.). Equivale a update --init --recursive al terminar. Nombra el path o no recursa.

¿Puedo commitear dentro del submódulo? Es otro repo. Un commit ahí no mueve el gitlink del superproyecto hasta un add del path. Un agente one-shot no cruza esa frontera.

El curso instalar un agente cubre el loop local. Hub: comparativas y decisiones. submodule no es clone: monta un commit fijo; no crea otro objeto store del superproyecto.