Kiểm tra chỉ mục bẩn hoặc các tệp không bị theo dõi bằng Git


243

Làm cách nào để kiểm tra xem tôi có bất kỳ thay đổi nào không được cam kết trong kho git của mình không:

  1. Thay đổi được thêm vào chỉ mục nhưng không được cam kết
  2. Các tập tin không bị theo dõi

từ một kịch bản?

git-status dường như luôn trả về số 0 với phiên bản git 1.6.4.2.


3
trạng thái git sẽ trả về 1 nếu có các tệp sửa đổi chưa được chỉnh sửa. Nhưng nói chung, tôi thấy rằng các công cụ git không đặc biệt kỹ lưỡng với trạng thái trả về. EG git diff trả về 0 dù có khác biệt hay không.
trực giác

11
@intuited: Nếu bạn cần diffchỉ ra sự hiện diện hay vắng mặt của sự khác biệt thay vì chạy lệnh thành công thì bạn cần sử dụng --exit-codehoặc --quiet. Các lệnh git thường rất phù hợp với việc trả về mã thoát 0 hoặc không bằng 0 để biểu thị sự thành công của lệnh.
CB Bailey

1
@Charles Bailey: Hey tuyệt vời, tôi đã bỏ lỡ tùy chọn đó. Cảm ơn! Tôi đoán rằng tôi không bao giờ thực sự cần nó để làm điều đó, hoặc tôi có thể đã truy quét trang này cho nó. Vui mừng bạn đã sửa chữa cho tôi :)
trực quan

1
robert - đây là một chủ đề rất khó hiểu đối với cộng đồng (không giúp "đồ sứ" được sử dụng khác nhau trong các bối cảnh khác nhau). Câu trả lời bạn đã chọn bỏ qua câu trả lời được bình chọn cao hơn 2,5 lần, mạnh mẽ hơn và tuân theo thiết kế git. Bạn đã thấy câu trả lời của @ChrisJ chưa?
mike

1
@Robert - Tôi hy vọng bạn có thể xem xét thay đổi câu trả lời được chấp nhận của mình. Người bạn đã chọn dựa vào các lệnh 'sứ' dễ vỡ hơn; câu trả lời đúng thực tế (cũng với gấp đôi số lần nâng cấp) phụ thuộc vào các lệnh git 'ống nước' chính xác. Câu trả lời đúng đó ban đầu là của Chris Johnsen. Tôi đưa ra điều này vì hôm nay là lần thứ 3 tôi phải giới thiệu ai đó đến trang này, nhưng tôi không thể chỉ ra câu trả lời, tôi đã giải thích tại sao câu trả lời được chấp nhận là không tối ưu / sai giới hạn. Cảm ơn bạn!
mike

Câu trả lời:


173

Thời gian tuyệt vời! Tôi đã viết một bài đăng trên blog về chính xác điều này một vài ngày trước, khi tôi tìm ra cách thêm thông tin trạng thái git vào lời nhắc của tôi.

Đây là những gì tôi làm:

  1. Đối với tình trạng bẩn:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. Đối với các tệp không bị theo dõi (Lưu ý --porcelaincờ sẽ git statuscung cấp cho bạn đầu ra có khả năng phân tích cú pháp tốt):

    # Returns the number of untracked files
    
    function evil_git_num_untracked_files {
      expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` 
    }

Mặc dù git diff --shortstatthuận tiện hơn, bạn cũng có thể sử dụng git status --porcelainđể nhận các tệp bẩn:

# Get number of files added to the index (but uncommitted)
expr $(git status --porcelain 2>/dev/null| grep "^M" | wc -l)

# Get number of files that are uncommitted and not added
expr $(git status --porcelain 2>/dev/null| grep "^ M" | wc -l)

# Get number of total uncommited files
expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)

Lưu ý: Bộ 2>/dev/nulllọc ra các thông báo lỗi để bạn có thể sử dụng các lệnh này trên các thư mục không phải là git. (Đơn giản là họ sẽ trả lại 0số lượng tập tin.)

Chỉnh sửa :

Dưới đây là bài viết:

Thêm thông tin trạng thái Git vào Nhắc thiết bị đầu cuối của bạn

Nhắc nhở Shell kích hoạt Git được cải thiện


8
Điều đáng chú ý là việc hoàn thành git bash đi kèm với chức năng shell để thực hiện khá nhiều việc bạn đang làm với lời nhắc của mình - __git_ps1. Nó hiển thị tên chi nhánh, bao gồm cả điều trị đặc biệt nếu bạn đang trong quá trình rebase, áp dụng, hợp nhất hoặc chia đôi. Và bạn có thể đặt biến môi trường GIT_PS1_SHOWDIRTYSTATEđể lấy dấu hoa thị cho những thay đổi không được tổ chức và cộng với thay đổi theo giai đoạn. (Tôi nghĩ rằng bạn cũng có thể lấy nó để chỉ ra các tệp không bị theo dõi và cung cấp cho bạn một số git-describeđầu ra)
Cascabel

8
Một cảnh báo: git diff --shortstatsẽ đưa ra âm tính giả nếu các thay đổi đã có trong chỉ mục.
Marko Topolnik

4
git status --porcelaintốt hơn là vì git diff --shortstatsẽ không bắt được các tệp trống mới tạo. Bạn có thể thử nó trong bất kỳ cây làm việc sạch nào:touch foo && git diff --shortstat
Campadrenalin

7
KHÔNG - sứ có nghĩa là đầu ra là cho con người và dễ dàng phá vỡ !! xem câu trả lời của @ChrisJohnsen, sử dụng chính xác các tùy chọn ổn định, thân thiện với tập lệnh.
mike

7
@mike từ trang man trạng thái git về tùy chọn sứ: "Cung cấp đầu ra ở định dạng dễ phân tích cú pháp cho tập lệnh. Điều này tương tự như đầu ra ngắn, nhưng sẽ ổn định trên các phiên bản Git và bất kể cấu hình người dùng. "
itadok

399

Chìa khóa để tạo ra kịch bản tin cậy của Gv trên Git là sử dụng các lệnh 'ống nước'.

Các nhà phát triển cẩn thận khi thay đổi các lệnh hệ thống ống nước để đảm bảo rằng họ cung cấp các giao diện rất ổn định (nghĩa là sự kết hợp nhất định của trạng thái kho lưu trữ, stdin, tùy chọn dòng lệnh, đối số, v.v. sẽ tạo ra cùng một đầu ra trong tất cả các phiên bản của Git trong đó lệnh / tùy chọn tồn tại). Các biến thể đầu ra mới trong các lệnh hệ thống ống nước có thể được giới thiệu thông qua các tùy chọn mới, nhưng điều đó không thể gây ra bất kỳ vấn đề nào cho các chương trình đã được viết so với các phiên bản cũ hơn (chúng sẽ không sử dụng các tùy chọn mới, vì chúng không tồn tại (hoặc ít nhất là không được sử dụng) tại thời điểm kịch bản được viết).

Thật không may, các lệnh Git 'hàng ngày' là các lệnh 'sứ', vì vậy hầu hết người dùng Git có thể không quen thuộc với các lệnh hệ thống ống nước. Sự khác biệt giữa lệnh sứ và hệ thống ống nước được thực hiện trong trang git chính (xem phần phụ có tiêu đề Lệnh cấp cao (sứ)lệnh cấp thấp (hệ thống ống nước) .


Để tìm hiểu về các thay đổi không được cam kết, bạn có thể sẽ cần git diff-index(so sánh chỉ mục (và có thể các bit của cây làm việc được theo dõi) với một số cây khác (ví dụ HEAD)), có thể git diff-files(so sánh cây làm việc với chỉ mục) và có thể git ls-files(liệt kê các tệp; , tập tin chưa được đăng ký).

(Lưu ý rằng trong các lệnh bên dưới, HEAD --được sử dụng thay HEADvì vì nếu không lệnh sẽ thất bại nếu có tệp có tên HEAD.)

Để kiểm tra xem kho lưu trữ có thay đổi theo giai đoạn (chưa được cam kết hay không), hãy sử dụng:

git diff-index --quiet --cached HEAD --
  • Nếu nó thoát ra 0thì không có sự khác biệt ( 1có nghĩa là có sự khác biệt).

Để kiểm tra xem một cây làm việc có những thay đổi có thể được dàn dựng hay không:

git diff-files --quiet
  • Mã thoát giống như cho git diff-index( 0== không có sự khác biệt; 1== sự khác biệt).

Để kiểm tra xem sự kết hợp của chỉ mục và các tệp được theo dõi trong cây làm việc có thay đổi liên quan đến HEAD:

git diff-index --quiet HEAD --
  • Đây giống như một sự kết hợp của hai phần trước. Một điểm khác biệt chính là nó vẫn sẽ báo cáo không có sự khác biệt nào nếu bạn có một sự thay đổi theo giai đoạn mà bạn có sự hoàn tác của Google trong cây làm việc (quay lại nội dung trong đó HEAD). Trong tình huống tương tự, hai lệnh riêng biệt này sẽ trả về các báo cáo về sự khác biệt của hiện tại.

Bạn cũng đề cập đến các tập tin chưa được theo dõi. Bạn có thể có nghĩa là không bị đánh cắp và không được xếp hạng, hoặc bạn có thể có nghĩa là chỉ đơn giản là không bị đánh cắp (bao gồm các tập tin bị bỏ qua). Dù bằng cách nào, git ls-fileslà công cụ cho công việc:

Đối với những người chưa được theo dõi trên mạng (sẽ bao gồm các tập tin bị bỏ qua, nếu có):

git ls-files --others

Đối với những người khác không bị theo dõi và không được xếp hạng:

git ls-files --exclude-standard --others

Suy nghĩ đầu tiên của tôi là chỉ kiểm tra xem các lệnh này có đầu ra không:

test -z "$(git ls-files --others)"
  • Nếu nó thoát với 0thì không có tập tin nào được theo dõi. Nếu nó thoát với 1thì có các tệp không bị theo dõi.

Có một khả năng nhỏ là điều này sẽ dịch các lối thoát bất thường từ git ls-filessang không có tệp nào không bị theo dõi các báo cáo của Google (cả hai đều dẫn đến các lối thoát khác không của lệnh trên). Một phiên bản mạnh mẽ hơn một chút có thể trông như thế này:

u="$(git ls-files --others)" && test -z "$u"
  • Ý tưởng này giống như lệnh trước đó, nhưng nó cho phép các lỗi không mong muốn từ git ls-fileslan truyền ra ngoài. Trong trường hợp này, một lối thoát khác không có thể có nghĩa là có những tập tin không bị theo dõi, hoặc có thể có nghĩa là đã xảy ra lỗi. Thay vào đó, nếu bạn muốn các kết quả lỗi Lỗi khác kết hợp với các tập tin không bị bẻ khóa, kết quả là sử dụng test -n "$u"(trong đó lối ra của 0phương tiện là một số tập tin không bị bẻ khóa, và không có nghĩa là lỗi hoặc không có các tập tin không bị bẻ khóa).

Một ý tưởng khác là sử dụng --error-unmatchđể gây ra một lối thoát khác không khi không có tệp không bị theo dõi. Điều này cũng có nguy cơ khiến conflating không có các tập tin không bị bẻ khóa. (Thoát 1) với lỗi xảy ra lỗi (thoát không khác, nhưng có lẽ 128). Nhưng việc kiểm tra mã thoát 0so 1với khác không có lẽ khá mạnh mẽ:

git ls-files --others --error-unmatch . >/dev/null 2>&1; ec=$?
if test "$ec" = 0; then
    echo some untracked files
elif test "$ec" = 1; then
    echo no untracked files
else
    echo error from ls-files
fi

Bất kỳ git ls-filesví dụ nào ở trên có thể được thực hiện --exclude-standardnếu bạn chỉ muốn xem xét các tệp không bị theo dõi và không được đánh dấu.


5
Tôi muốn chỉ ra rằng git ls-files --otherscung cấp các tệp không bị theo dõi cục bộ , trong khi git status --porcelaincâu trả lời được chấp nhận cung cấp cho tất cả các tệp không bị theo dõi nằm trong kho git. Tôi không phải là người trong số những người ban đầu muốn, nhưng sự khác biệt giữa cả hai là thú vị.
Eric O Lebigot

1
@phunehehe: Bạn cần cung cấp một pathspec với --error-unmatch. Hãy thử (ví dụ) git ls-files --other --error-unmatch --exclude-standard .(lưu ý khoảng thời gian kéo dài, nó đề cập đến cwd; chạy cái này từ thư mục cấp cao nhất của cây làm việc).
Chris Johnsen

8
@phs: Bạn có thể cần phải thực hiện git update-index -q --refreshtrước khi diff-indexđể tránh một số thông tin tích cực giả mạo khác gây ra bởi thông tin không khớp (2).
Chris Johnsen

1
Tôi đã bị cắn bởi sự git update-indexcần thiết! Điều này rất quan trọng nếu một cái gì đó chạm vào các tập tin mà không thực hiện sửa đổi.
Khỏa thân

2
@RobertSiemer Bởi "cục bộ" Tôi có nghĩa là các tệp trong thư mục hiện tại của bạn , có thể nằm dưới kho git chính của nó. Các --porcelaindanh mục giải pháp tất cả các file untracked tìm thấy trong kho lưu trữ toàn bộ git (file trừ git-lờ), ngay cả khi bạn đang ở trong một trong các thư mục con của nó.
Eric O Lebigot

139

Giả sử bạn đang ở trên git 1.7.0 trở lên ...

Sau khi đọc tất cả các câu trả lời trên trang này và một số thử nghiệm, tôi nghĩ rằng phương pháp đạt được sự kết hợp đúng đắn của sự đúng đắn và ngắn gọn là:

test -n "$(git status --porcelain)"

Mặc dù git cho phép nhiều sắc thái giữa những gì được theo dõi, bỏ qua, không bị theo dõi nhưng không được kiểm soát, v.v., tôi tin rằng trường hợp sử dụng thông thường là để tự động hóa các tập lệnh xây dựng, nơi bạn muốn dừng mọi thứ nếu thanh toán của bạn không sạch.

Trong trường hợp đó, sẽ rất hợp lý khi mô phỏng những gì lập trình viên sẽ làm: gõ git statusvà nhìn vào đầu ra. Nhưng chúng tôi không muốn dựa vào các từ cụ thể hiển thị, vì vậy chúng tôi sử dụng --porcelainchế độ được giới thiệu trong 1.7.0; Khi được bật, một thư mục sạch sẽ không dẫn đến kết quả.

Sau đó, chúng tôi sử dụng test -nđể xem nếu có bất kỳ đầu ra hay không.

Lệnh này sẽ trả về 1 nếu thư mục làm việc sạch và 0 nếu có thay đổi được cam kết. Bạn có thể thay đổi -nthành a -znếu bạn muốn ngược lại. Điều này rất hữu ích để kết nối điều này với một lệnh trong một kịch bản. Ví dụ:

test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"

Điều này nói một cách hiệu quả "hoặc không có thay đổi nào được thực hiện hoặc đặt báo thức"; phần lót này có thể thích hợp hơn cho câu lệnh if tùy thuộc vào tập lệnh bạn đang viết.


1
Đối với tôi, tất cả các lệnh khác đều cho kết quả khác nhau trên cùng một đại diện giữa Linux và windows. Lệnh này đã cho tôi đầu ra giống nhau trong cả hai.
Adarsha

6
Cảm ơn bạn đã trả lời câu hỏi và không lan man, không bao giờ cung cấp một câu trả lời rõ ràng.
NateS

Đối với tập lệnh triển khai thủ công, hãy kết hợp tập lệnh này với test -n "$(git diff origin/$branch)", để giúp ngăn các cam kết cục bộ không được phép triển khai
Erik Aronesty

14

Một triển khai từ câu trả lời của VonC :

if [[ -n $(git status --porcelain) ]]; then echo "repo is dirty"; fi

8

Đã xem qua một vài trong số những câu trả lời này ... (và có nhiều vấn đề khác nhau trên * nix và windows, đó là một yêu cầu tôi có) ... thấy những điều sau đây hoạt động tốt ...

git diff --no-ext-diff --quiet --exit-code

Để kiểm tra mã thoát trong * nix

echo $?   
#returns 1 if the repo has changes (0 if clean)

Để kiểm tra mã thoát trong cửa sổ $

echo %errorlevel% 
#returns 1 if the repos has changes (0 if clean) 

Được đăng tải từ https://github.com/sindresorhus/pure/issues/115 Cảm ơn @paulirish trên bài đăng đó để chia sẻ


4

Tại sao không gói gọn ' git statusvới một tập lệnh:

  • sẽ phân tích đầu ra của lệnh đó
  • sẽ trả về mã lỗi thích hợp dựa trên những gì bạn cần

Bằng cách đó, bạn có thể sử dụng trạng thái 'nâng cao' đó trong tập lệnh của mình.


Như 0xfe đề cập đến câu trả lời xuất sắc của mình , git status --porcelainlà công cụ trong mọi giải pháp dựa trên kịch bản

--porcelain

Cung cấp đầu ra ở định dạng ổn định, dễ phân tích cho các tập lệnh.
Hiện tại điều này giống hệt --short output, nhưng được đảm bảo không thay đổi trong tương lai, làm cho nó an toàn cho các tập lệnh.


Bởi vì tôi lười biếng, có lẽ. Tôi nghĩ rằng có một tích hợp cho việc này, vì nó có vẻ như là một trường hợp sử dụng khá gặp phải.
Robert Munteanu

Tôi đã đăng một giải pháp dựa trên đề xuất của bạn, mặc dù tôi không cực kỳ hài lòng với nó.
Robert Munteanu

4

Một khả năng DIY, được cập nhật để làm theo đề xuất của 0xfe

#!/bin/sh
exit $(git status --porcelain | wc -l) 

Theo ghi nhận của Chris Johnsen , điều này chỉ hoạt động trên Git 1.7.0 hoặc mới hơn.


6
Vấn đề với điều này là bạn không thể tin tưởng vào chuỗi 'thư mục làm việc sạch' trong các phiên bản trong tương lai. Cờ --porbide có nghĩa là để phân tích cú pháp, vì vậy một giải pháp tốt hơn sẽ là: thoát $ (trạng thái git --por chì | wc -l)
0xfe

@ 0xfe - Bạn có biết --porcelaincờ được thêm khi nào không? Không hoạt động với 1.6.4.2.
Robert Munteanu

@Robert: thử đi git status --short.
VonC

3
git status --porcelaingit status --shortcả hai được giới thiệu trong 1.7.0. --porcelainNó được giới thiệu đặc biệt để cho phép git status --shortthay đổi định dạng của nó trong tương lai. Vì vậy, git status --shortsẽ gặp phải vấn đề tương tự như git status(đầu ra có thể thay đổi bất cứ lúc nào vì đó không phải là lệnh 'hệ thống ống nước').
Chris Johnsen

@Chris, cảm ơn thông tin cơ bản. Tôi đã cập nhật câu trả lời để phản ánh cách tốt nhất để thực hiện việc này kể từ Git 1.7.0.
Robert Munteanu

2

Tôi cần khá thường xuyên một cách đơn giản để thất bại trong quá trình xây dựng nếu ở cuối thực thi có bất kỳ tệp được theo dõi sửa đổi hoặc bất kỳ tệp không bị theo dõi nào không bị bỏ qua.

Điều này khá quan trọng để tránh trường hợp các bản dựng sản xuất thức ăn thừa.

Cho đến nay, lệnh tốt nhất tôi đã sử dụng trông giống như:

 test -z "$(git status --porcelain | tee /dev/fd/2)" || \
     {{ echo "ERROR: git unclean at the end, failing build." && return 1 }}

Nó có thể trông hơi phức tạp và tôi sẽ đánh giá cao nếu bất cứ ai tìm thấy một biến thể rút gọn mà điều này duy trì hành vi mong muốn:

  • không có mã thoát đầu ra và succcess nếu có thứ tự
  • thoát mã 1 nếu thất bại
  • thông báo lỗi trên stderr giải thích lý do tại sao nó thất bại
  • hiển thị danh sách các tập tin gây ra lỗi, stderr một lần nữa.

2

Câu trả lời @ eduard-wirch đã khá đầy đủ, nhưng vì tôi muốn kiểm tra cả hai cùng một lúc, đây là biến thể cuối cùng của tôi.

        set -eu

        u="$(git ls-files --others)"
        if ! git diff-index --name-only --quiet HEAD -- || [ -z "${u:-}" ]; then
            dirty="-dirty"
        fi

Khi không thực thi bằng set -e hoặc tương đương, thay vào đó chúng ta có thể thực hiện u="$(git ls-files --others)" || exit 1(hoặc trả về nếu điều này hoạt động cho một hàm được sử dụng)

Vì vậy, unsracked_files, chỉ được đặt nếu lệnh thành công đúng.

sau đó, chúng ta có thể kiểm tra cả hai thuộc tính và đặt một biến (hoặc bất cứ thứ gì).


1

Đây là một biến thể thân thiện với vỏ hơn để tìm hiểu xem tệp nào chưa được mã hóa tồn tại trong kho không:

# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
  echo untracked files
fi

Điều này không rẽ nhánh một quá trình thứ hai grepvà không cần kiểm tra xem bạn có ở trong kho git hay không. Đó là tiện dụng cho lời nhắc shell, vv


1

Bạn cũng có thể làm

git describe --dirty

. Cuối cùng, nó sẽ nối từ "ba mươi" nếu phát hiện ra một cây làm việc bẩn. Theo git-describe(1):

   --dirty[=<mark>]
       Describe the working tree. It means describe HEAD and appends <mark> (-dirty by default) if
       the working tree is dirty.

. Hãy cẩn thận: các tập tin không bị theo dõi không được coi là "bẩn", bởi vì, như trang man nói, nó chỉ quan tâm đến cây làm việc.


0

Có thể có một sự kết hợp tốt hơn về câu trả lời từ chủ đề này .. nhưng công trình này cho tôi ... cho bạn .gitconfig's [alias]phần ...

          # git untracked && echo "There are untracked files!"
untracked = ! git status --porcelain 2>/dev/null | grep -q "^??"
          # git unclean && echo "There are uncommited changes!"
  unclean = ! ! git diff --quiet --ignore-submodules HEAD > /dev/null 2>&1
          # git dirty && echo "There are uncommitted changes OR untracked files!"
    dirty = ! git untracked || git unclean

0

Kiểm tra tự động đơn giản nhất tôi sử dụng để phát hiện trạng thái bẩn = mọi thay đổi bao gồm các tệp không bị theo dõi :

git add --all
git diff-index --exit-code HEAD

GHI CHÚ:

  • Nếu không có add --all diff-indexkhông nhận file được theo dõi.
  • Thông thường, tôi chạy git resetsau khi kiểm tra mã lỗi để hủy bỏ mọi thứ trở lại.

Câu hỏi cụ thể là "từ một kịch bản" ... không nên thay đổi chỉ mục để kiểm tra trạng thái bẩn.
ScottJ

@ScottJ, khi nó giải quyết được vấn đề, không phải ai cũng nhất thiết phải nghiêm khắc về việc liệu chỉ số có thể được sửa đổi hay không. Hãy xem xét trường hợp công việc tự động liên quan đến các nguồn tự động vá với số phiên bản và tạo thẻ - tất cả những gì bạn cần đảm bảo là hoàn toàn không có sửa đổi cục bộ nào khác cản trở (bất kể chúng có trong chỉ mục hay không). Cho đến nay, đây là thử nghiệm đáng tin cậy cho bất kỳ thay đổi nào, kể cả các tệp không được theo dõi .
uvsmtid

-3

Đây là cách tốt nhất, sạch nhất. Câu trả lời được chọn không hoạt động đối với tôi vì một số lý do, nó không nhận được các thay đổi được dàn dựng là các tệp mới không được cam kết.

function git_dirty {
    text=$(git status)
    changed_text="Changes to be committed"
    untracked_files="Untracked files"

    dirty=false

    if [[ ${text} = *"$changed_text"* ]];then
        dirty=true
    fi

    if [[ ${text} = *"$untracked_files"* ]];then
        dirty=true
    fi

    echo $dirty
}
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.