どのようにgit reset --hardサブディレクトリをリセットするのですか?


197

UPDATE²:Git 2.23(2019年8月)では、これを行う新しいコマンドgit restoreがあります承認された回答を参照してください。

更新:これはGit 1.8.3の時点でより直感的に機能します自分の答えを参照しください。

次の使用例を想像してみてください。Git作業ツリーの特定のサブディレクトリにあるすべての変更を取り除き、他のすべてのサブディレクトリはそのまま残したいとします。

この操作に適切なGitコマンドは何ですか?

以下のスクリプトは問題を示しています。How to make filesコメントの下に適切なコマンドを挿入します。現在のコマンドはa/c/ac、スパースチェックアウトによって除外されるはずのファイルを復元します。私がいることを注意していない明示的に復元したいa/aa/b、私は「知っている」aと、以下のすべてのものを復元したいです。編集:そして私はまたb、「知っている」、または他のどのディレクトリがと同じレベルにあるかを知りません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

3
どうgit stash && git stash dropですか?
CharlesB 2013年

1
どうgit checkout -- /path/to/subdir/ですか?
iberbeu 2013年

3
@CharlesB:git stashパス引数を受け付けません...
krlmlr '14 / 03/13

@iberbeu:いいえ。まばらなチェックアウトによって除外されたファイルも追加します。
krlmlr 2013年

1
@CharlesBailey:では、なぜ「信頼できる、または公式の情報源からの回答を探している」というラジオボタンがあるのですか。バウンティダイアログで?自分でタイプしなかった!また、「git reset subdirectory」(引用符なし)をグーグルで試し、最初の3つの位置を確認してください。確かに、kernel.orgメーリングリストへのメッセージを見つけるのはより困難です。-また、私にとって、この動作がバグなのか機能なのかはまだはっきりしていません。
krlmlr 2013年

回答:


162

Git 2.23(2019年8月)では、新しいコマンドがありますgit restore

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

これにより、インデックスと作業ツリーの両方がHEADコンテンツと置き換えられreset --hardます。


元の回答(2013)

Dan Fabulichのコメントによる)次の点に注意してください。

  • git checkout -- <path> ハードリセットは行いません。作業ツリーの内容をステージングされた内容に置き換えます。
  • git checkout HEAD -- <path>パスのハードリセットを行い、インデックスと作業ツリーの両方をHEADコミットのバージョンで置き換えます。

答えによってAjedi32、両方のチェックアウトのフォームは、対象リビジョンで削除されたファイルを削除しないでください
作業ツリーにHEADに存在しない追加のファイルがある場合、git checkout HEAD -- <path>はそれらを削除しません。

注:git checkout --overlay HEAD -- <path>(Git 2.22、2019年第1四半期)では、完全に<tree-ish>一致させるために、インデックスと作業ツリーに表示されても表示されないファイルは削除され<tree-ish>ます。

ただしgit update-index --skip-worktree、「除外したファイルがgit sparse checkoutに繰り返し表示されるのはなぜですか?」で説明したように、そのチェックアウトは(無視したいディレクトリについて)を尊重できます


1
どうか明らかにしてください。の後git checkout HEAD -- .、スパースチェックアウトによって除外されたファイルが再び表示されます。何をgit update-index --skip-worktreeすべきか?
krlmlr 2013年

@krlmlr-worktreeをスキップしたり想定し、そのままgitのための「目に見えない」インデックス内のエントリを作成しようとしているの双方向です:fallengamer.livejournal.com/93321.htmlstackoverflow.com/q/13630849/6309stackoverflowの。 com / a / 6139470/6309
VonC 2013年

@krlmlrこれらのリンクは、それらが「スキップされたワークツリー」としてマークされた後、チェックアウトがそれらのエントリをまだ復元するかどうかを試すためのポインタにすぎません。
VonC 2013年

申し訳ありませんが、それは現在のタスクには複雑すぎます。排他的なリセットではなく、包括的なリセットが必要です。Gitでこれを実行する良い方法は本当にありませんか?
krlmlr 2013年

@krlmlr no:を実行してgit checkout HEAD -- <path>、復元されたディレクトリを削除することをお勧めします(ただし、スパースチェックアウトでまだ宣言されています)。
VonC 2013年

125

この機能と互換性スイッチを親切に実装したGit開発者のDuy Nguyen氏によると、Git 1.8.3以降は次のように動作します。

git checkout -- a

aハードリセットしたいディレクトリはどこですか?)元の動作には、

git checkout --ignore-skip-worktree-bits -- a

5
Git開発チームでフォローアップして、Gitにこの変更をもたらした努力に感謝します。
Dan Cruz

13
そして、「」このケースでは、あなたが復帰したいディレクトリにいる場合ので、コマンドがあるべき、あなたが元に戻したいディレクトリをノート意味するgit checkout -- .ところ.の手段カレントディレクトリ。
TheWestIsThe ... 2014

5
私の側からの1つのコメントは、最初にフォルダーをアンステージする必要があるということです git reset -- a(aはリセットするディレクトリです)
Boyan

git reset --hardリポジトリ全体を使いたい場合もそうではありませんか?
krlmlr 2016年

そして、そのディレクトリに新しいファイルを追加した場合は、rm -rf a前に行います。
トビアスフェイル

30

変更してみてください

git checkout -- a

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

バージョン1.7.0以降、Git はskip-worktreeフラグを尊重しますls-files

テストスクリプトを実行すると(いくつかのマイナーな調整をgit commit...からtoに変更git commit -qgit statusgit status --short)出力:

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

提案された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

いいですね。しかしgit checkout、そもそも「スキップワークツリー」を尊重すべきではないでしょうか。
krlmlr 2013年

クイックレビューcheckout.ctree.cスキップworktreeフラグが使用されていることを明らかにしていません。
Dan Cruz

これは、このコマンドのbashエイリアスを設定する必要がある場合でも、実際に役立つほど単純です。Duy NguyenがGitメーリングリストへの私のメッセージに返信ました。もっとユーザーフレンドリーな代替案がすぐにポップアップするかどうかを見てみましょう。
krlmlr 2013年

18

変更を単に破棄する場合は、他の回答で提案されているgit checkout -- path/またはgit checkout HEAD -- path/コマンドが適切に機能します。ただし、ディレクトリをHEAD以外のリビジョンにリセットする場合、その解決策には重大な問題があります。ターゲットリビジョンで削除されたファイルは削除されません。

代わりに、次のコマンドを使い始めました。

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

これは、ターゲットコミットとインデックス間の差分を見つけ、その差分を作業ディレクトリとインデックスに逆に適用することで機能します。基本的に、これはインデックスの内容を指定したリビジョンの内容と一致させることを意味します。git diffパス引数を取るという事実により、この効果を特定のファイルまたはディレクトリに制限することができます。

このコマンドはかなり長く、頻繁に使用する予定なので、名前を付けたエイリアスを設定しましたreset-checkout

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

次のように使用できます。

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

あるいは単に:

git reset-checkout 451a9a4

私は昨日あなたのコメントを見て、今日それを実験しました。あなたのエイリアスは役に立ちます。+1
VonC、2015年

このオプションgit checkout --overlay HEAD -- <path>は、@ VonCが彼の回答で言及しているコマンドとどのように違いますか?
Ehteshチョードリー

1
@EhteshChoudhury注:git checkout --overlay HEAD -- <path>まだリリースされていません(Git 2.22は2019年第2四半期にリリースされます)
VonC

5

私は以外にgitで何かをするか分からないため、ここで恐ろしいオプションを提供するつもりだadd commitpush、ここで私は、サブディレクトリを「元に戻す」方法は次のとおりです。

私は自分のローカルPCで新しいリポジトリを開始し、コードをコピーしたいコミット全体を元に戻し、それらのファイルを作業ディレクトリなどにコピーしましたadd commit push。プレイヤーを憎むな、トーバルズ氏は私たち全員より賢いのが嫌いだ。


4

通常、リセットするとすべてが変更されますが、使用git stashして保持したいものを選択できます。あなたが述べたstashように、パスを直接受け入れませんが、--keep-indexフラグを使用して特定のパスを維持するために引き続き使用できます。あなたの例では、bディレクトリを隠して、それから他のすべてをリセットします。

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

これにより、スクリプトの最後の部分で次のように出力されるようになります。

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

これはターゲットの結果だったと思います(bは変更されたまま、a / *ファイルは元に戻り、a / cは再作成されません)。

このアプローチには、非常に柔軟であるという追加の利点があります。ディレクトリに特定のファイルを追加するだけで、他のファイルを追加することはできません。


それはいいのですが、git add以外のすべてのものにならなければなりaませんよね?実際には難しいようです。
krlmlr 2013年

1
@krlmlrそうでもない。あなたはできるgit add .、その後git reset a以外のすべてを追加しますa
ジョナサンレン2013年

1
@krlmlrまた、git add削除されたファイルが追加されないことに注意してください。したがって、削除されたファイルのみを回復する場合はgit add .、変更されたファイルはすべて追加されますが、削除されたファイルは追加されません。
ジョナサンレン2013年

3

サブディレクトリのサイズがそれほど大きくなく、CLIから離れたい場合は、サブディレクトリを手動でリセットする簡単な解決策を次に示します。

  1. マスターブランチに切り替え、リセットするサブディレクトリをコピーします。
  2. ここで機能ブランチに戻り、サブディレクトリを手順1で作成したコピーで置き換えます。
  3. 変更をコミットします。

乾杯。機能ブランチのサブディレクトリを手動でリセットして、マスターブランチのサブディレクトリと同じにするだけです!!


この回答に対する投票はありませんでしたが、成功への最も簡単な保証されたルートである場合があります。
スースペンス2017年

1

Ajedi32答えは私が探していたものですが、一部のコミットではこのエラーに遭遇しました:

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

ディレクトリの一部のファイルがバイナリファイルである可能性があります。git diffコマンドに「--binary」オプションを追加すると修正されました:

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

0

どうですか

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 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.