Làm cách nào để xác định theo chương trình nếu có những thay đổi không được cam kết?


226

Trong Makefile, tôi muốn thực hiện một số hành động nhất định nếu có những thay đổi không được cam kết (trong cây làm việc hoặc chỉ mục). Cách sạch nhất và hiệu quả nhất để làm điều đó là gì? Một lệnh thoát với giá trị trả về bằng 0 trong một trường hợp và khác không trong trường hợp khác sẽ phù hợp với mục đích của tôi.

Tôi có thể chạy git statusvà dẫn đầu ra thông qua grep, nhưng tôi cảm thấy như phải có một cách tốt hơn.


Câu trả lời:


289

CẬP NHẬT : OP Daniel Stutzbach chỉ ra trong các ý kiến rằng lệnh đơn giản này git diff-indexđã làm việc cho anh ta:

git update-index --refresh 
git diff-index --quiet HEAD --

( nornagon đề cập trong các ý kiến rằng, nếu có các tệp đã bị chạm, nhưng có nội dung giống như trong chỉ mục, bạn sẽ cần phải chạy git update-index --refreshtrước git diff-index, nếu không diff-indexsẽ báo cáo không chính xác rằng cây bị bẩn)

Sau đó, bạn có thể xem " Cách kiểm tra xem một lệnh có thành công không? " Nếu bạn đang sử dụng nó trong tập lệnh bash:

git diff-index --quiet HEAD -- || echo "untracked"; // do something about it

Lưu ý: như nhận xét của Anthony Sottile

git diff-index HEAD ...sẽ thất bại trên một nhánh không có cam kết (chẳng hạn như kho lưu trữ mới được khởi tạo).
Một cách giải quyết tôi đã tìm thấy làgit diff-index $(git write-tree) ...

haridsvchỉ ra trong các ý kiến rằng git diff-filestrên một tệp mới không phát hiện ra nó là một khác biệt.
Cách tiếp cận an toàn hơn dường như là chạy git addtrên spec tệp trước và sau đó sử dụng git diff-indexđể xem có gì được thêm vào chỉ mục trước khi chạy không git commit.

git add ${file_args} && \
git diff-index --cached --quiet HEAD || git commit -m '${commit_msg}'

6502 báo cáo trong các ý kiến:

Một vấn đề tôi gặp phải là nó git diff-indexsẽ cho biết rằng có những khác biệt khi thực sự không có gì ngoại trừ dấu thời gian của các tệp.
Chạy git diffmột lần giải quyết vấn đề (đủ ngạc nhiên, git diffthực sự thay đổi nội dung của hộp cát, có nghĩa là ở đây .git/index)

Các vấn đề dấu thời gian này cũng có thể xảy ra nếu git đang chạy trong docker .


Câu trả lời gốc:

"Lập trình" có nghĩa là không bao giờ dựa vào các lệnh sứ .
Luôn luôn dựa vào các lệnh hệ thống ống nước .

Xem thêm " Kiểm tra chỉ mục bẩn hoặc các tệp không bị theo dõi bằng Git " để biết các lựa chọn thay thế (như git status --porcelain)

Bạn có thể lấy cảm hứng từ " require_clean_work_treechức năng " mới được viết khi chúng ta nói ;) (đầu tháng 10 năm 2010)

require_clean_work_tree () {
    # Update the index
    git update-index -q --ignore-submodules --refresh
    err=0

    # Disallow unstaged changes in the working tree
    if ! git diff-files --quiet --ignore-submodules --
    then
        echo >&2 "cannot $1: you have unstaged changes."
        git diff-files --name-status -r --ignore-submodules -- >&2
        err=1
    fi

    # Disallow uncommitted changes in the index
    if ! git diff-index --cached --quiet HEAD --ignore-submodules --
    then
        echo >&2 "cannot $1: your index contains uncommitted changes."
        git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
        err=1
    fi

    if [ $err = 1 ]
    then
        echo >&2 "Please commit or stash them."
        exit 1
    fi
}

12
Nguyên tắc "ống nước so với sứ cho kịch bản" là một bài học mà Jakub Narębski liên tục đề cập với tôi: " Làm thế nào để liệt kê tất cả nhật ký cho dự án hiện tại trong git? ", " Git: changelog ngày qua ngày ", ...
VonC

18
Sau khi nhấp vào một số liên kết bạn đề xuất, tôi tìm thấy những gì tôi đang tìm kiếm : git diff-index --quiet HEAD.
Daniel Stutzbach

11
@DanielStutzbach: Điều đó có thể thất bại nếu bạn có một tệp được gọi HEADtrong thư mục làm việc. Sử dụng tốt hơn git diff-index --quiet HEAD --.
David Ongaro

7
Chưa hết, hướng dẫn sử dụng tại git status --helpcác tiểu bang: --porbide Cung cấp đầu ra ở định dạng dễ phân tích cú pháp cho các tập lệnh. Điều này tương tự với đầ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. Xem bên dưới để biết chi tiết.
Ed Randall

7
@VonC mà thực sự không có ý nghĩa. Bằng cách này bạn có thể xoắn mọi thứ vào đảo ngược của nó. --poriances mang đến cho bạn ấn tượng rằng nó sẽ sớm bị phá vỡ. Nếu không, nó nên được gọi là hệ thống ống nước, không phải bằng sứ. Việc sử dụng --porbide khiến tập lệnh của bạn không bị hỏng, điều này làm cho nó KHÔNG phải là tập lệnh sứ ;-). Nếu bạn muốn tập lệnh của mình bị hỏng, bạn không nên sử dụng --porbide !!. Vì vậy, nó hoàn toàn không thể hiểu được điều này và ném mọi người đi.
Xennex81

104

Mặc dù các giải pháp khác rất kỹ lưỡng, nhưng nếu bạn muốn một cái gì đó thực sự nhanh chóng và bẩn thỉu, hãy thử một cái gì đó như thế này:

[[ -z $(git status -s) ]]

Nó chỉ kiểm tra nếu có bất kỳ đầu ra trong bản tóm tắt trạng thái.


7
làm việc cho tôi sử dụng -n cho nghịch đảo (bạn có thay đổi), ví dụ `if [[-n $ (git status -s)]]; sau đó ... fi`
aaron

Điều này hoạt động, nhưng bạn có thể cho biết [[ ... ]]cú pháp thực sự đang làm gì? Tôi chưa bao giờ thấy bất cứ điều gì như vậy trước đây.
GMA

2
@EM mã trả về git statusthực sự bị bỏ qua trong thử nghiệm này. Nó chỉ nhìn vào đầu ra. Kiểm tra bash trang có liên quan này để biết thêm về [, [[và làm thế nào kiểm tra các công trình trong bash.
Nepthar

2
Đây là câu trả lời gần như đúng, nhưng đối với kịch bản, tốt hơn là sử dụng --porcelaintham số như được hiển thị ở đây
Mariusz Pawelski

2
Bạn có thể muốn sử dụng git status -s -uallđể bao gồm các tệp không bị theo dõi.
barfuin

59

git diff --exit-codesẽ trả lại giá trị khác nếu có bất kỳ thay đổi nào; git diff --quietlà như nhau không có đầu ra. Vì bạn muốn kiểm tra cây làm việc và chỉ mục, hãy sử dụng

git diff --quiet && git diff --cached --quiet

Hoặc là

git diff --quiet HEAD

Một trong hai sẽ cho bạn biết nếu có những thay đổi không được cam kết được dàn dựng hay không.


6
Những cái đó không tương đương. Lệnh duy nhất git diff --quite HEADsẽ chỉ cho bạn biết cây làm việc có sạch không, không biết chỉ mục có sạch không. Ví dụ: nếu fileđã được thay đổi giữa HEAD ~ và HEAD, thì sau đó git reset HEAD~ -- file, nó vẫn sẽ thoát 0 mặc dù có những thay đổi theo giai đoạn có trong chỉ mục (wt == HEAD, nhưng index! = HEAD).
Chris Johnsen

2
Cảnh báo, điều này sẽ không bắt các tệp bị xóa khỏi khu vực tổ chức với git rm, AFAICS.
nmr

24
Các tập tin mới (không bị theo dõi) không được phát hiện bởi git diff --quiet && git diff --cached --quiet.
4LegsDrivenCat

17

Mở rộng câu trả lời của @ Nepthar:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi

1
Điều này là tốt; Tôi sử dụng nó để tự động truy cập các tệp đơn lẻ bằng cách thử nghiệm $(git status -s "$file")và sau đó trong elsemệnh đềgit add "$file"; git commit -m "your autocommit process" "$file"
babkaufmann

Nếu bạn thực hiện điều đó git status -smột git status --porcelain ; git clean -ndthay vào đó, thư mục rác sẽ được nổi lên ở đây cũng vậy, đó là vô hình git status.
ecmanaut

4

Như đã chỉ ra trong câu trả lời khác, lệnh đơn giản như vậy là đủ:

git diff-index --quiet HEAD --

Nếu bạn bỏ qua hai dấu gạch ngang cuối cùng, lệnh sẽ thất bại nếu bạn có một tệp có tên HEAD.

Thí dụ:

#!/bin/bash
set -e
echo -n "Checking if there are uncommited changes... "
trap 'echo -e "\033[0;31mFAILED\033[0m"' ERR
git diff-index --quiet HEAD --
trap - ERR
echo -e "\033[0;32mAll set!\033[0m"

# continue as planned...

Lời cảnh báo: lệnh này bỏ qua các tệp không được theo dõi.


2
Như đã nêu trong các bình luận cho câu trả lời đó, điều này không phát hiện các tệp mới được thêm vào
minexew

Không, nó phát hiện mới được thêm vào tập tin chỉ mục. Mới kiểm tra.
sanmai

Xem câu hỏi. Các tập tin không bị theo dõi không thay đổi . git addgit cleanđể giải cứu
sanmai

4

Tôi đã tạo một số bí danh git tiện dụng để liệt kê các tệp chưa được phân loại và theo giai đoạn:

git config --global alias.unstaged 'diff --name-only'
git config --global alias.staged 'diff --name-only --cached'

Sau đó, bạn có thể dễ dàng làm những việc như:

[[ -n "$(git unstaged)" ]] && echo unstaged files || echo NO unstaged files
[[ -n "$(git staged)" ]] && echo staged files || echo NO staged files

Bạn có thể làm cho nó dễ đọc hơn bằng cách tạo một tập lệnh ở đâu đó trên PATHtên bạn git-has:

#!/bin/bash
[[ $(git "$@" | wc -c) -ne 0 ]]

Bây giờ các ví dụ trên có thể được đơn giản hóa để:

git has unstaged && echo unstaged files || echo NO unstaged files
git has staged && echo staged files || echo NO staged files

Để hoàn thiện ở đây là các bí danh tương tự cho các tệp không bị theo dõi và bỏ qua:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'

2

Với python và gói GitPython:

import git
git.Repo(path).is_dirty(untracked_files=True)

Trả về Đúng nếu kho lưu trữ không sạch


Điều này tránh được một số vấn đề "dấu thời gian" được đề cập trong các bình luận khác
Jason

1
Lưu ý rằng GitPython cũng chỉ sử dụng git CLI. Nếu bạn đặt, LOGLEVEL=DEBUGbạn sẽ thấy tất cả các lệnh Popen mà nó sử dụng để chạygit diff
Jason

-3

Đây là cách tốt nhất, sạch nhấ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
}

4
Không, đây không phải là tốt nhất. git statuslà một lệnh 'sứ'. Không sử dụng lệnh sứ trong tập lệnh vì chúng có thể thay đổi giữa các phiên bản git. Thay vào đó sử dụng các lệnh 'ống nước'.
kẻ lừa đảo

3
Tôi nghĩ rằng nếu bạn cập nhật nó để sử dụng git status --porcelain(có nghĩa là cho mục đích này - một định dạng ổn định bạn có thể phân tích cú pháp trong một tập lệnh), cũng có thể với -z (tách biệt thay vì dòng mới?) Bạn có thể làm gì đó hữu ích với ý tưởng này . @ codyc4321 xem stackoverflow.com/questions/6976473/ Khăn để biết chi tiết
msouth
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.