Wie git reset --hard ein Unterverzeichnis?

196

UPDATE² : Mit Git 2.23 (August 2019) gibt es einen neuen Befehl git restore, der dies tut, siehe die akzeptierte Antwort .

UPDATE : Dies wird ab Git 1.8.3 intuitiver funktionieren, siehe meine eigene Antwort .

Stellen Sie sich den folgenden Anwendungsfall vor: Ich möchte alle Änderungen in einem bestimmten Unterverzeichnis meines Git-Arbeitsbaums entfernen und alle anderen Unterverzeichnisse intakt lassen.

Was ist der richtige Git-Befehl für diese Operation?

Das folgende Skript veranschaulicht das Problem. Fügen Sie den richtigen Befehl unter dem How to make filesKommentar ein. Der aktuelle Befehl stellt die Datei wieder her, a/c/acdie beim Auschecken mit geringer Dichte ausgeschlossen werden soll. Beachten Sie, dass ich nicht explizit wiederherstellen möchte a/aund a/bich nur "weiß" aund alles unten wiederherstellen möchte. EDIT : Und ich "weiß" auch nicht b, oder welche anderen Verzeichnisse sich auf der gleichen Ebene befinden wie 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
krlmlr
quelle
3
was ist mit einem git stash && git stash drop?
CharlesB
1
was ist mit git checkout -- /path/to/subdir/?
Iberbeu
3
@ CharlesB: git stashakzeptiert kein
Pfadargument
@iberbeu: Nein. Fügt auch Dateien hinzu, die durch spärliches Auschecken ausgeschlossen sind.
krlmlr
1
@CharlesBailey: Warum gibt es dann ein Optionsfeld mit der Aufschrift "Auf der Suche nach einer Antwort aus glaubwürdigen und / oder offiziellen Quellen". im Kopfgelddialog? Das habe ich selbst nicht getippt! Versuchen Sie auch, das Unterverzeichnis "git reset" (ohne Anführungszeichen) zu googeln, und sehen Sie, was sich auf den ersten drei Positionen befindet. Eine Nachricht an eine kernel.org-Mailingliste ist mit Sicherheit schwieriger zu finden. - Außerdem ist mir noch nicht klar, ob dieses Verhalten ein Fehler oder eine Funktion ist.
krlmlr

Antworten:

160

Mit Git 2.23 (August 2019) haben Sie den neuen Befehlgit restore

git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore -s@ -SW  -- aDirectory

Dies würde sowohl den Index als auch den Arbeitsbaum durch HEADInhalte ersetzen , wie reset --harddies bei einem bestimmten Pfad der Fall wäre.


Ursprüngliche Antwort (2013)

Beachten Sie (wie von Dan Fabulich kommentiert ), dass:

  • git checkout -- <path> führt keinen Hard-Reset durch: Ersetzt den Inhalt des Arbeitsbaums durch den bereitgestellten Inhalt.
  • git checkout HEAD -- <path>führt einen Hard-Reset für einen Pfad durch und ersetzt sowohl den Index als auch den Arbeitsbaum durch die Version aus dem HEADCommit.

Wie von Ajedi32 beantwortet, entfernen beide Checkout-Formulare keine Dateien, die in der Zielversion gelöscht wurden .
Wenn der Arbeitsbaum zusätzliche Dateien enthält, die in HEAD nicht vorhanden sind, git checkout HEAD -- <path>werden diese nicht entfernt.

Hinweis: Mit git checkout --overlay HEAD -- <path>(Git 2.22, Q1 2019) werden Dateien entfernt, die im Index und im Arbeitsbaum angezeigt werden, jedoch nicht in <tree-ish>, damit sie <tree-ish>genau übereinstimmen .

Diese Kasse kann jedoch a respektieren git update-index --skip-worktree(für die Verzeichnisse, die Sie ignorieren möchten), wie unter " Warum werden ausgeschlossene Dateien in meiner git spärlichen Kasse immer wieder angezeigt? " Erwähnt .

VonC
quelle
1
Bitte klären Sie. Danach git checkout HEAD -- .werden durch spärliches Auschecken ausgeschlossene Dateien wieder angezeigt. Was git update-index --skip-worktreesoll tun?
krlmlr
@krlmlr skip-worktree oder accept-unverändert sind die beiden Möglichkeiten, einen Eintrag im Index für git "unsichtbar" zu machen: fallengamer.livejournal.com/93321.html , stackoverflow.com/q/13630849/6309 und stackoverflow. com / a / 6139470/6309
VonC
@krlmlr Diese Links sind nur Hinweise, mit denen Sie versuchen können, festzustellen, ob eine Kasse diese Einträge noch wiederherstellen würde, sobald sie als "übersprungener Arbeitsbaum" markiert wurden.
VonC
Entschuldigung, aber das ist zu kompliziert für die anstehende Aufgabe. Ich möchte einen inklusiven Reset, keinen exklusiven. Gibt es wirklich keinen guten Weg, dies in Git zu tun?
krlmlr
@krlmlr no: Am besten machen Sie das git checkout HEAD -- <path>und löschen Sie dann die Verzeichnisse, die wiederhergestellt wurden (die aber noch in der Sparse-Kasse deklariert sind).
VonC
125

Laut dem Git-Entwickler Duy Nguyen, der die Funktion und einen Kompatibilitätsschalter freundlicherweise implementiert hat , funktioniert Folgendes wie erwartet ab Git 1.8.3 :

git checkout -- a

(Wo aist das Verzeichnis, das Sie zurücksetzen möchten). Auf das ursprüngliche Verhalten kann über zugegriffen werden

git checkout --ignore-skip-worktree-bits -- a
krlmlr
quelle
5
Vielen Dank für Ihre Bemühungen, das Git-Entwicklungsteam weiterzuverfolgen, was zu dieser Änderung in Git geführt hat.
Dan Cruz
13
Beachten Sie, dass "a" in diesem Fall das Verzeichnis bedeutet, das Sie zurücksetzen möchten. Wenn Sie sich also in dem Verzeichnis befinden, das Sie zurücksetzen möchten, sollte der Befehl " git checkout -- .where ." das aktuelle Verzeichnis sein.
TheWestIsThe ...
5
Ein Kommentar von meiner Seite ist, dass Sie den Ordner zuerst mit entfernen müssen git reset -- a(wobei a das Verzeichnis ist, das Sie zurücksetzen möchten)
Boyan
Ist das nicht auch wahr, wenn Sie git reset --harddas ganze Repo wollen?
krlmlr
Wenn Sie diesem Verzeichnis neue Dateien hinzugefügt haben, führen Sie rm -rf azuvor einen Vorgang durch.
Tobias Feil
30

Versuchen Sie es zu ändern

git checkout -- a

zu

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

Seit Version 1.7.0 erkennt Git's das Skip-Worktree-Flag an .ls-files

Ausführen Ihres Testskripts (mit einigen geringfügigen Änderungen, die sich ändern git commit... zu git commit -qund git statuszu git status --short) Ausgaben:

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

Ausführen Ihres Testskripts mit den vorgeschlagenen checkoutÄnderungsausgaben:

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
Dan Cruz
quelle
Klingt gut. Aber sollte man git checkoutdas "Skip-Worktree" -Bit überhaupt nicht respektieren?
krlmlr
Eine schnelle Überprüfung checkout.cund tree.czeigt nicht, dass das Flag "Arbeitsbaum überspringen" verwendet wird.
Dan Cruz
Dies ist einfach genug, um in der Praxis nützlich zu sein, auch wenn ich für diesen Befehl einen Bash-Alias ​​einrichten muss. Duy Nguyen hat auf meine Nachricht in der Git-Mailingliste geantwortet. Mal sehen, ob bald eine benutzerfreundlichere Alternative auftaucht.
krlmlr
18

Für den Fall einfach Änderungen verwerfen, die git checkout -- path/oder git checkout HEAD -- path/arbeiten Befehle von anderen Antworten vorgeschlagen groß. Wenn Sie jedoch ein Verzeichnis auf eine andere Revision als HEAD zurücksetzen möchten, weist diese Lösung ein erhebliches Problem auf: Es werden keine Dateien entfernt, die in der Zielversion gelöscht wurden.

Stattdessen habe ich den folgenden Befehl verwendet:

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

Dies funktioniert, indem der Unterschied zwischen dem Ziel-Commit und dem Index ermittelt und dann umgekehrt auf das Arbeitsverzeichnis und den Index angewendet wird. Grundsätzlich bedeutet dies, dass der Inhalt des Index mit dem Inhalt der von Ihnen angegebenen Revision übereinstimmt. Die Tatsache, dass git diffein Pfadargument verwendet wird, ermöglicht es Ihnen, diesen Effekt auf eine bestimmte Datei oder ein bestimmtes Verzeichnis zu beschränken.

Da dieser Befehl ziemlich lang ist und ich ihn häufig verwenden möchte, habe ich einen Alias ​​dafür eingerichtet, den ich benannt habe reset-checkout:

git config --global alias.reset-checkout '!f() { git diff --cached "$@" | git apply -R --index; }; f'

Sie können es so verwenden:

git reset-checkout 451a9a4 -- path/to/directory

Oder nur:

git reset-checkout 451a9a4
Ajedi32
quelle
Ich habe Ihren Kommentar gestern gesehen und heute experimentiert. Ihr Alias ​​ist hilfreich. +1
VonC
Wie vergleicht sich diese Option mit dem git checkout --overlay HEAD -- <path>Befehl, den @VonC in seiner Antwort erwähnt?
Ehtesh Choudhury
1
@EhteshChoudhury Hinweis, der git checkout --overlay HEAD -- <path>noch nicht veröffentlicht wurde (Git 2.22 wird im
zweiten
5

Ich werde hier eine schreckliche Option anbieten, da ich keine Ahnung habe, wie ich etwas mit Git machen soll, außer add commitund pushhier ist, wie ich ein Unterverzeichnis "zurückgesetzt" habe:

Ich habe ein neues Repository auf meinem lokalen PC gestartet, das Ganze auf das Commit zurückgesetzt, von dem ich Code kopieren wollte, und diese Dateien dann in mein Arbeitsverzeichnis add commit pushet voila kopiert. Hassen Sie den Spieler nicht, hassen Sie Herrn Torvalds dafür, dass er schlauer ist als wir alle.

Abraham Brookes
quelle
4

Ein Reset ändert normalerweise alles, aber Sie können git stashauswählen, was Sie behalten möchten. Wie Sie bereits erwähnt haben, stashwird ein Pfad nicht direkt akzeptiert, er kann jedoch weiterhin verwendet werden, um einen bestimmten Pfad mit dem --keep-indexFlag beizubehalten. In Ihrem Beispiel würden Sie das Verzeichnis b verstauen und dann alles andere zurücksetzen.

# 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)

Dies bringt Sie zu einem Punkt, an dem der letzte Teil Ihres Skripts Folgendes ausgibt:

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

Ich glaube, dies war das Zielergebnis (b bleibt geändert, a / * -Dateien sind zurück, a / c wird nicht neu erstellt).

Dieser Ansatz hat den zusätzlichen Vorteil, dass er sehr flexibel ist. Sie können so feinkörnig werden, wie Sie möchten, aber bestimmte Dateien, aber keine anderen, in ein Verzeichnis einfügen.

Jonathan Wren
quelle
Das ist schön, aber ich müsste git addalles außer a, oder? Klingt in der Praxis schwierig.
krlmlr
1
@krlmlr Nicht wirklich. Sie können git add .dann git reset aalles außer hinzufügen a.
Jonathan Wren
1
@krlmlr Es ist auch erwähnenswert, dass git addkeine gelöschten Dateien hinzugefügt werden. Wenn Sie also nur gelöschte Dateien wiederherstellen, git add .werden alle geänderten Dateien hinzugefügt, jedoch nicht die gelöschten.
Jonathan Wren
3

Wenn die Größe des Unterverzeichnisses nicht besonders groß ist UND Sie sich von der CLI fernhalten möchten, finden Sie hier eine schnelle Lösung zum manuellen Zurücksetzen des Unterverzeichnisses:

  1. Wechseln Sie zum Hauptzweig und kopieren Sie das zurückzusetzende Unterverzeichnis.
  2. Wechseln Sie nun zurück zu Ihrem Feature-Zweig und ersetzen Sie das Unterverzeichnis durch die Kopie, die Sie gerade in Schritt 1 erstellt haben.
  3. Übernehmen Sie die Änderungen.

Prost. Sie setzen einfach ein Unterverzeichnis in Ihrem Feature-Zweig manuell zurück, damit es dem des Hauptzweigs entspricht !!

Mehul Parmar
quelle
Ich habe keine Stimmen für diese Antwort gesehen, aber manchmal ist dies der einfachste garantierte Weg zum Erfolg.
Sue Spence
1

Die Antwort von Ajedi32 ist genau das , wonach ich gesucht habe, aber bei einigen Commits bin ich auf diesen Fehler gestoßen :

error: cannot apply binary patch to 'path/to/directory' without full index line

Möglicherweise, weil einige Dateien des Verzeichnisses Binärdateien sind. Durch Hinzufügen der Option '--binary' zum Befehl git diff wurde Folgendes behoben:

git diff --binary --cached commit -- path/to/directory | git apply -R --index
khelkun
quelle
0

Wie wäre es mit

subdir=thesubdir
for fn in $(find $subdir); do
  git ls-files --error-unmatch $fn 2>/dev/null >/dev/null;
  if [ "$?" = "1" ]; then
    continue;
  fi
  echo "Restoring $fn";
  git show HEAD:$fn > $fn;
done 
Cochise Ruhulessin
quelle