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?
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 HEAD
y 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 delHEAD
commit en el índice, sin tocar la copia del árbol de trabajo. La copia índice y laHEAD
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 elHEAD
versión del archivo al índice, al igual quegit restore --staged file
.
(Tenga en cuenta que git restore
a 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.c
y 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 SPACEMSPACEMakefile
indicando que el índice o área de ensayo copia de Makefile
coincide con el HEAD
copia de Makefile
mientras 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 M
s: el HEAD
copia de Makefile
difiere de la copia índice de Makefile
que 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 Makefile
esta 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 --short
uno 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 bar
como 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 comogit reset
,git rm
ygit add
implícitamente trabajar con el índice / área de preparación, aunque las banderas pueden modificar un poco este comportamiento; lagit 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 add
o parcheado, congit add -p
o eliminado por completo, congit rm
ogit 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; ygit 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
ogit reset
puede ser usado para volver a extraer una copia confirmada en la copia de índice. Perogit 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 rm
sin 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
ygit reset --hard
mientras ambos alteran el árbol de trabajo,git restore
(sin opciones) no toca el índice. Entonces puedo asumirgit restore --staged --worktree
es lo mismo quegit 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 quegit 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