Bagaimana cara mereset --dari subdirektori?

197

UPDATE² : Dengan Git 2.23 (Agustus 2019), ada perintah baru git restoreyang melakukan ini, lihat jawaban yang diterima .

UPDATE : Ini akan bekerja lebih intuitif pada Git 1.8.3, lihat jawaban saya sendiri .

Bayangkan use case berikut ini: Saya ingin menyingkirkan semua perubahan dalam subdirektori tertentu dari pohon kerja Git saya, membiarkan semua subdirektori lainnya tetap utuh.

Apa perintah Git yang tepat untuk operasi ini?

Script di bawah ini menggambarkan masalah. Masukkan perintah yang tepat di bawah How to make fileskomentar - perintah saat ini akan mengembalikan file a/c/acyang seharusnya dikecualikan oleh checkout jarang. Perhatikan bahwa saya tidak ingin mengembalikan secara eksplisit a/adan a/b, saya hanya "tahu" adan ingin mengembalikan semuanya di bawah. EDIT : Dan saya juga tidak "tahu" b, atau direktori lain yang berada pada tingkat yang sama dengan 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
sumber
3
bagaimana dengan git stash && git stash drop?
CharlesB
1
bagaimana git checkout -- /path/to/subdir/?
iberbeu
3
@CharlesB: git stashtidak menerima argumen jalur ...
krlmlr
@ iberbeu: Tidak. Juga akan menambahkan file yang dikecualikan oleh checkout jarang.
krlmlr
1
@CharlesBailey: Lalu mengapa ada tombol radio bertuliskan "Mencari gambar jawaban dari sumber yang kredibel dan / atau resmi." dalam dialog karunia? Saya tidak mengetik itu sendiri! Coba juga googling "subditory git reset" (tanpa tanda kutip) dan lihat apa yang ada di 3 posisi pertama. Pasti pesan ke mailing list kernel.org akan lebih sulit ditemukan. - Juga, bagi saya belum jelas apakah perilaku ini bug atau fitur.
krlmlr

Jawaban:

162

Dengan Git 2.23 (Agustus 2019), Anda memiliki perintah barugit restore

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

Itu akan menggantikan kedua indeks dan pohon kerja dengan HEADkonten, seperti reset --hardakan, tetapi untuk jalur tertentu.


Jawaban asli (2013)

Perhatikan (seperti yang dikomentari oleh Dan Fabulich ) bahwa:

  • git checkout -- <path> tidak melakukan hard reset: ia mengganti konten pohon yang berfungsi dengan konten yang dipentaskan.
  • git checkout HEAD -- <path>melakukan reset keras untuk jalan, mengganti indeks dan pohon yang bekerja dengan versi dari HEADkomit.

Sebagaimana dijawab oleh Ajedi32 , kedua formulir checkout tidak menghapus file yang dihapus dalam revisi target .
Jika Anda memiliki file tambahan di pohon kerja yang tidak ada di HEAD, a git checkout HEAD -- <path>tidak akan menghapusnya.

Catatan: Dengan git checkout --overlay HEAD -- <path>(Git 2.22, Q1 2019) , file yang muncul dalam indeks dan pohon yang berfungsi, tetapi tidak dalam <tree-ish>dihapus, untuk membuatnya sama <tree-ish>persis.

Tetapi checkout itu dapat menghormati a git update-index --skip-worktree(untuk direktori yang ingin Anda abaikan), sebagaimana disebutkan dalam " Mengapa file yang dikecualikan tetap muncul kembali di checkout git sparse saya? ".

VONC
sumber
1
Mohon klarifikasi. Setelah itu git checkout HEAD -- ., file yang dikecualikan oleh checkout jarang muncul kembali. Apa yang git update-index --skip-worktreeseharusnya dilakukan?
krlmlr
@krlmlr skip-worktree atau menganggap-tidak berubah adalah dua cara untuk mencoba membuat entri dalam indeks "tidak terlihat" untuk git: fallengamer.livejournal.com/93321.html , stackoverflow.com/q/13630849/6309 dan stackoverflow. com / a / 6139470/6309
VonC
@ krlmlr tautan-tautan itu hanyalah petunjuk bagi Anda untuk mencoba dan melihat apakah checkout masih akan mengembalikan entri-entri itu, begitu mereka telah ditandai sebagai 'skipping-worktree'.
VonC
Maaf, tapi itu terlalu rumit untuk tugas yang dihadapi. Saya ingin reset inklusif, bukan yang eksklusif. Apakah benar-benar tidak ada cara yang baik untuk melakukan ini di Git?
krlmlr
@ krlmlr no: terbaik untuk dilakukan git checkout HEAD -- <path>, dan kemudian hapus direktori yang dipulihkan (tetapi yang masih dinyatakan dalam checkout jarang).
VonC
125

Menurut pengembang Git Duy Nguyen yang dengan ramah mengimplementasikan fitur dan sakelar kompatibilitas , berikut ini berfungsi seperti yang diharapkan pada Git 1.8.3 :

git checkout -- a

(di mana adirektori yang ingin Anda atur ulang). Perilaku asli dapat diakses melalui

git checkout --ignore-skip-worktree-bits -- a
krlmlr
sumber
5
Terima kasih atas upaya Anda menindaklanjuti dengan tim pengembangan Git, menghasilkan perubahan dalam Git ini.
Dan Cruz
13
Dan perhatikan bahwa "a" dalam hal ini berarti direktori yang ingin Anda kembalikan, jadi jika Anda berada di direktori yang ingin Anda kembalikan, perintahnya adalah di git checkout -- .mana .artinya direktori saat ini.
TheWestIsThe ...
5
Satu komentar dari pihak saya adalah bahwa Anda harus membatalkan stage folder terlebih dahulu git reset -- a(di mana a adalah direktori yang ingin Anda atur ulang)
Boyan
Bukankah itu benar juga jika Anda ingin git reset --hardrepo keseluruhan?
krlmlr
Dan jika Anda menambahkan file baru ke direktori itu, lakukan rm -rf asebelumnya.
Tobias Feil
30

Coba ubah

git checkout -- a

untuk

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

Sejak versi 1.7.0, Git menghormati bendera skip-worktree .ls-files

Menjalankan skrip pengujian Anda (dengan beberapa perubahan kecil yang mengubah git commit... ke git commit -qdan git statuske git status --short) menghasilkan:

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

Menjalankan skrip pengujian Anda dengan checkoutoutput perubahan yang diusulkan :

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
sumber
Kedengarannya bagus. Tapi bukankah seharusnya git checkoutmenghormati bit "skip-worktree"?
krlmlr
Tinjauan singkat checkout.cdan tree.ctidak mengungkapkan bahwa flag skip-worktree digunakan.
Dan Cruz
Ini cukup sederhana untuk berguna dalam praktik, bahkan jika saya harus mengatur alias bash untuk perintah ini. Duy Nguyen telah membalas pesan saya ke milis Git, mari kita lihat apakah alternatif yang lebih ramah pengguna akan segera muncul.
krlmlr
18

Untuk kasus hanya membuang perubahan, git checkout -- path/atau git checkout HEAD -- path/perintah yang disarankan oleh jawaban lain berfungsi dengan baik. Namun, ketika Anda ingin mengatur ulang direktori ke revisi selain HEAD, solusi itu memiliki masalah yang signifikan: itu tidak menghapus file yang dihapus di revisi target.

Jadi sebagai gantinya, saya sudah mulai menggunakan perintah berikut:

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

Ini berfungsi dengan menemukan perbedaan antara komit target dan indeks, kemudian menerapkan perbedaan itu secara terbalik ke direktori kerja dan indeks. Pada dasarnya, ini berarti membuat isi indeks cocok dengan isi revisi yang Anda tentukan. Fakta yang git diffmengambil argumen jalur memungkinkan Anda membatasi efek ini ke file atau direktori tertentu.

Karena perintah ini cukup panjang dan saya berencana untuk sering menggunakannya, saya telah mengatur alias untuknya yang saya beri nama reset-checkout:

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

Anda bisa menggunakannya seperti ini:

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

Atau hanya:

git reset-checkout 451a9a4
Ajedi32
sumber
Saya melihat komentar Anda kemarin dan mencobanya hari ini. Alias ​​Anda sangat membantu. +1
VonC
Bagaimana opsi ini dibandingkan dengan git checkout --overlay HEAD -- <path>perintah yang @VonC sebutkan dalam jawabannya?
Ehtesh Choudhury
1
@EhteshChoudhury Note yang git checkout --overlay HEAD -- <path>belum dirilis (Git 2.22 akan dirilis pada Q2 2019)
VonC
5

Saya akan menawarkan opsi yang mengerikan di sini, karena saya tidak tahu bagaimana melakukan apa pun dengan git kecuali add commitdan push, inilah cara saya "mengembalikan" sebuah subdirektori:

Saya memulai repositori baru di komputer lokal saya, mengembalikan semuanya ke komit yang ingin saya salin kode dari dan kemudian menyalin file-file itu ke direktori kerja saya, add commit pushdan voila. Jangan membenci pemain, benci Tuan Torvalds karena lebih pintar dari kita semua.

Abraham Brookes
sumber
4

Reset biasanya akan mengubah segalanya, tetapi Anda dapat menggunakan git stashuntuk memilih apa yang ingin Anda simpan. Seperti yang Anda sebutkan, stashtidak menerima jalur secara langsung, tetapi masih dapat digunakan untuk menyimpan jalur tertentu dengan --keep-indexbendera. Dalam contoh Anda, Anda akan menyimpan direktori b, lalu mengatur ulang yang lainnya.

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

Ini membawa Anda ke titik di mana bagian terakhir dari skrip Anda akan menampilkan ini:

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

Saya percaya ini adalah hasil target (b tetap dimodifikasi, a / * file kembali, a / c tidak diciptakan kembali).

Pendekatan ini memiliki manfaat tambahan karena sangat fleksibel; Anda bisa mendapatkan serinci yang Anda inginkan untuk menambahkan file tertentu, tetapi tidak yang lain, dalam direktori.

Jonathan Wren
sumber
Itu bagus, tapi aku harus melakukan git addsemuanya kecuali a, kan? Kedengarannya sulit dalam latihan.
krlmlr
1
@ krlmlr Tidak juga. Anda git add .kemudian git reset adapat menambahkan semuanya kecuali a.
Jonathan Wren
1
@ krlmlr Juga, perlu dicatat bahwa git addtidak menambahkan file yang dihapus. Jadi, jika Anda hanya memulihkan file yang dihapus, git add .akan menambahkan semua file yang dimodifikasi, tetapi tidak yang dihapus.
Jonathan Wren
3

Jika ukuran subdirektori tidak terlalu besar, DAN Anda ingin menjauh dari CLI, berikut ini adalah solusi cepat untuk mengatur ulang subdirektori secara manual :

  1. Beralihlah ke cabang utama dan salin sub-direktori untuk diatur ulang.
  2. Sekarang kembali ke cabang fitur Anda dan ganti sub-direktori dengan salinan yang baru saja Anda buat pada langkah 1.
  3. Komit perubahan.

Bersulang. Anda cukup mereset sub-direktori secara manual di cabang fitur Anda menjadi sama dengan cabang master !!

Mehul Parmar
sumber
Saya tidak melihat suara untuk jawaban ini, tetapi kadang-kadang ini adalah rute yang paling sederhana untuk sukses.
Sue Spence
1

Ajedi32 's jawabannya adalah apa yang saya cari tapi untuk beberapa komit aku berlari ke kesalahan ini:

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

Mungkin karena beberapa file direktori adalah file biner. Menambahkan opsi '--binary' ke perintah git diff memperbaikinya:

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

Bagaimana dengan

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
sumber