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.
Puedo hacer la
git checkout .
, pero git pago y envío. agrega directorios excluidos por pago escasoHay un
git reset --hard
, pero no me deja hacerlo para un subdirectorio:> git reset --hard . fatal: Cannot do hard reset with paths.
De nuevo: ¿Por qué git no puede hacer restablecimientos duros / blandos por ruta?
Puedo aplicar un parche inverso al estado actual usando
git diff subdir | patch -p1 -R
git diff subdir | patch -p1 -R
, pero esta es una forma bastante extraña de hacer esto.
¿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ónHEAD
.
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:
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:
- Cambie a la rama principal y copie el subdirectorio para reiniciar.
- Ahora vuelva a la rama de características y reemplace el subdirectorio con la copia que acaba de crear en el paso 1.
- 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.