Saltar al contenido
← Todos los posts

Qué es un git worktree: cómo funciona por dentro y cuándo usarlo

Qué es un git worktree explicado a fondo: qué guarda git en disco, qué comparte con el repositorio, sus comandos, sus límites y cuándo conviene usarlo.

Ilustración de una carpeta de proyecto y tres copias idénticas, cada una con un cambio propio, todas apoyadas en una misma base compartida

Un git worktree es un directorio de trabajo adicional del mismo repositorio: una segunda carpeta con sus propios archivos, su propia rama activa y su propio staging, que comparte con la carpeta original el historial, las ramas y la configuración. Con él puedes tener main abierta en una carpeta y feature/login en otra al mismo tiempo, sin clonar el repositorio dos veces y sin hacer stash cada vez que cambias de tarea. El comando existe desde git 2.5 (2015), pero es fácil pasarlo por alto porque solo tiene sentido cuando sabes cómo está hecho un repositorio por dentro: qué se comparte entre carpetas y qué no.

Resumen para perezosos
  • Un git worktree es otra carpeta de trabajo del mismo repositorio: tiene su propio HEAD, su índice y sus archivos, pero comparte los commits, las ramas, la configuración y los hooks con el worktree principal.
  • Un git worktree vinculado no tiene una carpeta .git sino un archivo .git de una línea que apunta a .git/worktrees/<nombre>, donde git guarda lo que es propio de ese worktree. Por eso crearlo es rápido y no duplica el historial.
  • Un git worktree tiene dos límites que conviene conocer: una rama solo puede estar activa en un worktree a la vez, y no copia lo que git no versiona (node_modules, .env, builds).

En este artículo:

Qué es un git worktree

Un git worktree es un directorio de trabajo adicional del mismo repositorio, con su propia rama activa, su HEAD y su índice; los commits, las ramas y la configuración se comparten con el worktree principal.

El problema que resuelve es concreto. Un repositorio recién clonado tiene una sola carpeta de trabajo, y en esa carpeta solo puede estar activa una rama a la vez. Si estás a mitad de feature/payments y te piden revisar un bug en main, tienes tres opciones, y ninguna es cómoda: hacer un commit temporal, guardar los cambios con git stash, o clonar el repositorio otra vez en otra carpeta. Las dos primeras te obligan a interrumpir lo que estabas haciendo; la tercera duplica el historial completo y crea un repositorio aparte que no ve tus ramas locales.

git worktree agrega una cuarta opción: añadir una segunda carpeta de trabajo al mismo repositorio. En esa carpeta abres main, arreglas el bug y haces commit, mientras feature/payments sigue intacta en la carpeta original, con sus cambios a medias. Las dos carpetas leen y escriben en la misma base de datos de git, así que el commit que hiciste en una aparece de inmediato en la otra.

Para entender por qué eso funciona, y qué se comparte exactamente, hay que separar las piezas de un repositorio.

Las tres piezas de un repositorio git

Un repositorio git tiene tres piezas: la base de objetos (el historial), las referencias (las ramas y tags que apuntan a ese historial) y el estado de trabajo (la rama activa, el índice y los archivos de la carpeta).

PiezaQué contieneDónde vive
La base de objetosTodos los commits, árboles y contenidos de archivos del historial.git/objects
Las referenciasLos nombres que apuntan a commits: ramas (refs/heads/main), tags, ramas remotas.git/refs y .git/packed-refs
El estado de trabajoQué rama tienes activa (HEAD), qué preparaste para el próximo commit (el índice) y los archivos que ves en la carpeta.git/HEAD, .git/index y el directorio de trabajo

Las dos primeras piezas son el repositorio en sí: el historial y los nombres que apuntan a él. La tercera es tu posición dentro de ese historial: en qué rama estás, qué archivos tienes desplegados y qué cambios preparaste. Un repositorio normal tiene una sola copia de cada pieza, y por eso solo puedes estar en un lugar a la vez.

Un worktree duplica solo la tercera pieza. Cada worktree tiene su propio HEAD, su propio índice y su propia carpeta de archivos, pero todos comparten la base de objetos y las referencias.

Los worktrees tienen nombre según su origen. La carpeta donde clonaste o iniciaste el repositorio, la que contiene la carpeta .git de verdad, es el worktree principal (main worktree). Las que añades con git worktree add son worktrees vinculados (linked worktrees). Funcionan igual para el día a día; la diferencia es que el principal no se puede borrar ni mover con git worktree, porque es donde vive el repositorio.

Worktree, clon, rama o stash: en qué se diferencian

Un worktree se diferencia de un clon, de un checkout y de un stash en qué pieza toca: crea un segundo estado de trabajo sin duplicar el historial ni las referencias. Las otras tres opciones reemplazan el estado de trabajo o duplican el repositorio entero:

Rama + checkoutstashClon nuevoWorktree
Qué hace con el estado de trabajoLo reemplazaLo guarda y lo limpiaCrea otro, en otro repositorioCrea otro, en el mismo repositorio
Varias ramas abiertas a la vezNoNoSíSí
Duplica el historialNoNoSíNo
Un commit se ve en el otro sin push/fetch——NoSí
Ve tus ramas locales——No, solo las que subiste con pushSí, todas
Costo de crearloCasi nuloCasi nuloCopia el historial (un clon local usa hardlinks, pero sigue siendo otro repositorio)Solo escribe los archivos de la rama

La rama y el stash trabajan sobre tu única copia del estado de trabajo: la reemplazan o la vacían. El clon crea un estado de trabajo nuevo, pero dentro de un repositorio independiente, con su propia base de objetos y sus propias referencias; para que un commit pase de uno a otro necesitas push y fetch. El worktree crea un estado de trabajo nuevo dentro del mismo repositorio, así que obtienes la independencia de carpetas del clon sin perder el historial y las ramas compartidas.

Cuándo conviene usar un worktree

Un worktree conviene cuando necesitas dos estados de trabajo vivos a la vez sobre el mismo repositorio. Los casos más comunes:

SituaciónPor qué un worktree y no otra cosa
Un hotfix urgente a mitad de una featureNo tocas tu carpeta ni tus cambios a medias; arreglas en otra y vuelves
Revisar un pull requestAbres la rama del PR en su carpeta, la corres y la pruebas sin perder tu contexto
Comparar dos versionesAbres el tag viejo con --detach y corres las dos a la vez
Una suite de tests o un build largoLo corres en un worktree mientras sigues programando en otro
Varios agentes de código en paraleloCada agente trabaja en su carpeta y su rama sin pisar los archivos de los otros

El último caso es el más reciente. Herramientas como Orca crean un worktree por agente para correr varios a la vez sobre la misma tarea y quedarse con el mejor resultado; en el agente dentro de un repo real muestro cómo aislar una tarea de un agente con un worktree y entregarla como PR. En los dos casos el mecanismo es el que describe este post.

Y no conviene cuando el cambio de contexto es corto y no tienes cambios a medias (un checkout alcanza), o cuando necesitas aislar el repositorio entero, no solo el estado de trabajo: por ejemplo, probar algo que reescribe ramas o la configuración. Ahí un clon separado es más seguro, porque un worktree comparte todo eso.

Qué escribe git en disco cuando creas un worktree

Cuando creas un worktree, git escribe dos cosas: una carpeta de registro en .git/worktrees/<nombre> con el estado propio del worktree, y en la carpeta nueva un archivo .git de una línea que apunta a ese registro. Partimos de un repositorio en ~/app con un commit en main:

cd ~/app
git worktree add -b feature/login ../app-login
# Preparing worktree (new branch 'feature/login')
# HEAD is now at 0e4f78d primer commit

Ese comando hace cuatro cosas:

  1. Crea la rama — refs/heads/feature/login

    Apunta al commit actual, porque no le pasamos otro punto de partida.

  2. Crea el registro interno — .git/worktrees/app-login/

    Una carpeta dentro del .git principal con el estado propio del worktree nuevo.

  3. Crea la carpeta de trabajo — ../app-login/

    Con un archivo .git que apunta al registro interno.

  4. Despliega los archivos — checkout de feature/login

    Escribe en la carpeta nueva los archivos de esa rama y llena su índice.

Lo interesante está en los pasos 2 y 3. En la carpeta nueva no hay una carpeta .git, sino un archivo .git de una sola línea:

$ cat ../app-login/.git
gitdir: /home/ramon/app/.git/worktrees/app-login

Cuando ejecutas cualquier comando de git dentro de app-login, git busca .git, encuentra ese archivo y sigue la ruta. Así sabe que esa carpeta es parte del repositorio de ~/app. Del otro lado, el registro interno contiene pocos archivos:

$ ls .git/worktrees/app-login
HEAD  ORIG_HEAD  commondir  gitdir  index  logs
ArchivoQué guarda
HEADLa rama activa de ese worktree: ref: refs/heads/feature/login
indexEl índice (staging) propio de ese worktree
ORIG_HEADEl commit anterior a la última operación que movió HEAD (aquí lo deja el checkout interno de add)
logs/El reflog de su HEAD: a dónde apuntó y cuándo
commondirLa ruta al repositorio compartido, relativa: ../..
gitdirLa ruta de vuelta al archivo .git de la carpeta de trabajo

Es un vínculo en los dos sentidos: la carpeta de trabajo sabe dónde está su registro (archivo .git), y el registro sabe dónde está su carpeta (archivo gitdir). Gracias al segundo, git worktree list puede mostrar todas las carpetas y git worktree prune puede detectar las que ya no existen.

Git te deja ver esa separación directamente. Desde dentro del worktree vinculado:

cd ../app-login

# Dónde está el estado propio de este worktree
git rev-parse --git-dir
# /home/ramon/app/.git/worktrees/app-login

# Dónde está el repositorio compartido
git rev-parse --git-common-dir
# /home/ramon/app/.git

Fíjate en lo que no está en el registro: no hay objects, no hay refs/heads, no hay config. Todo eso se lee de --git-common-dir. Por eso crear un worktree tarda lo que tarda escribir los archivos de la rama, y no más: el historial ya existe y no se copia.

Qué es propio de cada worktree y qué se comparte

Cada worktree tiene para sí lo que forma su estado de trabajo, y comparte con los demás todo lo que forma el repositorio:

Propio de cada worktreeCompartido por todos
HEAD: qué rama o commit está activoLos objetos: commits, árboles, contenidos (.git/objects)
El índice: lo que tienes en stagingLas ramas y tags (refs/heads, refs/tags)
El reflog de HEADLa configuración (.git/config)
Operaciones en curso: un rebase, un merge, un cherry-pickLos hooks (.git/hooks)
El estado de un git bisect (refs/bisect)El stash (refs/stash)
Los archivos del directorio de trabajoLos remotos y las ramas remotas (refs/remotes)

De esa tabla salen comportamientos que sorprenden la primera vez:

  • Un fetch en un worktree sirve para todos. Las ramas remotas son referencias compartidas, así que después de un git fetch en cualquier carpeta, todas ven origin/main actualizado.
  • Borrar una rama la borra para todos. No hay “ramas de este worktree”: hay ramas del repositorio, y cada worktree tiene una de ellas activa.
  • El stash es uno solo. Un git stash hecho en un worktree aparece en git stash list de los demás y se puede aplicar desde cualquiera.
  • Los hooks y la configuración son los mismos. Todos los worktrees ejecutan el mismo hook de pre-commit, y un cambio en .git/config los afecta a todos. Si necesitas configuración distinta en uno, git lo permite activando extensions.worktreeConfig y usando git config --worktree.
  • Puedes hacer un rebase en un worktree mientras otro está a mitad de un merge. Cada uno tiene su propio estado de operaciones en curso.

Un worktree aísla el estado de trabajo, no el repositorio. Los archivos a medio editar, el staging y la rama activa son de cada carpeta; los commits, las ramas, la configuración y los hooks son de todas.

Un recorrido completo: dos ramas abiertas a la vez

Con lo anterior, este es el flujo de principio a fin. Estás en ~/app, en main, y necesitas trabajar en el login sin tocar tu carpeta:

# 1. Crear el worktree con su rama
git worktree add -b feature/login ../app-login

# 2. Trabajar en él como en cualquier repositorio
cd ../app-login
echo "login" > login.txt
git add login.txt
git commit -m "Agrega login"

# 3. Volver a la carpeta original: el commit ya está ahí
cd ../app
git log --oneline --all
# b31849a (feature/login) Agrega login
# 0e4f78d (HEAD -> main) primer commit

# 4. Ver los dos worktrees y la rama de cada uno
git worktree list
# /home/ramon/app        0e4f78d [main]
# /home/ramon/app-login  b31849a [feature/login]

En el paso 3 no hubo push ni fetch: el commit se escribió en la base de objetos compartida y la rama feature/login se actualizó en las referencias compartidas, así que ~/app lo ve en cuanto existe. Lo que ~/app no ve son los archivos: login.txt no aparece en su carpeta, porque esa carpeta sigue en main. Para integrar el trabajo haces lo mismo que con cualquier rama: git merge feature/login desde main, o un pull request.

Cuando terminas, borras el worktree:

git worktree remove ../app-login
git branch -d feature/login   # la rama sobrevive al worktree; bórrala si ya se mergeó

La regla: una rama solo puede estar en un worktree

Hay una restricción que aparece pronto: git no deja tener la misma rama activa en dos worktrees a la vez. Si feature/login ya está abierta en ../app-login e intentas abrirla en otra carpeta, o hacer checkout de ella en la principal, falla:

$ git checkout feature/login
fatal: 'feature/login' is already used by worktree at '/home/ramon/app-login'

En versiones anteriores de git el mismo error dice fatal: 'feature/login' is already checked out at '…'.

La razón está en las piezas compartidas. La rama es una referencia del repositorio, y hacer commit en una carpeta mueve esa referencia. Si otra carpeta tuviera la misma rama activa, su HEAD apuntaría de pronto a un commit nuevo mientras sus archivos y su índice siguen en el anterior: un estado inconsistente. Git lo evita de entrada. Por el mismo motivo, tampoco te deja borrar una rama que está activa en algún worktree:

$ git branch -d feature/login
error: cannot delete branch 'feature/login' used by worktree at '/home/ramon/app-login'

Se puede saltar la regla con --force, pero no hay un caso normal donde convenga. Si necesitas dos carpetas partiendo del mismo punto, crea una rama distinta para cada una desde el mismo commit, o usa --detach para abrir un commit sin rama.

Los comandos de git worktree

Estos son los comandos de git worktree que cubren el uso diario:

# Crear un worktree con una rama nueva, partiendo de main
git worktree add -b feature/login ../app-login main

# Crear un worktree sobre una rama que ya existe
git worktree add ../app-hotfix hotfix/price

# Abrir un commit o un tag sin rama (útil para probar una versión vieja)
git worktree add --detach ../app-v1 v1.0.0

# Listar los worktrees con su commit y su rama
git worktree list

# Lo mismo en un formato estable, pensado para scripts
git worktree list --porcelain

# Borrar un worktree (se niega si tiene cambios sin commitear)
git worktree remove ../app-login

# Limpiar registros de worktrees cuya carpeta ya no existe
git worktree prune

# Proteger un worktree de prune (por ejemplo, si vive en un disco externo)
git worktree lock --reason "disco externo" ../app-v1
git worktree unlock ../app-v1

# Mover un worktree de carpeta sin romper el vínculo
git worktree move ../app-hotfix ../hotfixes/price

# Reparar los vínculos si moviste carpetas a mano
git worktree repair

Tres detalles de add que conviene saber:

  • Si pasas solo la ruta, sin -b ni rama, git crea una rama nueva con el nombre de la última carpeta: git worktree add ../app-login deja una rama llamada app-login. En un repositorio con convenciones (feature/, fix/), pasa siempre -b.
  • Si pasas el nombre de una rama remota que no existe en local (git worktree add ../app-x feature/x cuando solo existe origin/feature/x), git crea la rama local con seguimiento de la remota.
  • La carpeta va mejor al lado del repositorio (../app-login) que dentro. Si la creas dentro, el worktree principal la ve como una carpeta sin versionar y tienes que añadirla a .gitignore.

Y uno de remove: borra la carpeta y el registro interno, pero no la rama. Los commits siguen en la rama, que puedes mergear o borrar después.

Un patrón que se ve seguido es combinar worktrees con un repositorio bare (sin carpeta de trabajo propia): clonas con git clone --bare <url> app.git y abres cada rama que necesitas como un worktree, por ejemplo git -C app.git worktree add ../main main. Así ninguna rama es “la principal” por estar en la carpeta del repositorio; todas son carpetas al mismo nivel.

Lo que un worktree no copia

Un worktree nuevo contiene exactamente lo que está versionado en la rama. Todo lo que git ignora o nunca vio se queda en la carpeta original. En un proyecto típico eso incluye:

  • Dependencias (node_modules, vendor, .venv): hay que instalarlas en cada worktree. Los gestores con caché global (el store de pnpm, el caché de npm o de pip) reducen el tiempo, pero cada carpeta necesita su instalación.
  • Variables de entorno (.env, .env.local): no están en git, así que el worktree arranca sin ellas. Hay que copiarlas.
  • Artefactos de build (dist, .next, cachés de compilación): empiezan vacíos, así que el primer build en cada worktree es completo.
  • Configuración local del editor que no esté versionada.

Y hay recursos que el worktree no aísla porque no son parte del repositorio:

  • Puertos: si dos worktrees levantan el servidor de desarrollo en el puerto 3000, el segundo falla. Cada uno necesita su puerto.
  • Bases de datos y servicios locales: dos worktrees apuntando a la misma base de datos de desarrollo comparten datos, y si corren migraciones distintas se interfieren.
  • Submódulos: la documentación oficial de git worktree advierte que el soporte para submódulos está incompleto. Cada worktree necesita su propio git submodule update --init, y git worktree remove se niega a borrar un worktree que tiene submódulos inicializados salvo con --force.

“Crear un worktree” casi nunca es solo git worktree add: en un proyecto real también incluye los pasos para que la app arranque en esa carpeta.

Crear y listar worktrees desde un script

Cuando creas worktrees seguido, conviene juntar esos pasos en una función: crea el worktree sobre una rama, copia el .env del principal e instala las dependencias. Una segunda función lista los worktrees leyendo git worktree list --porcelain, que tiene un formato estable para scripts: un bloque por worktree, una línea clave valor por dato y una línea en blanco entre bloques.

// worktrees.ts — crear un worktree listo para trabajar y listar los existentes
import { execFileSync } from "node:child_process";
import { copyFileSync, existsSync } from "node:fs";
import { join } from "node:path";

type Worktree = { path: string; head: string; branch: string | null };

function git(repoDir: string, ...args: string[]): string {
  return execFileSync("git", ["-C", repoDir, ...args], { encoding: "utf8" });
}

export function addWorktree(repoDir: string, branch: string, base = "main"): string {
  // La carpeta va al lado del repositorio, con el nombre de la rama sin barras.
  const path = join(repoDir, "..", branch.replaceAll("/", "-"));
  git(repoDir, "worktree", "add", "-b", branch, path, base);

  // Lo que git no versiona no viene en el worktree: el .env se copia a mano.
  const env = join(repoDir, ".env");
  if (existsSync(env)) copyFileSync(env, join(path, ".env"));

  // Cada worktree necesita su propia instalación de dependencias.
  execFileSync("npm", ["install"], { cwd: path, stdio: "inherit" });
  return path;
}

export function listWorktrees(repoDir: string): Worktree[] {
  // --porcelain: un bloque por worktree, separados por una línea en blanco.
  const blocks = git(repoDir, "worktree", "list", "--porcelain").trim().split("\n\n");
  return blocks.map((block) => {
    const fields = new Map(
      block.split("\n").map((line) => {
        const [key, ...rest] = line.split(" ");
        return [key, rest.join(" ")] as const;
      }),
    );
    return {
      path: fields.get("worktree") ?? "",
      head: fields.get("HEAD") ?? "",
      // Un worktree en modo detached no tiene línea "branch".
      branch: fields.get("branch")?.replace("refs/heads/", "") ?? null,
    };
  });
}
# worktrees.py — crear un worktree listo para trabajar y listar los existentes
import shutil
import subprocess
from dataclasses import dataclass
from pathlib import Path


@dataclass
class Worktree:
    path: str
    head: str
    branch: str | None


def git(repo_dir: str, *args: str) -> str:
    return subprocess.check_output(["git", "-C", repo_dir, *args], text=True)


def add_worktree(repo_dir: str, branch: str, base: str = "main") -> str:
    # La carpeta va al lado del repositorio, con el nombre de la rama sin barras.
    path = Path(repo_dir).parent / branch.replace("/", "-")
    git(repo_dir, "worktree", "add", "-b", branch, str(path), base)

    # Lo que git no versiona no viene en el worktree: el .env se copia a mano.
    env = Path(repo_dir) / ".env"
    if env.exists():
        shutil.copyfile(env, path / ".env")

    # Cada worktree necesita su propia instalación de dependencias.
    subprocess.run(["npm", "install"], cwd=path, check=True)
    return str(path)


def list_worktrees(repo_dir: str) -> list[Worktree]:
    # --porcelain: un bloque por worktree, separados por una línea en blanco.
    blocks = git(repo_dir, "worktree", "list", "--porcelain").strip().split("\n\n")
    result = []
    for block in blocks:
        fields = dict((line.split(" ", 1) + [""])[:2] for line in block.splitlines())
        branch = fields.get("branch")
        result.append(Worktree(
            path=fields.get("worktree", ""),
            head=fields.get("HEAD", ""),
            # Un worktree en modo detached no tiene línea "branch".
            branch=branch.removeprefix("refs/heads/") if branch else None,
        ))
    return result
<?php
// worktrees.php — crear un worktree listo para trabajar y listar los existentes

function run(string $cwd, string ...$cmd): string {
    $parts = implode(" ", array_map("escapeshellarg", $cmd));
    exec("cd " . escapeshellarg($cwd) . " && $parts 2>&1", $output, $code);
    if ($code !== 0) {
        throw new RuntimeException(implode("\n", $output));
    }
    return implode("\n", $output);
}

function addWorktree(string $repoDir, string $branch, string $base = "main"): string {
    // La carpeta va al lado del repositorio, con el nombre de la rama sin barras.
    $path = dirname($repoDir) . "/" . str_replace("/", "-", $branch);
    run($repoDir, "git", "worktree", "add", "-b", $branch, $path, $base);

    // Lo que git no versiona no viene en el worktree: el .env se copia a mano.
    if (file_exists("$repoDir/.env")) {
        copy("$repoDir/.env", "$path/.env");
    }

    // Cada worktree necesita su propia instalación de dependencias.
    run($path, "npm", "install");
    return $path;
}

function listWorktrees(string $repoDir): array {
    // --porcelain: un bloque por worktree, separados por una línea en blanco.
    $blocks = explode("\n\n", trim(run($repoDir, "git", "worktree", "list", "--porcelain")));
    return array_map(function (string $block): array {
        $fields = [];
        foreach (explode("\n", $block) as $line) {
            [$key, $value] = array_pad(explode(" ", $line, 2), 2, "");
            $fields[$key] = $value;
        }
        return [
            "path" => $fields["worktree"] ?? "",
            "head" => $fields["HEAD"] ?? "",
            // Un worktree en modo detached no tiene línea "branch".
            "branch" => isset($fields["branch"]) ? str_replace("refs/heads/", "", $fields["branch"]) : null,
        ];
    }, $blocks);
}

El ejemplo usa npm, pero el paso es el mismo con cualquier gestor: lo que cambia es el comando de instalación. Si tu proyecto levanta un servidor, el siguiente paso natural es que la función también le asigne un puerto distinto a cada worktree.

Limpieza y errores comunes

Los worktrees se acumulan si no los borras al terminar. Estos son los problemas que aparecen con más frecuencia:

SíntomaCausaQué hacer
is already used by worktree at … (o is already checked out at …)La rama ya está activa en otro worktreeUsa otra rama, o borra el worktree que la tiene
git worktree list muestra carpetas que ya no existenBorraste la carpeta con rm -rf en vez de git worktree removegit worktree prune limpia esos registros
remove se niega a borrarEl worktree tiene cambios sin commitear o archivos sin versionarCommitea lo que sirva; si no, git worktree remove --force
Quedan ramas de worktrees ya borradosremove borra la carpeta, no la ramagit branch -d <rama> (o -D si no se mergeó)
El worktree dejó de funcionar tras mover el repositorioEl vínculo usa rutas absolutas por defecto (desde git 2.48 se pueden usar relativas con worktree.useRelativePaths)git worktree repair reescribe los vínculos

Sobre prune: también corre solo, como parte de git gc, y borra los registros de worktrees cuya carpeta desapareció hace más de tres meses (el valor por defecto de gc.worktreePruneExpire). Si un worktree vive en un disco que no siempre está conectado, git worktree lock evita que lo borre.

La regla que evita casi todos estos problemas: borrar siempre con git worktree remove, nunca con rm -rf, y borrar la rama en el mismo momento si ya no la necesitas.

Preguntas frecuentes

¿Qué diferencia hay entre un git worktree y un git clone?

Un clon es un repositorio independiente, con su propia base de objetos y sus propias ramas; para pasar commits de un clon a otro necesitas push y fetch. Un worktree es otra carpeta de trabajo del mismo repositorio: comparte el historial, las ramas y la configuración, así que un commit hecho en un worktree se ve al instante en los demás. El worktree es más rápido de crear y ocupa menos espacio, porque no duplica el historial.

¿Qué es la carpeta .git/worktrees?

La carpeta .git/worktrees es donde git guarda el estado propio de cada worktree vinculado: una subcarpeta por worktree con su HEAD, su índice, su reflog y dos archivos de vínculo (commondir y gitdir). La carpeta de trabajo del worktree no tiene una carpeta .git sino un archivo .git que apunta a esa subcarpeta. No conviene editarla a mano: se administra con git worktree.

¿Puedo tener la misma rama abierta en dos worktrees?

No. Git impide que una rama esté activa en dos worktrees a la vez y falla con el mensaje “is already used by worktree at”. La regla existe porque un commit en un worktree movería la rama y dejaría al otro con archivos que no coinciden con su HEAD. Si necesitas dos carpetas desde el mismo punto, crea una rama distinta para cada una o usa --detach.

¿Cuándo usar git worktree en lugar de git stash?

Git stash conviene cuando la interrupción es corta: guardas los cambios, cambias de rama y los recuperas en la misma carpeta. Git worktree conviene cuando necesitas las dos ramas vivas a la vez, por ejemplo para correr la app o los tests de una mientras sigues editando la otra, porque cada worktree tiene sus propios archivos y su propio índice y no hay que guardar ni restaurar nada.

¿Un worktree copia node_modules y el archivo .env?

No. Un worktree nuevo contiene solo lo que está versionado en la rama. Las dependencias (node_modules, .venv, vendor), los archivos .env y los artefactos de build no se copian, así que hay que instalarlos o copiarlos en cada worktree. Los gestores con caché global, como pnpm, reducen el tiempo de instalación.

¿Cómo borro un git worktree correctamente?

Un git worktree se borra con git worktree remove <ruta>, que borra la carpeta y el registro interno en .git/worktrees. Si ya borraste la carpeta a mano, git worktree prune limpia el registro huérfano. En los dos casos la rama sigue existiendo: bórrala con git branch -d cuando ya no la necesites.

Conclusión

Para probarlo: crea un worktree con git worktree add -b para tu próxima tarea, mira el archivo .git y la carpeta .git/worktrees que dejó, haz un commit y comprueba que lo ves desde la carpeta original. Después anota qué te faltó para que la app arrancara ahí y automatiza esos pasos.

Fuentes

Seguir leyendo