¿Cuál es la diferencia entre ‘git rm –cached’, ‘git restore –staged’ y ‘git reset’

11 minutos de lectura

Avatar de usuario de sekai_no_suda
sekai_no_suda

Me he encontrado con las siguientes tres formas para desorganizar los archivos que fueron preparados con el comando ‘git add’

git rm --cached <file>
git restore --staged <file>
git reset <file>

Sus comportamientos se veían completamente iguales cuando ejecuté esos comandos uno por uno. ¿Cuáles son exactamente las diferencias entre ellos?

avatar de usuario de torek
Torek

Dos son iguales; uno no lo es, excepto bajo circunstancias particulares.

Para entender esto, recuerda que:

  • una confirmación contiene una instantánea de todos los archivos que Git conocía, a partir de la forma que tenían cuando dijiste que los confirmaras;
  • la instantánea está hecha de los archivos que están en el índice de Git, también conocido como staging-area, también conocido como caché (tres términos para lo mismo); y
  • git add medio hacer que la copia en el índice/área de preparación/caché coincida con la copia en mi árbol de trabajo (copiando del árbol de trabajo si se actualiza la copia del árbol de trabajo, o eliminando del índice si se elimina la copia del árbol de trabajo).

Así que el índice/área de preparación contiene, en todo momento, su próximo compromiso propuestoy se sembró inicialmente a partir de su compromiso actual cuando hiciste un git checkout o git switch para obtener ese compromiso.1 Su árbol de trabajo por lo tanto contiene un tercera Copiar2 de cada expediente, siendo las dos primeras copias la que se encuentra en el compromiso actual alias HEADy el del índice.

Con eso en mente, esto es lo que hace cada uno de sus comandos:

  • git rm --cached file: elimina la copia del archivo del índice/área de preparación, sin tocar la copia del árbol de trabajo. El próximo compromiso propuesto ahora carece el archivo. Si el compromiso actual posee el archivo, y de hecho realiza una próxima confirmación en este punto, la diferencia entre la confirmación anterior y la nueva confirmación es que el archivo ya no está.

  • git restore --staged file: Git copia el archivo del HEAD commit en el índice, sin tocar la copia del árbol de trabajo. La copia índice y la HEAD copiar ahora coincide, ya sea que coincidan o no antes. Una nueva confirmación hecha ahora tendrá el mismo copia del archivo como la confirmación actual.

    Si el compromiso actual carece el archivo, esto tiene el efecto de quitando el archivo del índice. Asi que en este caso hace lo mismo que git rm --cached.

  • git reset file: esto copia el HEAD versión del archivo al índice, al igual que git restore --staged file.

(Tenga en cuenta que git restorea diferencia de esta forma particular de git reset, pueden sobrescribir la copia del árbol de trabajo de algún archivo, si se lo solicita. los --staged opción, sin la --worktree opción, le indica que escriba solo en el índice).

Nota al margen: muchas personas inicialmente piensan que el índice/área de preparación contiene solo cambios, o solo archivos modificados. Este no es el caso, pero si lo estuvieras pensando de esta manera, git rm --cached parecería ser el mismo que los otros dos. Como no es así como funciona el índice, no lo es.


1Hay algunos casos extremos extravagantes cuando organizas algo y luego haces algo nuevo. git checkout. Esencialmente, si es posible mantener una copia preparada diferente, Git lo hará. Para conocer los detalles sangrientos, consulte Verificar otra rama cuando haya cambios no confirmados en la rama actual.

2La copia confirmada y cualquier copia preparada se mantienen en forma de un Git interno. objeto de gota, que elimina los duplicados de contenido. Entonces, si estos dos coinciden, literalmente solo comparten una copia subyacente. Si la copia preparada difiere de la HEAD copia, pero coincide con cualquier otra copia o copias confirmadas existentes, tal vez incluso con muchas, la copia preparada comparte el almacenamiento subyacente con todas esas otras confirmaciones. Así que llamar a cada uno una “copia” es una exageración. Pero como modelo mental, funciona bastante bien: nunca se puede sobrescribir ninguno; un nuevo git add creará un nuevo objeto blob si es necesario, y si nadie usa algún objeto blob al final, Git eventualmente lo descartará.


Un ejemplo específico

En un comentario, pavel_orekhov dice:

Todavía no me queda claro dónde difieren “git rm –cached” y “git restore –staged”. ¿Podría mostrar una serie de comandos con estos 2 que muestran un comportamiento diferente?

Veamos una confirmación específica en el repositorio de Git para el mismo Git (clonarlo primero si es necesario, por ejemplo, desde https://github.com/git/git.git):

$ git switch --detach v2.35.1
HEAD is now at 4c53a8c20f Git 2.35.1

Su árbol de trabajo contendrá archivos llamados Makefile, README.md, git.cy así.

Vamos ahora modificar algún archivo existente en el árbol de trabajo:

$ ed Makefile << end
> 1a
> foo
> .
> w
> q
> end
107604
107608
$ git status --short
 M Makefile

los > los signos son del caparazón que solicita información; los dos números son los conteos de bytes del archivo Makefile. Tenga en cuenta la salida de git status es SPACEMSPACEMakefileindicando que el índice o área de ensayo copia de Makefile coincide con el HEAD copia de Makefilemientras que la arbol de trabajo copia de Makefile difiere de la índice copia de Makefile.

(Aparte: accidentalmente agregué dos foo líneas mientras prepara el texto de cortar y pegar. No voy a regresar y arreglarlo, pero si haces este experimento tú mismo, espera resultados ligeramente diferentes).

Vamos ahora git add este archivo actualizado, luego reemplace foo en primera fila con bar:

$ git add Makefile
$ git status --short
M  Makefile

Tenga en cuenta que el M se ha movido a la izquierda una columna, M-space-space-Makefile, indicando que el índice copia de Makefile difiere de la HEAD copy, pero ahora las copias del índice y del árbol de trabajo coinciden. Ahora hacemos el reemplazo foo-to-bar:

$ ed Makefile << end
> 1s/foo/bar/
> w
> q
> end
107608
107608
$ git status --short
MM Makefile

ahora tenemos dos Ms: el HEAD copia de Makefile difiere de la copia índice de Makefileque difiere de la copia del árbol de trabajo de Makefile. Correr git diff --cached y git diff le mostrará exactamente cómo se compara cada emparejamiento.

$ git diff --cached
diff --git a/Makefile b/Makefile
index 5580859afd..8b8fc5a6d6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
-# The default target of this Makefile is...
+foo
+foo
 all::
 
 # Define V=1 to have a more verbose compile.
$ git diff
diff --git a/Makefile b/Makefile
index 8b8fc5a6d6..96a787d50d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-foo
+bar
 foo
 all::
 

Ahora, si corremos git rm --cached Makefileesta voluntad eliminar la copia índice del archivo Makefile enteramentey git status cambiará en consecuencia. Debido a que tenemos todas estas modificaciones, Git también exige el indicador de “forzar”:

$ git rm --cached Makefile
error: the following file has staged content different from both the
file and the HEAD:
    Makefile
(use -f to force removal)
$ git rm --cached -f Makefile
rm 'Makefile'
$ git status --short
D  Makefile
?? Makefile

ahora tenemos no archivo llamado Makefile en nuestro próximo compromiso propuesto en el índice/área de ensayo. Sin embargo, el archivo Makefile todavía aparece (con la lectura de la primera línea bar) en el árbol de trabajo (inspeccione el archivo usted mismo para verlo). Este Makefile es un archivo sin seguimiento entonces obtenemos dos líneas de salida de git status --shortuno para anunciar la desaparición inminente del archivo Makefile en el siguiente compromiso, y el otro para anunciar la existencia del archivo sin seguimiento Makefile.

Sin hacer ningún compromiso, ahora usamos git restore --staged Makefile:

$ git restore --staged Makefile
$ git status --short
 M Makefile

El estado ahora es espacio-M nuevamente, lo que indica que Makefile existe en el índice (y por lo tanto estará en el próximo compromiso), y además, coincide con el HEAD copia de Makefileasi que git diff --staged—que es otra forma de deletrear git diff --cached— no lo mostrará (y de hecho no mostrará nada). los arbol de trabajo la copia permanece intacta y aún contiene la línea adicional barcomo git diff muestra:

$ git diff --staged
$ git diff
diff --git a/Makefile b/Makefile
index 5580859afd..96a787d50d 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,5 @@
-# The default target of this Makefile is...
+bar
+foo
 all::
 
 # Define V=1 to have a more verbose compile.

De nuevo, la clave para entender todo esto es:

  • Cada confirmación tiene un instantánea completa de cada archivo que Git conoce.

  • Esta instantánea existe, en todo momento, en Git’s índiceque Git también llama el área de ensayou ocasionalmente, ahora principalmente en el --cached bandera—la cache. los --staged o --cached bandera3 generalmente significa hacer algo con este índice/área de ensayo. Comandos como git reset, git rmy git add implícitamente trabajar con el índice / área de preparación, aunque las banderas pueden modificar un poco este comportamiento; la git restore El comando tiene el explícito --staged y --worktree banderas

  • Mientras tanto, su árbol de trabajo contiene archivos cotidianos ordinarios. Estos son los únicos archivos que puede ver y trabajar directamente (con su editor por ejemplo); solamente Comandos Git puede ver y trabajar con las copias confirmadas e indexadas de los archivos.

  • Comprometido las copias de los archivos nunca se pueden cambiar. Están en esos compromisos Siempre (o mientras esas confirmaciones continúen existiendo): son de solo lectura. sin embargo, el índice copia de un archivo se puede reemplazar al por mayor, con git addo parcheado, con git add -po eliminado por completo, con git rm o git rm --cached.

  • Los archivos ordinarios son, bueno, archivos ordinarios: todos sus comandos ordinarios funcionan normalmente en los archivos ordinarios. (¿Y no es extraordinario cómo la palabra ordinaria “ordinario” ahora es divertida?)

  • Correr git commit toma todo el índice las copia y las congela en una nueva instantánea. Entonces, lo que haces, mientras trabajas en Git, es:

    • manipular archivos ordinarios, de manera ordinaria;
    • git add ellos para actualizar la copia del índice de Git, para preparar la congelación; y
    • git commit el resultado, congelarlos para siempre.

    Este es el proceso para hacer un nuevo compromisoy si cambias de opinión y decides no para hacer un nuevo compromiso, git restore --staged o git reset puede ser usado para volver a extraer una copia confirmada en la copia de índice. Pero git rm elimina una copia de índice por completo.

Entonces si y solamente si eliminando la copia del índice por completo vuelve a poner las cosas como estaban (lo que puede suceder cuando se nuevo), luego “hacer que la copia del índice coincida con la inexistente HEAD copiar, eliminándolo” es una forma correcta de hacer lo que quieres. Pero si el HEAD commit contiene una copia del archivo en cuestión, git rm --cached the-file Está Mal.


3Tenga en cuenta que --cached y --staged tener el mismo significado por git diff. Para git rmsin embargo, simplemente no hay --staged opción en absoluto. ¿Por qué? Esa es una pregunta para los desarrolladores de Git, pero podemos notar que históricamente, en el pasado distante, git diff no tenía --staged o. Mi mejor suposición es, por lo tanto, que fue un descuido: cuando quienquiera que agregó --staged a git diff lo hizo, se olvidaron de agregar --staged a git rm también.

  • Considerar git restore y git reset --hardmientras ambos alteran el árbol de trabajo, git restore (sin opciones) no toca el índice. Entonces puedo asumir git restore --staged --worktree es lo mismo que git reset --hard?

    – Gordon Bai

    22 de abril de 2021 a las 15:06


  • @GordonBai: cierto (aunque git restore se centra en archivos individuales dentro de una confirmación/el-índice/su-árbol de trabajo, mientras que git reset --hard se niega a aceptar cualquier especificación de ruta: siempre es para todo el compromiso).

    – torek

    22 de abril de 2021 a las 23:02

  • Todavía no me queda claro dónde difieren “git rm –cached” y “git restore –staged”. ¿Podría mostrar una serie de comandos con estos 2 que muestran un comportamiento diferente?

    – pavel_orejov

    2 de febrero a las 23:09

  • @pavel_orekhov: git rm --cached elimina una entrada de índice (sin hacer nada más). git restore --staged escribe una entrada de índice, o muchas entradas de índice: la forma en que esto podría lograr el mismo resultado es si alguna entrada de índice se escribe con un valor que significa “no incluir este archivo”. Existe tal valor (el OID nulo) pero no hay forma de acceder a él directamente; puedes conseguirlo indirectamente en otras formas debido a la idea de un conflicto de directorio/archivo. Pero estos son los dos que digo son diferentes, como normalmente son diferentes. Agregaré un ejemplo específico.

    – torek

    3 de febrero a las 1:00

¿Ha sido útil esta solución?