Cómo git reset hard hard un subdirectorio?


ACTUALIZAR: Esto funcionará de forma más intuitiva a partir de Git 1.8.3, ver mi propia respuesta.

Imagine el siguiente caso de uso: Quiero 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 Git adecuado para esta operación?

El siguiente script ilustra el problema. Inserte el comando apropiado debajo del comentario How to make files the el comando actual restaurará el archivo a/c/ac que se supone que debe ser excluido por el sparse Checkout. Tenga en cuenta que yo no quiero restaurar explícitamente a/a y a/b, solo "sé" a y quiero restaurar todo lo que aparece 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
Author: Community, 2013-03-14

8 answers

Nota (como comentó por Dan Fabulich) que:

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

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

Pero ese checkout 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 git sparse checkout?".

 92
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:26

Según el desarrollador de Git Duy Nguyen, quien amablemente implementó la característica y un interruptor de compatibilidad , lo siguiente funciona como se esperaba a partir de Git 1.8.3:

git checkout -- a

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

git checkout --ignore-skip-worktree-bits -- a
 84
Author: krlmlr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-05 15:53:45

Intenta cambiar

git checkout -- a

A

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

Desde la versión 1.7.0, Git ls-files honra la bandera skip-worktree .

Ejecutando su script de prueba (con algunos ajustes menores cambiando 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 las salidas de cambio propuestas checkout:

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
 23
Author: Dan Cruz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-16 22:33:51

Para el caso de simplemente descartar cambios, los comandos git checkout -- path/ o git checkout HEAD -- path/ 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 significativo: no elimina los archivos que se eliminaron en la revisión de destino.

Así que en su lugar, he comenzado a usar el siguiente comando:

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

Esto funciona encontrando el diff entre la confirmación de destino y el índice, luego aplicando ese diff en sentido inverso al directorio de trabajo y al índice. Básicamente, esto significa que hace que el contenido del índice coincida con el contenido de la revisión que especificó. El hecho de que git diff tome un argumento path 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 él que he nombrado 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 simplemente:

git reset-checkout 451a9a4
 15
Author: Ajedi32,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-16 16:45:17

Un reinicio normalmente cambiará todo, pero puede usar git stash para escoger lo que quieres conservar. Como mencionaste, stash no acepta una ruta directamente, pero aún se puede usar para mantener una ruta específica con la bandera --keep-index. En tu ejemplo, esconderías el directorio b y luego restablecerías 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 a un punto donde la última parte de tu script producirá 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 permanece modificado, a / * archivos están de vuelta, a / c no se ha recreado).

Este enfoque tiene la ventaja añadida de ser muy flexible; puede obtener todo el grano que desee agregando archivos específicos, pero no otros, en un directorio.

 4
Author: Jonathan Wren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-17 01:37:22

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 manualmente restablecer el subdirectorio:

  1. Cambie a la rama master y copie el subdirectorio a restablecer.
  2. Ahora vuelva a su rama de características y reemplace el subdirectorio con la copia que acaba de crear en el paso 1.
  3. Confirme los cambios.
Salud. Simplemente restablece manualmente un subdirectorio en su rama de características a ¡sea igual que el de la rama maestra !!
 2
Author: Mehul Parmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-16 17:24:12

Ajedi32 ' s respuesta es lo que estaba buscando, pero para algunos commits me encontré con 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. Al agregar la opción 'binary binary' al comando git diff se solucionó:

git diff --binary --cached commit -- path/to/directory | git apply -R --index
 1
Author: khelkun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:33:26

Voy a ofrecer una opción terrible aquí, ya que no tengo idea de cómo hacer nada con git excepto add commit y push, así es como "revirtió" un subdirectorio:

Empecé un nuevo repositorio en mi pc local, volvió todo a la confirmación quería copiar el código y, a continuación, copiar los archivos a mi directorio de trabajo, add commit push et voila. No odies al jugador, odia al Sr. Torvalds por ser más listo que todos nosotros.

 1
Author: Abraham Brookes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-13 23:21:27