Tôi có thể sử dụng Git để tìm kiếm các tên tệp phù hợp trong kho lưu trữ không?


76

Chỉ cần nói rằng tôi có một tệp: "HelloWorld.pm" trong nhiều thư mục con trong kho lưu trữ Git.

Tôi muốn phát hành một lệnh để tìm đường dẫn đầy đủ của tất cả các tệp khớp với "HelloWorld.pm":

Ví dụ:

/path/to/repository/HelloWorld.pm
/path/to/repository/but/much/deeper/down/HelloWorld.pm
/path/to/repository/please/dont/make/me/search/through/the/lot/HelloWorld.pm

Làm cách nào tôi có thể sử dụng Git để tìm một cách hiệu quả tất cả các đường dẫn đầy đủ phù hợp với một tên tệp nhất định?

Tôi nhận ra mình có thể thực hiện việc này bằng lệnh tìm Linux / Unix nhưng tôi đã hy vọng tránh quét tất cả các thư mục con để tìm kiếm các bản sao của tên tệp.

Câu trả lời:


114

git ls-filessẽ cung cấp cho bạn danh sách tất cả các tệp ở trạng thái hiện tại của kho lưu trữ (bộ nhớ cache hoặc chỉ mục). Bạn có thể chuyển một mẫu vào để nhận các tệp phù hợp với mẫu đó.

git ls-files HelloWorld.pm '**/HelloWorld.pm'

Nếu bạn muốn tìm một tập hợp các tệp và tra cứu nội dung của chúng, bạn có thể làm điều đó với git grep:

git grep some-string -- HelloWorld.pm '**/HelloWorld.pm'

tệp ls cũng có thể có một mẫu.
Josh Lee

1
Hãy nhớ sử dụng '** / HelloWorld.pm' thay vì '* / HelloWorld.pm' để tìm kiếm bất kỳ độ sâu nào của kho lưu trữ cho các kết quả phù hợp. Ví dụ của OP có các tệp ở nhiều cấp độ khác nhau.
John Rix

8
'git ls-files' không liệt kê các tệp trong kho lưu trữ. Nó liệt kê các tên tệp trong chỉ mục (vùng dàn dựng) hoặc cây làm việc. Hoàn toàn bình thường khi một tên tệp nằm ở đâu đó trong kho lưu trữ nhưng không nằm trong chỉ mục hoặc cây làm việc - ví dụ: tên tệp có thể nằm trên một nhánh khác với tên bạn hiện đã kiểm tra. Câu trả lời của @GregHewgill nên được coi là đúng hơn ở đây.
stevegt

1
(Bỏ lỡ cửa sổ chỉnh sửa nhận xét kéo dài 5 phút ...) Câu trả lời của Uwe Geuder và Dean Hall về cơ bản mở rộng trên Greg's, bằng cách lặp qua tất cả các nhánh và thẻ, xử lý trường hợp các tệp có tên trên các nhánh khác (hoặc đã bị xóa) .
stevegt

1
lưu ý rằng điều này sẽ không tìm thấy HelloWorld.pm ở gốc dự án của bạn. Trong trường hợp đó bạn cần sử dụnggit ls-files 'HelloWorld.pm' '*/HelloWorld.pm'
Chris Maes

44

Hmm, câu hỏi ban đầu là về kho lưu trữ. Một kho lưu trữ chứa nhiều hơn 1 cam kết (trong trường hợp chung là ít nhất), nhưng các câu trả lời được đưa ra trước khi tìm kiếm chỉ thông qua một cam kết.

Bởi vì tôi không thể tìm thấy câu trả lời thực sự tìm kiếm toàn bộ lịch sử cam kết, tôi đã viết một tập lệnh nhanh brute force git-find-by-name mà (gần như) xem xét tất cả các cam kết.

#! /bin/sh
tmpdir=$(mktemp -td git-find.XXXX)
trap "rm -r $tmpdir" EXIT INT TERM

allrevs=$(git rev-list --all)
# well, nearly all revs, we could still check the log if we have
# dangling commits and we could include the index to be perfect...

for rev in $allrevs
do
  git ls-tree --full-tree -r $rev >$tmpdir/$rev 
done

cd $tmpdir
grep $1 * 

Có lẽ có một cách thanh lịch hơn.

Xin lưu ý rằng cách thông số được truyền vào grep, vì vậy nó sẽ khớp với các phần của tên tệp. Nếu điều đó không được mong muốn, hãy neo biểu thức tìm kiếm của bạn và / hoặc thêm các tùy chọn grep phù hợp.

Đối với lịch sử sâu, đầu ra có thể quá ồn ào, tôi đã nghĩ về một tập lệnh chuyển đổi danh sách các bản sửa đổi thành một phạm vi, giống như điều ngược lại với những gì git rev-list có thể làm. Nhưng cho đến nay nó vẫn chỉ là một suy nghĩ.


Kịch bản tuyệt vời. Tuy nhiên tôi đã không thể sử dụng nó vì git repo của tôi là rất lớn mà kịch bản ngập ổ cứng của tôi :(
Arne Böckmann

@ ArneBöckmann Chỉ cần di chuyển lệnh grep vào vòng lặp cuối cùng và xóa mọi thứ sau mỗi lần grep.
Uwe Geuder 12/1213

9
Mã của bạn có thể được làm thành một lớp lót: git rev-list --all | xargs -I '{}' git ls-tree --full-tree -r '{}' | grep '.*HelloWorld\.pm$'. Điều này cũng giải quyết vấn đề ngập ổ cứng.
subhacom

@subhacom oneliner của bạn phải là câu trả lời được chấp nhận
hobs

24

Thử:

git ls-tree -r HEAD | grep HelloWorld.pm

1
Hoặc trên Windows:git ls-tree -r HEAD | findstr HelloWorld.pm
John Rix

man git ls-treecho thấy điều đó -rcó nghĩa là "Đệ quy vào cây con". Tôi không biết điều đó có nghĩa là gì. bạn có thể vui lòng giải thích điều này có nghĩa gì?
Gabriel Staples

@JohnRix, lần cuối tôi đã kiểm tra, nếu bạn đang sử dụng thiết bị đầu cuối được cung cấp bởi Git cho Windows , mà tôi thực sự khuyên dùng trên Windows, thì nó hỗ trợ các lệnh Linux phổ biến như đường ống đến grep, chạy tập lệnh bash, v.v., vì vậy câu trả lời này sẽ hoạt động tốt nguyên trạng. Hãy thử và cho tôi biết. Tôi đã bỏ hoàn toàn Windows cho Ubuntu vài năm trước.
Gabriel Staples

@GabrielStaples, đúng hay sai, tôi hơi khó hiểu khi nói đến các thiết bị đầu cuối thay thế trong Windows (có lẽ một phần do bị CygWin làm cho nâu nhiều năm trước) và có xu hướng gắn bó với mẫu số chung thấp nhất sẽ luôn sẵn sàng cho tôi. (Mặt khác, việc phát hành WSL 2 trên Windows 10 sắp xảy ra và các báo cáo cho biết nó sẽ hoạt động rất hiệu quả, vì vậy có lẽ cuối cùng tôi sẽ nói lời tạm biệt với dấu nhắc lệnh cũ của Windows!)
John Rix

Nhân tiện, -rnên làm cho lệnh ls-tree tìm kiếm thông qua các thư mục con trong kho lưu trữ.
John Rix


4

[Tôi thừa nhận đó là một chút lạm dụng bình luận, nhưng tôi chưa thể bình luận và nghĩ rằng tôi sẽ cải thiện câu trả lời của @ uwe-geuder.]

#!/bin/bash
#
#

# I'm using a fixed string here, not a regular expression, but you can easily
# use a regular expression by altering the call to grep below.
name="$1"

# Verify usage.
if [[ -z "$name" ]]
then
    echo "Usage: $(basename "$0") <file name>" 1>&2
    exit 100
fi  

# Search all revisions; get unique results.
while IFS= read rev
do
    # Find $name in $rev's tree and only use its path.
    grep -F -- "$name" \
        <(git ls-tree --full-tree -r "$rev" | awk '{ print $4 }')
done < \
    <(git rev-list --all) \
    | sort -u

Một lần nữa, hãy +1 đến @ uwe-geuder để có câu trả lời tuyệt vời.

Nếu bạn quan tâm đến BASH:

Trừ khi bạn được đảm bảo về khả năng tách từ trong vòng lặp for (như khi sử dụng một mảng như thế này for item in "${array[@]}":), tôi thực sự khuyên bạn nên sử dụng while IFS= read var ; do ... ; done < <(command)khi đầu ra lệnh mà bạn đang lặp lại được phân tách bằng các dòng mới (hoặc read -d''khi đầu ra được phân tách bằng chuỗi null $'\0'). Mặc dù git rev-list --allđược đảm bảo sử dụng chuỗi thập lục phân 40 byte (không có dấu cách), tôi không bao giờ muốn chấp nhận. Bây giờ tôi có thể dễ dàng thay đổi lệnh từ git rev-list --allbất kỳ lệnh nào tạo ra các dòng

Tôi cũng khuyên bạn nên sử dụng cơ chế BASH tích hợp để đưa đầu vào và lọc đầu ra thay vì các tệp tạm thời.


Bạn không chắc chắn lý do tại sao thay nhiều quá trình đang được sử dụng, khi bạn có thể chỉ đơn giản là ống:git rev-list --all | while read rev; do; git ls-tree --full-tree -r $rev | cut -c54- | fgrep -- "$name"; done | sort -u
Simon Buchan

Tập lệnh echos, nhưng không tìm thấy nó ở bản sửa đổi nào. Hữu ích để cũng vang $revđể hiển thị những gì các phiên bản đó có trong.
LB2

2

Tập lệnh của Uwe Geuder (@ uwe-geuder) rất tuyệt nhưng thực sự không cần phải đổ từng đầu ra ls-tree vào thư mục riêng, chưa được lọc.

Nhanh hơn nhiều và sử dụng ít dung lượng hơn: Chạy grep trên đầu ra và sau đó lưu trữ nó, như được hiển thị trong ý chính này


ý chính có thể thay đổi và tốt hơn hết là bạn nên đưa đoạn mã vào câu trả lời của mình để thuận tiện, đặc biệt là khi đoạn mã ngắn. Tôi khuyên bạn nên sao chép đoạn mã từ ý chính vào câu trả lời của mình. Chỉ cần để lại liên kết đến ý chính là tất cả để trích dẫn nó như là nguồn trong trường hợp bạn từng cập nhật ý chính nhưng không có câu trả lời này.
Gabriel Staples

Bây giờ tôi xem xét kịch bản của bạn kỹ hơn, tôi thấy điều này thực sự hữu ích. Tuy nhiên, câu trả lời của bạn cần 1) tiêu đề: # How to find a long-lost file by searching all commitsvà 2) mã từ ý chính được dán trực tiếp vào câu trả lời này.
Gabriel Staples
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.