Làm thế nào để sử dụng git bisect?


438

Tôi đã đọc một số bài báo nói rằng đó git bisectlà tuyệt vời. Tuy nhiên, tôi không phải là người bản ngữ và tôi không thể hiểu tại sao nó tuyệt vời.

Ai đó có thể vui lòng chứng minh với một số mẫu mã:

  1. Làm thế nào để sử dụng nó?
  2. Nó chỉ là như thế svn blame?

@ 01: Như cuốn sách git nói: thực hiện tìm kiếm vũ phu trong suốt lịch sử của dự án .
eckes

7
Không phải vậy /// brute :-), nó sử dụng tìm kiếm nhị phân.
cojocar

"Git đổ lỗi" tương tự như "svn đổ lỗi". "Git bisect" là một điều hoàn toàn khác
William Pursell

Đối với những gì nó có giá trị, cũng có một mô tả tốt về bisect trong Pro Git . Câu trả lời của Sylvain là một cú đánh tốt vào nó. Nếu sau khi xem tất cả những thứ mà bạn vẫn không hiểu, tôi khuyên bạn nên hỏi một câu hỏi cụ thể hơn. Câu hỏi chung hãy quên câu trả lời chung.
Cascabel

Câu trả lời:


655

Ý tưởng đằng sau git bisectlà thực hiện tìm kiếm nhị phân trong lịch sử để tìm ra hồi quy cụ thể. Hãy tưởng tượng rằng bạn có lịch sử phát triển sau:

... --- 0 --- 1 --- 2 --- 3 --- 4* --- 5 --- current

Bạn biết rằng chương trình của bạn không hoạt động đúng trong currentlần sửa đổi và chương trình đó đang hoạt động ở lần sửa đổi 0. Vì vậy, các hồi quy được khả năng giới thiệu trong một trong những cam kết 1, 2, 3, 4, 5, current.

Bạn có thể thử kiểm tra từng cam kết, xây dựng nó, kiểm tra xem có hồi quy hay không. Nếu có một số lượng lớn các cam kết, việc này có thể mất nhiều thời gian. Đây là một tìm kiếm tuyến tính. Chúng ta có thể làm tốt hơn bằng cách thực hiện tìm kiếm nhị phân. Đây là những gì git bisectlệnh làm. Ở mỗi bước, nó cố gắng giảm một nửa số lần sửa đổi có khả năng xấu đi một nửa.

Bạn sẽ sử dụng lệnh như thế này:

$ git stash save
$ git bisect start
$ git bisect bad
$ git bisect good 0
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[< ... sha ... >] 3

Sau lệnh này, gitsẽ kiểm tra một cam kết. Trong trường hợp của chúng tôi, nó sẽ được cam kết 3. Bạn cần xây dựng chương trình của mình và kiểm tra xem có hồi quy hay không. Bạn cũng cần cho biết gittrạng thái của bản sửa đổi này git bisect badnếu có hồi quy hoặc nếu không có hồi quy git bisect good.

Hãy giả sử rằng hồi quy đã được giới thiệu trong cam kết 4. Sau đó, hồi quy không có trong bản sửa đổi này, và chúng tôi nói với nó git.

$ make
$ make test
... ... ...
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[< ... sha ... >] 5

Sau đó nó sẽ kiểm tra một cam kết khác. Hoặc là 4hay 5(như chỉ có hai cam kết). Hãy giả sử nó được chọn 5. Sau khi xây dựng, chúng tôi kiểm tra chương trình và thấy rằng hồi quy có mặt. Sau đó chúng tôi nói với nó để git:

$ make
$ make test
... ... ...
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[< ... sha ... >] 4

Chúng tôi kiểm tra phiên bản cuối cùng 4,. Và vì nó là phần giới thiệu hồi quy, chúng tôi bảo nó git:

$ make
$ make test
... ... ...
$ git bisect bad
< ... sha ... > is the first bad commit
< ... commit message ... >

Trong tình huống đơn giản này, chúng tôi chỉ phải kiểm tra 3 phiên bản ( 3, 4, 5) thay vì 4 ( 1, 2, 3, 4). Đây là một chiến thắng nhỏ, nhưng điều này là do lịch sử của chúng ta quá nhỏ bé. Nếu phạm vi tìm kiếm là N cam kết, chúng ta nên thử nghiệm các cam kết 1 + log2 N git bisectthay vì khoảng N / 2 cam kết với tìm kiếm tuyến tính.

Khi bạn đã tìm thấy cam kết đưa ra hồi quy, bạn có thể nghiên cứu nó để tìm ra vấn đề. Khi điều này được thực hiện, bạn sử dụng git bisect resetđể đưa mọi thứ trở lại trạng thái ban đầu trước khi sử dụng git bisectlệnh.


5
Tôi sẽ giải thích ở đây, đây là một lời giải thích tốt về bisect nhưng thực sự không giúp tôi sử dụng nó. Đặc biệt là vì tôi đã tìm được một cam kết tốt và bây giờ tôi đang ở chi nhánh đó. Từ vị trí này, lời giải thích này không giúp được gì cả. Làm cách nào để chỉ định nhánh xấu mà không kiểm tra nó, ví dụ
PandaWood

4
Bạn có thể sử dụng git bisect bad <rev> [<rev>...]để đánh dấu các phiên bản cụ thể là xấu (hoặc tốt với git bisect good <rev> [<rev>...]). revcó thể là bất kỳ định danh sửa đổi nào như tên chi nhánh, thẻ, hàm băm cam kết (hoặc tiền tố duy nhất của hàm băm xác thực), ...
Sylvain Defresne

39
... và một khi bạn đã hoàn tất, bạn gõ git bisect resetđể đưa mọi thứ trở lại vào cam kết gần đây
peetonn

17
Một trong những câu trả lời tuyệt vời nhất trên stack. Khớp nối rất tốt. Tôi đã thực hiện quy trình này một cách thủ công trong nhiều năm, chỉ cần chọn một điểm giữa chừng tùy ý giữa cam kết tốt và xấu, sau đó lại giữa điều đó và điều tốt / xấu tùy thuộc vào việc nó tốt / xấu. Nó luôn luôn là một nỗi đau rất lớn ở mông và tôi thậm chí chưa bao giờ nghe nói về tiểu ban git này cho đến ngày hôm nay ... hahaha
Chev

3
@Nemoden, vâng, về cơ bản, nó có thể hữu ích cho bất kỳ loại dự án nào. Bạn chỉ cần thay thế bước "thực hiện kiểm tra" bằng "triển khai trang web và tái tạo vấn đề"
alex.b

158

git bisect run tự động chia đôi

Nếu bạn có một ./testtập lệnh tự động có trạng thái thoát 0 nếu kiểm tra là OK, bạn có thể tự động tìm lỗi với bisect run:

git checkout KNOWN_BAD_COMMIT
git bisect start

# Confirm that our test script is correct, and fails on the bad commit.
./test
# Should output != 0.
echo $?
# Tell Git that the current commit is bad.
git bisect bad

# Same for a known good commit in the past.
git checkout KNOWN_GOOD_COMMIT
./test
# Should output 0.
echo $?
# After this, git automatically checks out to the commit
# in the middle of KNOWN_BAD_COMMIT and KNOWN_GOOD_COMMIT.
git bisect good

# Bisect automatically all the way to the first bad or last good rev.
git bisect run ./test

# End the bisect operation and checkout to master again.
git bisect reset

Tất nhiên, điều này giả sử rằng nếu tập lệnh kiểm tra ./testđược theo dõi git, thì nó không biến mất trên một số cam kết trước đó trong quá trình chia đôi.

Tôi đã thấy rằng rất thường xuyên bạn có thể thoát khỏi bằng cách chỉ sao chép tập lệnh trong cây ra khỏi cây, và có thể chơi với PATHcác biến giống như, và chạy nó từ đó thay vào đó.

Tất nhiên, nếu cơ sở hạ tầng kiểm tra testphụ thuộc vào phá vỡ các cam kết cũ hơn, thì không có giải pháp nào và bạn sẽ phải thực hiện mọi việc theo cách thủ công, quyết định cách kiểm tra từng lần một.

Tuy nhiên, tôi đã phát hiện ra rằng việc sử dụng tự động hóa này thường hoạt động và có thể tiết kiệm thời gian rất lớn cho các bài kiểm tra chậm hơn trong các công việc tồn đọng của bạn, nơi bạn có thể để nó chạy qua đêm và có thể nhận ra lỗi của bạn vào sáng hôm sau. sự cố gắng.

Thêm lời khuyên

Tiếp tục cam kết thất bại đầu tiên sau khi chia đôi thay vì quay lại master:

git bisect reset HEAD

start+ ban đầu badgoodtrong một lần:

git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~

giống như:

git checkout KNOWN_BAD_COMMIT
git bisect start
git bisect bad
git bisect good KNOWN_GOOD_COMMIT

Xem những gì đã được thử nghiệm cho đến nay (bằng tay goodbadhoặc run):

git bisect log

Đầu ra mẫu:

git bisect log
git bisect start
# bad: [00b9fcdbe7e7d2579f212b51342f4d605e53253d] 9
git bisect bad 00b9fcdbe7e7d2579f212b51342f4d605e53253d
# good: [db7ec3d602db2d994fe981c0da55b7b85ca62566] 0
git bisect good db7ec3d602db2d994fe981c0da55b7b85ca62566
# good: [2461cd8ce8d3d1367ddb036c8f715c7b896397a5] 4
git bisect good 2461cd8ce8d3d1367ddb036c8f715c7b896397a5
# good: [8fbab5a3b44fd469a2da3830dac5c4c1358a87a0] 6
git bisect good 8fbab5a3b44fd469a2da3830dac5c4c1358a87a0
# bad: [dd2c05e71c246f9bcbd2fbe81deabf826c54be23] 8
git bisect bad dd2c05e71c246f9bcbd2fbe81deabf826c54be23
# bad: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05] 7
git bisect bad c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05
# first bad commit: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c0

Hiển thị các giới thiệu tốt và xấu trên nhật ký git để có được khái niệm tốt hơn về thời gian:

git log --decorate --pretty=fuller --simplify-by-decoration master

Điều này chỉ hiển thị các cam kết với một ref tương ứng, giúp giảm nhiễu thực sự, nhưng không bao gồm các ref được tạo tự động của loại:

refs/bisect/good*
refs/bisect/bad*

cho chúng tôi biết những cam kết nào chúng tôi đánh dấu là tốt hay xấu.

Xem xét thử nghiệm repo này nếu bạn muốn chơi xung quanh với lệnh.

Thất bại là nhanh, thành công là chậm

Đôi khi:

  • thất bại xảy ra nhanh chóng, ví dụ như một trong những thử nghiệm đầu tiên bị phá vỡ
  • thành công mất một thời gian, ví dụ như bài kiểm tra bị hỏng và tất cả các bài kiểm tra khác mà chúng tôi không quan tâm theo dõi

Đối với những trường hợp đó, ví dụ: giả sử thất bại luôn xảy ra trong 5 giây và nếu chúng ta lười biếng làm cho bài kiểm tra cụ thể hơn như chúng ta thực sự nên làm, chúng ta có thể sử dụng timeoutnhư trong:

#!/usr/bin/env bash
timeout 5 test-command
if [ $? -eq 1 ]; then
  exit 1
fi

Điều này hoạt động kể từ khi timeoutthoát 124trong khi sự thất bại của test-commandlối thoát 1.

Trạng thái thoát ma thuật

git bisect run là một chút kén chọn về trạng thái thoát:

  • bất cứ điều gì trên 127 làm cho việc chia đôi thất bại với một cái gì đó như:

    git bisect run failed:
    exit code 134 from '../test -aa' is < 0 or >= 128
    

    Cụ thể, một chữ C assert(0)dẫn đến a SIGABRTvà thoát với trạng thái 134, rất khó chịu.

  • 125 là ma thuật và làm cho việc chạy được bỏ qua git bisect skip.

    Mục đích của việc này là giúp bỏ qua các bản dựng bị hỏng do các lý do không liên quan.

Xem man git-bisectđể biết chi tiết.

Vì vậy, bạn có thể muốn sử dụng một cái gì đó như:

#!/usr/bin/env bash
set -eu
./build
status=0
./actual-test-command || status=$?
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
  status=1
fi
exit "$status"

Đã thử nghiệm trên git 2.16.1.


7
Làm thế nào để git biết để giữ cho bài kiểm tra mới của bạn không biến mất khi hoàn nguyên / chia đôi trở lại bản sửa đổi trước đó / xấu (điều đó không có bài kiểm tra mới được viết của bạn)?
thebjorn

8
@thebjorn bạn có một điểm: theo như tôi biết, bài kiểm tra phải nằm trên một tệp thực thi bên ngoài trong PATH hoặc trong một tệp không bị theo dõi trong repo. Trong nhiều trường hợp, điều này là có thể: đặt thử nghiệm vào một tệp riêng biệt, bao gồm bản thử nghiệm cần thiết với test_scriptbộ kiểm tra mô đun + được chế tạo tốt và chạy nó từ tệp riêng trong khi chia đôi. Khi bạn sửa chữa, hợp nhất bài kiểm tra vào bộ kiểm tra chính.
Ciro Santilli 郝海东 冠状 病 事件

1
@CiroSantilli 事件 法轮功 纳米比亚 威 视để thêm một điểm)
Nicks

1
Có rất nhiều cách để đi sai với 'git bisect run' - ví dụ như xem cách cam kết tốt đã bị đảo ngược bởi một sự hợp nhất xấu. Nó đến, ra, vào lại một lần nữa và chỉ có "ra" cuối cùng là xấu. Tuy nhiên, bạn luôn có thể thực hiện thủ công 'git bisect'. Bởi vì đó là một phần hai, nó chỉ mất một vài bước - ví dụ 1024 cam kết trong 10 bước.
tổ hợp

1
@combinatorist bạn nói đúng rằng nó có thể thất bại. Tôi thấy bisect runđặc biệt hữu ích khi bài kiểm tra mất nhiều thời gian để hoàn thành và tôi khá chắc chắn rằng hệ thống kiểm tra sẽ không bị hỏng. Bằng cách này, tôi chỉ có thể để nó chạy trên nền hoặc qua đêm nếu nó chiếm quá nhiều tài nguyên, mà không mất bất kỳ thời gian chuyển đổi bối cảnh não nào.
Ciro Santilli 郝海东 冠状 病 事件

124

TL; DR

Khởi đầu:

$ git bisect start
$ git bisect bad
$ git bisect good <goodcommit>

Bisecting: X revisions left to test after this (roughly Y steps)

Nói lại:

Vấn đề còn tồn tại?

  • Đúng: $ git bisect bad
  • Không: $ git bisect good

Kết quả:

<abcdef> is the first bad commit

Khi hoàn thành:

git bisect reset

3
Hãy chắc chắn rằng bạn đang ở gốc của repo git của bạn, hoặc bạn sẽ nhận được một điều kỳ lạ "Bạn cần chạy lệnh này từ toplevel của cây làm việc." lỗi.
PR Whitehead

Vì vậy, tôi đã git xấu trên ĐẦU của tôi và git tốt trong lần xác nhận đầu tiên, khi lỗi không xuất hiện. Vậy phải làm gì tiếp theo? Khi lỗi không có mặt git bisect tốt để chuyển sang cam kết tiếp theo?
Yêu tinh

@Gobliins Khi không có lỗi, hãy sửa, git bisect goodđể chuyển sang cam kết tiếp theo.
Geoffrey Hale

40

Chỉ cần thêm một điểm:

Chúng tôi có thể chỉ định tên tệp hoặc đường dẫn đến git bisect starttrong trường hợp chúng tôi biết rằng lỗi đã đến từ các tệp cụ thể. Ví dụ: Giả sử chúng tôi biết rằng những thay đổi gây ra hồi quy là trong thư mục com / workDir thì chúng tôi có thể chạy git bisect start com/workingDirĐiều này có nghĩa là chỉ những cam kết thay đổi nội dung của thư mục này sẽ được kiểm tra và điều này khiến mọi thứ nhanh hơn.

Ngoài ra, nếu khó biết liệu một cam kết cụ thể là tốt hay xấu, bạn có thể chạy git bisect skip, điều này sẽ bỏ qua nó. Do có đủ các cam kết khác, thay vào đó, git bisect sẽ sử dụng một cam kết khác để thu hẹp tìm kiếm.


15

$ git bisect ..về cơ bản là một công cụ Git để gỡ lỗi . 'Git Bisect' gỡ lỗi bằng cách thực hiện các cam kết trước đó kể từ lần cam kết làm việc cuối cùng (đã biết) của bạn. Nó sử dụng tìm kiếm nhị phân để đi qua tất cả các cam kết đó, để đi đến cái đã giới thiệu hồi quy / lỗi.

$ git bisect start # Bắt đầu chia đôi

$ git bisect bad # nói rằng cam kết hiện tại (v1.5) có điểm hồi quy / cài đặt 'xấu'

$ git bisect good v1.0 # đề cập đến nó là cam kết làm việc tốt cuối cùng (không có hồi quy)

Việc đề cập đến các điểm 'xấu' và 'tốt' sẽ giúp git bisect (tìm kiếm nhị phân) chọn phần tử ở giữa (cam kết v1.3). Nếu hồi quy có ở cam kết v1.3, bạn sẽ đặt điểm đó là điểm 'xấu' mới tức là ( Tốt -> v1.0 và Xấu -> v1.3 )

$ git bisect bad

hoặc tương tự nếu cam kết v1.3 không có lỗi, bạn sẽ đặt nó là 'Điểm tốt' tức là (* Tốt -> v1.3 và Xấu -> v1.6).

$ git bisect good

2

Lưu ý: các điều khoản goodbadkhông phải là điều khoản duy nhất bạn có thể sử dụng để đánh dấu một cam kết có hoặc không có một tài sản nhất định.

Git 2.7 (Q4 2015) giới thiệu các git bisecttùy chọn mới .

 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
                  [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]

Với tài liệu thêm:

Đôi khi bạn không tìm kiếm cam kết đã đưa ra một sự phá vỡ, mà là một cam kết gây ra sự thay đổi giữa một số trạng thái "cũ" và "mới" khác .

Ví dụ: bạn có thể đang tìm kiếm cam kết giới thiệu một bản sửa lỗi cụ thể.
Hoặc bạn có thể đang tìm kiếm cam kết đầu tiên trong đó tên tệp mã nguồn cuối cùng đã được chuyển đổi thành tiêu chuẩn đặt tên của công ty bạn. Hay bất cứ cái gì.

Trong những trường hợp như vậy, có thể rất khó hiểu khi sử dụng cụm từ "tốt" và "xấu" để chỉ "trạng thái trước khi thay đổi" và "trạng thái sau khi thay đổi".

Vì vậy, thay vào đó, bạn có thể sử dụng các thuật ngữ " old" và " new" tương ứng thay cho " good" và " bad".
(Nhưng lưu ý rằng bạn không thể trộn " good" và " bad" với " old" và " new" trong một phiên duy nhất.)

Trong cách sử dụng chung hơn này, bạn cung cấp git bisectmột newcam kết "" có một số thuộc tính và một oldcam kết "" không có thuộc tính đó.

Mỗi lần git bisectkiểm tra một cam kết, bạn kiểm tra xem cam kết đó có thuộc tính hay không:
Nếu có, hãy đánh dấu cam kết là " new"; mặt khác, đánh dấu nó là " old".

Khi chia đôi được thực hiện, git bisectsẽ báo cáo cam kết giới thiệu tài sản.


Xem cam kết 06e6a74 , cam kết 21b55e3 , cam kết676787 (29 tháng 6 năm 2015) của Matthieu Moy ( moy) .
Xem cam kết 21e5cfd (29 tháng 6 năm 2015) bởi Antoine Delaite ( CanardChouChinois) .
(Được hợp nhất bởi Junio ​​C Hamano - gitster- trong cam kết 22dd6eb , ngày 05 tháng 10 năm 2015)

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.