Làm cách nào để thiết lập lại git - có một thư mục con?


197

CẬP NHẬT : Với Git 2.23 (tháng 8 năm 2019), có một lệnh mới git restorethực hiện điều này, hãy xem câu trả lời được chấp nhận .

CẬP NHẬT : Điều này sẽ hoạt động trực quan hơn kể từ Git 1.8.3, xem câu trả lời của riêng tôi .

Hãy tưởng tượng trường hợp sử dụng sau: Tôi muốn loại bỏ tất cả các thay đổi trong một thư mục con cụ thể của cây làm việc Git của tôi, để lại tất cả các thư mục con khác.

Lệnh Git thích hợp cho hoạt động này là gì?

Kịch bản dưới đây minh họa vấn đề. Chèn lệnh thích hợp bên dưới How to make filesnhận xét - lệnh hiện tại sẽ khôi phục tệp a/c/acđược cho là bị loại trừ bởi kiểm tra thưa thớt. Lưu ý rằng tôi không muốn khôi phục rõ ràng a/aa/btôi chỉ "biết" avà muốn khôi phục mọi thứ bên dưới. EDIT : Và tôi cũng không "biết" b, hoặc những thư mục khác nằm cùng cấp với 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
một git stash && git stash dropcái gì
CharlesB

1
những gì về git checkout -- /path/to/subdir/?
iberbeu

3
@CharlesB: git stashkhông chấp nhận đối số đường dẫn ...
krlmlr

@iberbeu: Không. Cũng sẽ thêm các tập tin loại trừ bằng cách kiểm tra thưa thớt.
krlmlr

1
@CharlesBailey: Vậy thì tại sao lại có nút radio "Tìm kiếm câu trả lời rút ra từ các nguồn đáng tin cậy và / hoặc chính thức." trong hộp thoại tiền thưởng? Tôi đã không gõ nó cho mình! Ngoài ra, hãy thử googling "thư mục con git reset" (không có dấu ngoặc kép) và xem những gì trên 3 vị trí đầu tiên. Để chắc chắn một tin nhắn đến danh sách gửi thư kernel.org sẽ khó tìm hơn. - Ngoài ra, với tôi vẫn chưa rõ nếu hành vi này là một lỗi hoặc một tính năng.
krlmlr

Câu trả lời:


162

Với Git 2.23 (tháng 8 năm 2019), bạn có lệnh mớigit restore

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

Điều đó sẽ thay thế cả chỉ mục và cây làm việc bằng HEADnội dung, giống như reset --hard, nhưng cho một đường dẫn cụ thể.


Câu trả lời gốc (2013)

Lưu ý (như nhận xét của Dan Fabulich ) rằng:

  • git checkout -- <path> không thực hiện thiết lập lại cứng: nó thay thế nội dung của cây làm việc bằng nội dung được dàn dựng.
  • git checkout HEAD -- <path>thực hiện thiết lập lại cứng cho một đường dẫn, thay thế cả chỉ mục và cây làm việc bằng phiên bản từ HEADcam kết.

Như đã trả lời bởi Ajedi32 , cả hai hình thức thanh toán không gỡ bỏ những file đó đã bị xóa trong phiên bản mục tiêu .
Nếu bạn có các tệp bổ sung trong cây làm việc không tồn tại trong CHÍNH, git checkout HEAD -- <path>sẽ không xóa chúng.

Lưu ý: Với git checkout --overlay HEAD -- <path> (Git 2.22, Q1 2019) , các tệp xuất hiện trong chỉ mục và cây làm việc, nhưng không <tree-ish>được xóa, để làm cho chúng khớp <tree-ish>chính xác.

Nhưng thanh toán đó có thể tôn trọng git update-index --skip-worktree(đối với những thư mục bạn muốn bỏ qua), như đã đề cập trong phần " Tại sao các tệp bị loại trừ lại xuất hiện trong thanh toán thưa thớt của tôi? ".


1
Vui lòng làm rõ. Sau đó git checkout HEAD -- ., các tệp bị loại trừ bởi kiểm tra thưa thớt xuất hiện trở lại. Phải git update-index --skip-worktreelàm gì đây?
krlmlr

@krlmlr Skip-worktree hoặc giả định không thay đổi là hai cách cố gắng tạo một mục trong chỉ mục "vô hình" cho git: fallengamer.livejournal.com/93321.html , stackoverflow.com/q/13630849/6309stackoverflow. com / a / 6139470/6 630
VonC

@krlmlr các liên kết đó chỉ là con trỏ để bạn thử và xem liệu thanh toán có còn khôi phục các mục đó không, khi chúng được đánh dấu là 'Skipped-worktree'.
VonC

Xin lỗi, nhưng điều đó quá phức tạp cho nhiệm vụ trong tay. Tôi muốn thiết lập lại bao gồm, không phải là độc quyền. Có thực sự không có cách tốt đẹp để làm điều này trong Git?
krlmlr

@krlmlr no: tốt nhất để làm git checkout HEAD -- <path>, và sau đó xóa các thư mục đã được khôi phục (nhưng vẫn được khai báo trong thanh toán thưa thớt).
VonC

125

Theo nhà phát triển Git Duy Nguyễn, người vui lòng thực hiện tính năng và chuyển đổi tương thích , các công việc sau đây được mong đợi kể từ Git 1.8.3 :

git checkout -- a

(đâu alà thư mục bạn muốn thiết lập lại). Hành vi ban đầu có thể được truy cập thông qua

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

5
Cảm ơn bạn đã nỗ lực theo dõi nhóm phát triển Git, dẫn đến thay đổi này trong Git.
Dan Cruz

13
Và lưu ý rằng "a" trong trường hợp này có nghĩa là thư mục bạn muốn hoàn nguyên, vì vậy nếu bạn đang ở trong thư mục bạn muốn hoàn nguyên, lệnh sẽ là git checkout -- .nơi .có nghĩa là thư mục hiện tại.
TheWestIsThe ...

5
Một nhận xét từ phía tôi là trước tiên bạn phải bỏ thư mục với git reset -- a(trong đó a là thư mục bạn muốn đặt lại)
Boyan

Điều đó cũng không đúng nếu bạn muốn git reset --hardtoàn bộ repo?
krlmlr

Và nếu bạn đã thêm bất kỳ tập tin mới vào thư mục đó, hãy làm rm -rf atrước.
Tobias Feil

30

Hãy thử thay đổi

git checkout -- a

đến

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

Kể từ phiên bản 1.7.0, Git vinh danh cờ Skip-worktree .ls-files

Chạy tập lệnh thử nghiệm của bạn (với một số điều chỉnh nhỏ thay đổi git commit... thành git commit -qgit statussang git status --short) đầu ra:

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

Chạy tập lệnh thử nghiệm của bạn với các checkoutđầu ra thay đổi được đề xuất :

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

Âm thanh tốt. Nhưng không nên git checkouttôn trọng bit "Skip-worktree" ngay từ đầu?
krlmlr

Một đánh giá nhanh về checkout.ctree.ckhông tiết lộ rằng cờ Skip-worktree được sử dụng.
Dan Cruz

Điều này đủ đơn giản để hữu ích trong thực tế, ngay cả khi tôi sẽ phải thiết lập bí danh bash cho lệnh này. Duy Nguyễn đã trả lời tin nhắn của tôi vào danh sách gửi thư của Git, hãy xem liệu một lựa chọn thân thiện hơn với người dùng sẽ sớm xuất hiện.
krlmlr

18

Đối với trường hợp chỉ đơn giản là loại bỏ các thay đổi, git checkout -- path/hoặc git checkout HEAD -- path/các lệnh được đề xuất bởi các câu trả lời khác hoạt động rất tốt. Tuy nhiên, khi bạn muốn đặt lại thư mục thành bản sửa đổi khác với HEAD, giải pháp đó có một vấn đề quan trọng: nó không xóa các tệp đã bị xóa trong bản sửa đổi đích.

Vì vậy, thay vào đó, tôi đã bắt đầu sử dụng lệnh sau:

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

Điều này hoạt động bằng cách tìm khác biệt giữa cam kết đích và chỉ mục, sau đó áp dụng khác biệt đó ngược lại với thư mục và chỉ mục làm việc. Về cơ bản, điều này có nghĩa là nó làm cho nội dung của chỉ mục khớp với nội dung của bản sửa đổi mà bạn đã chỉ định. Thực tế làgit diff có một đối số đường dẫn cho phép bạn giới hạn hiệu ứng này vào một tệp hoặc thư mục cụ thể.

Vì lệnh này khá dài và tôi dự định sử dụng nó thường xuyên, tôi đã thiết lập một bí danh cho nó mà tôi đặt tên reset-checkout:

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

Bạn có thể sử dụng nó như thế này:

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

Hoặc chỉ:

git reset-checkout 451a9a4

Tôi thấy bình luận của bạn ngày hôm qua và thử nghiệm nó ngày hôm nay. Bí danh của bạn là hữu ích. +1
VonC

Làm thế nào để tùy chọn này so sánh với git checkout --overlay HEAD -- <path>lệnh mà @VonC đề cập trong câu trả lời của anh ấy?
Ehtesh Choudhury

1
@EhteshChoudhury Lưu ý rằng git checkout --overlay HEAD -- <path>chưa được phát hành (Git 2.22 sẽ được phát hành vào quý 2 năm 2019)
VonC

5

Tôi sẽ cung cấp một tùy chọn khủng khiếp ở đây, vì tôi không biết làm thế nào để làm bất cứ điều gì với git ngoại trừ add commitpushđây là cách tôi "hoàn nguyên" một thư mục con:

Tôi đã bắt đầu một kho lưu trữ mới trên máy tính cục bộ của mình, hoàn nguyên toàn bộ cho cam kết mà tôi muốn sao chép mã từ đó và sau đó sao chép các tệp đó vào thư mục làm việc của tôi, add commit pushet voila. Đừng ghét người chơi, ghét Mr Torvalds vì thông minh hơn tất cả chúng ta.


4

Một thiết lập lại thường sẽ thay đổi mọi thứ, nhưng bạn có thể sử dụng git stashđể chọn những gì bạn muốn giữ. Như bạn đã đề cập, stashkhông chấp nhận một đường dẫn trực tiếp, nhưng nó vẫn có thể được sử dụng để giữ một đường dẫn cụ thể với --keep-indexcờ. Trong ví dụ của bạn, bạn sẽ bỏ thư mục b, sau đó đặt lại mọi thứ khác.

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

Điều này đưa bạn đến một điểm mà phần cuối của tập lệnh của bạn sẽ xuất ra điều này:

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

Tôi tin rằng đây là kết quả đích (b vẫn được sửa đổi, tệp a / * đã trở lại, a / c không được tạo lại).

Cách tiếp cận này có thêm lợi ích là rất linh hoạt; bạn có thể có được chi tiết như bạn muốn thêm các tệp cụ thể, nhưng không phải các tệp khác, trong một thư mục.


Điều đó thật tuyệt, nhưng tôi phải làm git addmọi thứ trừ a, phải không? Nghe có vẻ khó khăn trong thực tế.
krlmlr

1
@krlmlr Không hẳn. git add .Sau đó bạn có thể git reset athêm mọi thứ trừ a.
Jonathan Wren

1
@krlmlr Ngoài ra, đáng chú ý là git addkhông thêm các tệp đã bị xóa. Vì vậy, nếu bạn chỉ khôi phục các tệp đã xóa, git add .sẽ thêm tất cả các tệp đã sửa đổi, nhưng không phải các tệp đã bị xóa.
Jonathan Wren

3

Nếu kích thước của thư mục con không đặc biệt lớn, VÀ bạn muốn tránh xa CLI, đây là một giải pháp nhanh chóng để đặt lại thư mục con theo cách thủ công :

  1. Chuyển sang nhánh chính và sao chép thư mục con sẽ được đặt lại.
  2. Bây giờ chuyển trở lại nhánh tính năng của bạn và thay thế thư mục con bằng bản sao bạn vừa tạo ở bước 1.
  3. Cam kết thay đổi.

Chúc mừng. Bạn chỉ cần đặt lại thủ công một thư mục con trong nhánh tính năng của mình giống với thư mục chính !!


Tôi đã không thấy bất kỳ phiếu bầu nào cho câu trả lời này, nhưng đôi khi đây là con đường được bảo đảm đơn giản nhất để thành công.
Sue Spence

1

Câu trả lời của Ajedi32 là những gì tôi đang tìm kiếm nhưng đối với một số cam kết tôi đã gặp phải lỗi này:

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

Có thể là do một số tệp của thư mục là tệp nhị phân. Thêm tùy chọn '--binary' vào lệnh git diff đã sửa nó:

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

0

Thế còn

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 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.