git reset git-reset sparse-checkout

Cómo restablecer git--hard un subdirectorio?



reset git-reset (7)

De acuerdo con el desarrollador de Git, Duy Nguyen, que amablemente implementó la función y un conmutador de compatibilidad , los siguientes funcionan como se esperaba a partir de Git 1.8.3 :

git checkout -- a

(donde a es el directorio que desea restablecer). El comportamiento original se puede acceder a través de

git checkout --ignore-skip-worktree-bits -- a

ACTUALIZACIÓN : Esto funcionará más intuitivamente a partir de Git 1.8.3, ver mi propia respuesta .

Imagine el siguiente caso de uso: deseo deshacerme de todos los cambios en un subdirectorio específico de mi árbol de trabajo de Git, dejando intactos todos los demás subdirectorios.

¿Cuál es el comando correcto de Git para esta operación?

El script a continuación ilustra el problema. Inserte el comando adecuado debajo de How to make files comentario de How to make files : el comando actual restaurará el archivo a/c/ac que se supone que será excluido por el proceso de pago disperso. Tenga en cuenta que no quiero restaurar explícitamente a/a y a/b , solo "sé" a y quiero restaurar todo a continuación. EDITAR : Y tampoco "sé" b , o qué otros directorios residen en el mismo nivel que a .

#!/bin/sh rm -rf repo; git init repo; cd repo for f in a b; do for g in a b c; do mkdir -p $f/$g touch $f/$g/$f$g git add $f/$g git commit -m "added $f/$g" done done git config core.sparsecheckout true echo a/a > .git/info/sparse-checkout echo a/b >> .git/info/sparse-checkout echo b/a >> .git/info/sparse-checkout git read-tree -m -u HEAD echo "After read-tree:" find * -type f rm a/a/aa rm a/b/ab echo >> b/a/ba echo "After modifying:" find * -type f git status # How to make files a/* reappear without changing b and without recreating a/c? git checkout -- a echo "After checkout:" git status find * -type f


Intenta cambiar

git checkout -- a

a

git checkout -- `git ls-files -m -- a`

Desde la versión 1.7.0, los ls-files de Git rinden homenaje al indicador skip-worktree .

Ejecutando su script de prueba (con algunos ajustes menores cambiando el git commit ... a git commit -q y git status a git status --short ) salidas:

Initialized empty Git repository in /home/user/repo/.git/ After read-tree: a/a/aa a/b/ab b/a/ba After modifying: b/a/ba D a/a/aa D a/b/ab M b/a/ba After checkout: M b/a/ba a/a/aa a/c/ac a/b/ab b/a/ba

Ejecutando su script de prueba con los resultados de cambio de checkout propuestos:

Initialized empty Git repository in /home/user/repo/.git/ After read-tree: a/a/aa a/b/ab b/a/ba After modifying: b/a/ba D a/a/aa D a/b/ab M b/a/ba After checkout: M b/a/ba a/a/aa a/b/ab b/a/ba


La answer Ajedi32 es lo que estaba buscando, pero para algunas confirmaciones encontré este error:

error: cannot apply binary patch to ''path/to/directory'' without full index line

Puede ser porque algunos archivos del directorio son archivos binarios. Agregar la opción ''--binary'' al comando git diff lo solucionó:

git diff --binary --cached commit -- path/to/directory | git apply -R --index


Nota (como lo commented Dan Fabulich ) que:

  • git checkout -- <path> no hace un restablecimiento completo: reemplaza el contenido del árbol de trabajo con el contenido en etapas.
  • git checkout HEAD -- <path> hace un restablecimiento completo de una ruta, reemplazando tanto el índice como el árbol de trabajo con la versión de la confirmación HEAD .

Como Ajedi32 answered , ambos formularios de pago no eliminan los archivos que se eliminaron en la revisión de destino .
Si tiene archivos adicionales en el árbol de trabajo que no existen en HEAD, un git checkout HEAD -- <path> no los eliminará.

Pero ese pago puede respetar un git update-index --skip-worktree (para aquellos directorios que desea ignorar), como se menciona en " ¿Por qué los archivos excluidos siguen reapareciendo en mi salida git update-index --skip-worktree ? ".


Para el caso de simplemente descartar cambios, la git checkout -- path/ o git checkout HEAD -- path/ comandos sugeridos por otras respuestas funcionan muy bien. Sin embargo, cuando desea restablecer un directorio a una revisión que no sea HEAD, esa solución tiene un problema importante: no elimina los archivos que se eliminaron en la revisión de destino.

Entonces, en cambio, comencé a usar el siguiente comando:

git diff --cached commit -- subdir | git apply -R --index

Esto funciona al encontrar la diferencia entre el compromiso de destino y el índice, y luego aplicar esa diferencia al reverso del directorio y el índice de trabajo. Básicamente, esto significa que hace que los contenidos del índice coincidan con los contenidos de la revisión que especificó. El hecho de que git diff tome un argumento de ruta le permite limitar este efecto a un archivo o directorio específico.

Dado que este comando es bastante largo y planeo usarlo con frecuencia, he configurado un alias para el que he llamado reset-checkout :

git config --global alias.reset-checkout ''!f() { git diff --cached "$@" | git apply -R --index; }; f''

Puedes usarlo así:

git reset-checkout 451a9a4 -- path/to/directory

O solo:

git reset-checkout 451a9a4


Si el tamaño del subdirectorio no es particularmente grande, Y desea mantenerse alejado de la CLI, aquí hay una solución rápida para restablecer manualmente el subdirectorio:

  1. Cambie a la rama principal y copie el subdirectorio para reiniciar.
  2. Ahora vuelva a la rama de características y reemplace el subdirectorio con la copia que acaba de crear en el paso 1.
  3. Confirma los cambios

Aclamaciones. ¡Simplemente restablece manualmente un subdirectorio en su rama de características para que sea el mismo que el de la rama principal!


Un reinicio normalmente cambiará todo, pero puede usar git stash para elegir lo que quiere conservar. Como mencionaste, el stash no acepta una ruta directamente, pero aún se puede usar para mantener una ruta específica con el --keep-index . En su ejemplo, ocultaría el directorio b y luego restablecería todo lo demás.

# How to make files a/* reappear without changing b and without recreating a/c? git add b #add the directory you want to keep git stash --keep-index #stash anything that isn''t added git reset #unstage the b directory git stash drop #clean up the stash (optional)

Esto te lleva al punto donde la última parte de tu script dará como resultado esto:

After checkout: # On branch master # Changes not staged for commit: # # modified: b/a/ba # no changes added to commit (use "git add" and/or "git commit -a") a/a/aa a/b/ab b/a/ba

Creo que este fue el resultado objetivo (b sigue siendo modificado, a / * los archivos están de vuelta, el aire acondicionado no se recrea).

Este enfoque tiene la ventaja adicional de ser muy flexible; puede obtener todo lo detallado que desee agregando archivos específicos, pero no otros, en un directorio.