Was ist der Unterschied zwischen „git rm –cached“, „git restore –staged“ und „git reset“

Lesezeit: 11 Minuten

Benutzeravatar von sekai_no_suda
sekai_no_suda

Ich bin auf die folgenden drei Möglichkeiten gestoßen, um die Dateien zu deaktivieren, die mit dem Befehl „git add“ bereitgestellt wurden.

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

Ihr Verhalten sah völlig gleich aus, als ich diese Befehle nacheinander ausführte. Was genau sind die Unterschiede zwischen ihnen?

Toreks Benutzeravatar
Torek

Zwei sind gleich; man ist es nicht, außer unter besonderen Umständen.

Um dies zu verstehen, denken Sie daran:

  • Ein Commit enthält eine Momentaufnahme aller Dateien, von denen Git wusste, in der Form, in der sie sich befanden, als Sie sagten, sie zu committen.
  • der Schnappschuss wird gemacht von die Dateien, die sich in Gits Index befinden, auch bekannt als Staging-Area, auch bekannt als Cache (drei Begriffe für dasselbe); und
  • git add meint Machen Sie die Kopie im Index/Staging-Bereich/Cache mit der Kopie in meinem Arbeitsbaum übereinstimmen (durch Kopieren aus dem Arbeitsbaum, wenn die Kopie des Arbeitsbaums aktualisiert wird, oder durch Entfernen aus dem Index, wenn die Kopie des Arbeitsbaums entfernt wird).

Der Index / Staging-Bereich enthält also zu jeder Zeit Ihre nächsten Commit vorgeschlagenund wurde ursprünglich von Ihrem ausgesät aktuelle Verpflichtung wenn du a gemacht hast git checkout oder git switch um dieses Commit zu erhalten.1 Ihr Arbeitsbaum enthält also a Dritter Kopieren2 jeder Datei, wobei die ersten beiden Kopien die in der sind aktuelle Verpflichtung auch bekannt HEADund die im Index.

In diesem Sinne macht jeder Ihrer Befehle Folgendes:

  • git rm --cached file: Entfernt die Kopie der Datei aus dem Index / Staging-Bereich, ohne die Kopie des Arbeitsbaums zu berühren. Das vorgeschlagene nächste Commit jetzt fehlt die Datei. Wenn das aktuelle Commit hat die Datei, und Sie machen an dieser Stelle tatsächlich einen nächsten Commit, der Unterschied zwischen dem vorherigen Commit und dem neuen Commit besteht darin, dass die Datei weg ist.

  • git restore --staged file: Git kopiert die Datei aus der HEAD in den Index übernehmen, ohne die Kopie des Arbeitsbaums zu berühren. Die Indexkopie und die HEAD Jetzt kopieren übereinstimmen, unabhängig davon, ob sie vorher übereinstimmten oder nicht. Ein neuer Commit, der jetzt gemacht wird, wird die haben gleich Kopie der Datei als aktuellen Commit.

    Wenn das aktuelle Commit fehlt die Datei, dies hat die Wirkung von entfernen die Datei aus dem Index. So in diesem Fall es macht das gleiche wie git rm --cached.

  • git reset file: Dies kopiert die HEAD Version der Datei in den Index, genau wie git restore --staged file.

(Beachten Sie, dass git restoreim Gegensatz zu dieser besonderen Form von git reset, kann Überschreiben Sie die Kopie des Arbeitsbaums einer Datei, wenn Sie dazu aufgefordert werden. Die --staged Option, ohne die --worktree Option, weist sie an, nur in den Index zu schreiben.)

Randnotiz: Viele Leute denken zunächst, dass der Index / Staging-Bereich nur Änderungen oder nur geänderte Dateien enthält. Dies ist nicht der Fall, aber wenn Sie so darüber nachdenken, git rm --cached scheint das gleiche zu sein wie die anderen beiden. Da der Index nicht so funktioniert, ist es nicht so.


1Es gibt einige skurrile Randfälle, wenn Sie etwas inszenieren und dann etwas Neues machen git checkout. Wenn es möglich ist, eine andere bereitgestellte Kopie beizubehalten, wird Git dies im Wesentlichen tun. Die blutigen Details finden Sie unter Anderen Zweig auschecken, wenn nicht festgeschriebene Änderungen im aktuellen Zweig vorhanden sind.

2Die festgeschriebene Kopie und jede bereitgestellte Kopie werden tatsächlich in Form eines internen Git aufbewahrt Blob-Objekt, die Inhalte dedupliziert. Wenn diese beiden also übereinstimmen, teilen sie sich buchstäblich nur eine zugrunde liegende Kopie. Wenn sich die bereitgestellte Kopie von der unterscheidet HEAD kopieren, aber mit irgendeiner – vielleicht sogar vielen – anderen existierenden festgeschriebenen Kopie(n) übereinstimmt, teilt sich die bereitgestellte Kopie den zugrunde liegenden Speicher mit all diesen anderen Festschreibungen. Es ist also übertrieben, jeden als “Kopie” zu bezeichnen. Aber als mentales Modell funktioniert es gut genug: Keines kann jemals überschrieben werden; eine neue git add erstellt bei Bedarf ein neues Blob-Objekt, und wenn am Ende niemand ein Blob-Objekt verwendet, verwirft Git es schließlich.


Ein konkretes Beispiel

In einem Kommentar sagt pavel_orekhov:

Mir ist immer noch nicht klar, wo sich “git rm –cached” und “git restore –staged” unterscheiden. Könnten Sie bitte eine Reihe von Befehlen mit diesen 2 zeigen, die sich unterschiedlich verhalten?

Schauen wir uns einen bestimmten Commit im Git-Repository für Git selbst an (klonen Sie ihn bei Bedarf zuerst, z https://github.com/git/git.git):

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

Ihr Arbeitsbaum enthält Dateien mit dem Namen Makefile, README.md, git.cusw.

Lass uns jetzt Ändern Sie eine vorhandene Datei im Arbeitsbaum:

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

Die > Zeichen kommen von der Shell, die um Eingabe bittet; die beiden Zahlen sind die Byte-Anzahl der Datei Makefile. Beachten Sie die Ausgabe von git status ist SPACEMSPACEMakefilewas darauf hinweist, dass die Index oder Bühnenbereich Kopie von Makefile entspricht dem HEAD Kopie von Makefilewährend Arbeitsbaum Kopie von Makefile unterscheidet sich von der Index Kopie von Makefile.

(Nebenbei: Ich habe versehentlich zwei hinzugefügt foo Linien, während Sie den Text zum Ausschneiden und Einfügen vorbereiten. Ich werde nicht zurückgehen und es beheben, aber wenn Sie dieses Experiment selbst durchführen, erwarten Sie etwas andere Ergebnisse.)

Lass uns jetzt git add diese aktualisierte Datei, dann ersetzen foo in der ersten Zeile mit bar:

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

Notiere dass der M hat sich um eine Spalte nach links bewegt, M-Leerzeichen-Leerzeichen-Makefile, was darauf hinweist, dass die Index Kopie von Makefile unterscheidet sich von der HEAD kopieren, aber jetzt stimmen die Index- und Arbeitsbaumkopien überein. Jetzt machen wir den foo-to-bar-Ersatz:

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

Wir haben nun zwei Ms: die HEAD Kopie von Makefile unterscheidet sich von der Indexkopie von Makefiledie sich von der Arbeitsbaumkopie von unterscheidet Makefile. Betrieb git diff --cached und git diff zeigt Ihnen genau, wie sich jede Paarung vergleicht.

$ 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::
 

Wenn wir jetzt laufen git rm --cached Makefiledieser Wille Entfernen Sie die Indexkopie der Datei Makefile vollständigund git status wird sich entsprechend ändern. Da wir all diese Modifikationen haben, fordert Git auch das “force”-Flag:

$ 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

Wir haben nun nein Datei benannt Makefile in unserer nächsten Commit vorgeschlagen im Index / Staging-Bereich. Allerdings die Datei Makefile erscheint immer noch (mit der ersten Zeile Lesung bar) in der Arbeitsstruktur (überprüfen Sie die Datei selbst, um sie zu sehen). Diese Makefile ist ein ungetrackte Datei so erhalten wir zwei Ausgabezeilen von git status --shorteine, um den bevorstehenden Untergang der Datei anzukündigen Makefile im nächsten Commit und der andere, um die Existenz der nicht verfolgten Datei anzukündigen Makefile.

Ohne ein Commit zu machen, verwenden wir jetzt git restore --staged Makefile:

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

Der Status ist jetzt wieder Leerzeichen-M, was darauf hinweist Makefile im Index vorhanden (und wird daher im nächsten Commit sein), und außerdem entspricht dem HEAD Kopie des Makefilesso git diff --staged– was eine andere Schreibweise ist git diff --cached– wird es nicht zeigen (und wird tatsächlich nichts zeigen). Die Arbeitsbaum Kopie bleibt ungestört und enthält immer noch die zusätzliche Zeile barals git diff zeigt an:

$ 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.

Auch hier ist der Schlüssel zum Verständnis all dessen:

  • Jeder Commit enthält a vollständige Momentaufnahme jeder Datei, die Git kennt.

  • Dieser Snapshot ist jederzeit in Git vorhanden Indexdie Git auch als bezeichnet Bühnenbereichoder gelegentlich – jetzt meistens in der --cached Flagge – die Zwischenspeicher. Die --staged oder --cached Flagge3 allgemein bedeutet machen Sie etwas mit diesem Index / Staging-Bereich. Befehle wie git reset, git rmund git add implizit mit dem Index / Staging-Bereich arbeiten, obwohl Flags dieses Verhalten etwas modifizieren können; der git restore Befehl hat die explizite --staged und --worktree Flaggen.

  • In der Zwischenzeit enthält Ihr Arbeitsbaum gewöhnliche Alltagsdateien. Dies sind die einzigen Dateien, die Sie direkt sehen und bearbeiten können (zum Beispiel mit Ihrem Editor); nur Git-Befehle kann die festgeschriebenen und indizierten Kopien von Dateien sehen und damit arbeiten.

  • Engagiert Kopien von Dateien können niemals geändert werden. Sie sind in diesen Commits für immer (oder solange diese Commits bestehen bleiben): Sie sind schreibgeschützt. Allerdings ist die Index Kopie einer Datei kann en gros ersetzt werden, mit git addoder gepatcht, mit git add -poder ganz entfernt, mit git rm oder git rm --cached.

  • Gewöhnliche Dateien sind, nun ja, gewöhnliche Dateien: Alle Ihre gewöhnlichen Befehle funktionieren ganz normal mit den gewöhnlichen Dateien. (Und ist es nicht außergewöhnlich, wie amüsant das gewöhnliche Wort „gewöhnlich“ ist?)

  • Betrieb git commit nimmt alles Index kopiert und friert sie in einen neuen Snapshot ein. Was Sie also tun, wenn Sie in Git arbeiten, ist:

    • gewöhnliche Dateien auf gewöhnliche Weise manipulieren;
    • git add sie, um die Indexkopie von Git zu aktualisieren und das Einfrieren vorzubereiten; und
    • git commit das Ergebnis, sie für alle Zeit einzufrieren.

    Dies ist der Prozess zur Herstellung von a neues Commitmentund wenn Sie Ihre Meinung ändern und entscheiden nicht um ein neues Commit zu machen, git restore --staged oder git reset kann benutzt werden um wieder extrahieren eine festgeschriebene Kopie in die Indexkopie. Aber git rm entfernt eine Indexkopie vollständig.

Also wenn und nur wenn Entfernen Sie die Indexkopie vollständig stellt die Dinge wieder so her, wie sie waren (was passieren kann, wenn eine Datei Neu), dann “passen Sie die Indexkopie mit der nicht vorhandenen an HEAD kopieren, indem Sie es entfernen” ist ein korrekter Weg, um das zu tun, was Sie wollen. Aber wenn die HEAD commit enthält eine Kopie der betreffenden Datei, git rm --cached the-file ist falsch.


3Beachten Sie, dass --cached und --staged habe den selbe Bedeutung Pro git diff. Für git rmaber es gibt einfach keine --staged Möglichkeit überhaupt. Warum? Das ist eine Frage für die Git-Entwickler, aber wir können das historisch, in der fernen Vergangenheit, feststellen git diff hatte nicht --staged entweder. Meine beste Vermutung ist daher, dass es ein Versehen war: als wer auch immer hinzufügte --staged zu git diff getan, sie haben vergessen hinzuzufügen --staged zu git rm zu.

  • In Betracht ziehen git restore und git reset --hardwährend beide den Arbeitsbaum verändern, git restore (ohne Optionen) berührt den Index nicht. Kann ich also vermuten git restore --staged --worktree ist das gleiche wie git reset --hard?

    – Gordon Bai

    22. April 2021 um 15:06 Uhr


  • @GordonBai: richtig (obwohl git restore konzentriert sich auf einzelne Dateien in einem Commit/dem-Index/Ihrem-Arbeitsbaum, während git reset --hard weigert sich, irgendeine Pfadangabe zu akzeptieren: sie ist immer commit-weit).

    – Torek

    22. April 2021 um 23:02 Uhr

  • Mir ist immer noch nicht klar, wo sich “git rm –cached” und “git restore –staged” unterscheiden. Könnten Sie bitte eine Reihe von Befehlen mit diesen 2 zeigen, die sich unterschiedlich verhalten?

    – pavel_orekhov

    2. Februar 2022 um 23:09 Uhr

  • @pavel_orekhov: git rm --cached entfernt einen Indexeintrag (ohne etwas anderes zu tun). git restore --staged schreibt ein Indexeintrag oder viele Indexeinträge: Dasselbe Ergebnis könnte dadurch erzielt werden, wenn ein Indexeintrag mit einem Wert geschrieben wird, der “diese Datei nicht einbeziehen” bedeutet. Es gibt einen solchen Wert (die Null-OID), aber es gibt keine Möglichkeit, direkt darauf zuzugreifen. Du kannst es bekommen indirekt auf andere Weise aufgrund der Idee eines Verzeichnis-/Dateikonflikts. Aber das sind die beiden, die ich sage sind unterschiedlich, da sie normalerweise unterschiedlich sind. Ich werde ein konkretes Beispiel hinzufügen.

    – Torek

    3. Februar 2022 um 1:00 Uhr

1439630cookie-checkWas ist der Unterschied zwischen „git rm –cached“, „git restore –staged“ und „git reset“

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy