はじめに
Little Pine Goods は、小さな贈り物の受け取り用見積書を作成しています。顧客から、FRIEND10 の特典で約束された割引が適用されなくなったと報告がありました。最近の履歴には、有用な商品、文言、受け取り情報の更新もあるため、店舗全体を古いバージョンに戻すと、正しい作業まで失われます。
通常の注文と、不具合があるギフト注文を実行します。用意された業務テストは、同じ入力とバージョンに対して常に同じ結果を返します。このテストで古いバージョンを分類します。少し手動で Git の二分探索を練習してから、同じテストで探索を自動化します。最初に不具合が生じたスナップショットとその直接の親を比較し、探索を終了してから、後続の更新を保持して現在の店舗を修復します。修正コミットに加え、完全な顧客用見積書ファイルも届けます。
前提は、ターミナルでの基本的な移動、コミット、ブランチ、簡単な log/diff の読み取りです。Git bisect、一時的な detached 状態でのチェックアウト、テストの終了ステータス、小さな特典ファイルの編集は、この実験で説明します。Python と見積もりプログラムは準備済みで、Python を書かずに実行できます。これは1台の学習用マシン上にある、実際のローカル Git 履歴とファイルを使う見積もりツールです。注文は架空で、価格は EUR です。Web サイト、ホスティングされたアカウント、ネットワークへのデプロイは不要です。標準の Terminal を使います。所要時間の50分は、著者による見積もりです。
最初に不具合が生じた見積もりを見つける
このステップでは、正しい業務上の結果を確認し、実際の過去のスナップショットをテストして、割引が誤るようになった境界を特定します。
Terminal で始め、店舗のリポジトリへ移動します。
cd /home/labex/project/quote-desk/shop
cd は、このターミナルのディレクトリを変更します。パスを打ち間違えた場合は、pwd で現在地を表示し、ls でそのディレクトリを一覧表示してから、上の完全な cd を実行し直してください。通常の移動ミスに VM のリセットは必要ありません。
顧客の依頼を読みます。cat はテキストファイルを表示し、.. は親ディレクトリを意味します。
cat ../request.txt
FRIEND10 は、商品小計から10%の割引を約束しています。送料や税はありません。ギフト注文は、1個 EUR 16.00 のキャンプ用マグ3個です。小計 EUR 48.00、割引 EUR 4.80、合計 EUR 43.20 になります。既存の商品一覧、注文、方針が業務上の事実です。誤った結果に合わせて変更しないでください。
まず通常の注文を実行します。python3 は、用意されたプログラム quote.py を実行します。その次の引数は、準備済みの注文ファイルです。Python のソースを編集したり、理解したりする必要はありません。
python3 quote.py ../orders/plain.order
マグ1個の注文は、クーポン NONE を使います。小計と合計は EUR 16.00、割引は EUR 0.00 で、現在の案内文と、土曜日16:00までの受け取り情報が含まれるはずです。これで実用的な通常の見積もりを確認できます。
報告された注文を実行します。
python3 quote.py ../orders/gift.order
直前のコマンドのネイティブな終了ステータスを読むには、シェルの値 $? を使います。echo はその値を表示します。見積もりコマンドの直後に実行してください。
echo $?
0 が表示されるはずです。金額の結果は誤っていても、プログラム自体は正常に実行されています。
プログラムは動きますが、見積もりは誤っています。割引は EUR 0.48、合計は EUR 47.52 です。プログラムの正常終了と、業務上の正しい答えは異なります。
用意されたテストはリポジトリの外側にあるため、古いバージョンをチェックアウトしても置き換わりません。実際の見積もりプログラムを複数の数量とクーポンなしの対照例で実行し、変わらない特典方針と結果全体を比較します。実行してください。
python3 /opt/labex/quote-desk/check.py
同じシェルの値を使い、このテストのステータスを直後に読みます。
echo $?
1 が表示されるはずです。このテストは業務上の不一致を検出します。
FRIEND10 の FAIL 行と、ネイティブな終了ステータス1を想定しています。 NONE の対照例は合格します。テストは、正しい見積もりなら0、この業務上の失敗なら1、バージョンをテストできなければ128を返します。テスト不能なら探索を止めるべきであり、割引の不具合だと推測してはいけません。この準備済みの履歴には、ビルド失敗やスキップするバージョンはありません。
現在のブランチとクリーンな状態を確認します。branch --show-current はブランチを表示し、--short は簡潔なファイル一覧を表示します。
git branch --show-current
git status --short
main が表示され、変更ファイルの出力はないはずです。探索中は店舗をクリーンに保ちます。履歴を読みます。--no-pager はスクロールするビューアーを開かずに直接出力し、--oneline は項目を短くし、--decorate はブランチとタグにラベルを付けます。
git --no-pager log --oneline --decorate
固定のタグ incident-tip は引き継いだ現在の先端、known-good は以前の正常なバージョンを示します。コミットの件名は作業を説明しますが、その文言は見積もりがいつ誤ったかの証明にはなりません。両方のタグと、引き継いだすべてのコミットを保持してください。
テストの --revision オプションは、選択したコミット済みスナップショットを一時的な隔離ディレクトリで実行し、チェックアウトを変えたり修復したりはしません。実際の動作で両端を確認します。
python3 /opt/labex/quote-desk/check.py --revision known-good
python3 /opt/labex/quote-desk/check.py --revision incident-tip
最初のコマンドはステータス0で合格する必要があります。2つ目はステータス1で失敗する必要があります。 正常なバージョンと、その後の不良なバージョンが探索範囲を区切ります。この演習では、その区間に不具合の導入が1回あり、その後の修正はありません。
2つのバージョンを手動で分類する
Git の bisect は、境界の間にあるバージョンをテストして、その区間を絞り込みます。既知の不良リビジョンを先に、既知の正常リビジョンを次に指定して開始します。
git bisect start incident-tip known-good
Git は中間のスナップショットをチェックアウトし、残りの区間を表示します。探索中の detached HEAD は、main を動かさずにコミットを直接見ていることを意味します。一時的なこれらのバージョンでは、編集やコミットをしないでください。
チェックアウトされたスナップショットで同じテストを実行します。
python3 /opt/labex/quote-desk/check.py
この準備済みの最初の選択は、ステータス1で失敗します。 観察したバージョンを bad と分類します。bad は業務テストの結果を表し、コミットメッセージのことではありません。
git bisect bad
Git は別のスナップショットを選びます。推測せずにテストしてください。
python3 /opt/labex/quote-desk/check.py
この選択はステータス0で合格します。その観察結果を記録します。
git bisect good
Git は再び区間を絞ります。それぞれの結果によって、探索範囲の一部が除かれる様子を確認できました。同じ元の境界から自動化する方法を練習するため、まずこの未完了の探索を終了します。bisect reset は探索状態を消去し、開始時のブランチとリビジョンに戻ります。コミットを取り消すわけではありません。
git bisect reset
git branch --show-current
main が表示されるはずです。練習中にバージョンの分類を間違えた場合も、同じ reset と新たな開始によって、ファイルを破棄せずに、実際のテスト結果を使ってやり直せます。
テストで探索を進める
もう一度開始し、bisect run に同じ完全なテストコマンドを渡します。Git は各ネイティブ終了ステータスを使い、自動的に good か bad を選びます。
git bisect start incident-tip known-good
git bisect run python3 /opt/labex/quote-desk/check.py
複数回のテスト実行と、is the first bad commit で終わる行があるはずです。個々の見積もりテストに失敗したものがあっても、境界を正しく見つけた探索コマンド自体はステータス0で終了します。コミット ID は異なる場合があります。
Git の bisect/bad 参照が、特定された最初の不良コミットを指します。現在の HEAD は、最後にテストした正常なバージョンのままの場合があります。報告された結果だと思い込まないでください。実際の候補のパッチを読みます。show はそのコミットとパッチを選び、-- はファイルパスを区切ります。
git --no-pager show bisect/bad -- discounts.txt
FRIEND10 の割合が10から1に変わっています。外部テストで両側を確認します。末尾の ^ は、コミットの直接の第1親を1つ選びます。したがって、bisect/bad^ は候補の直前のバージョンであり、範囲全体ではありません。
python3 /opt/labex/quote-desk/check.py --revision bisect/bad
python3 /opt/labex/quote-desk/check.py --revision bisect/bad^
候補はステータス1で失敗し、その親はステータス0で合格するはずです。 値と実際の動作が一致しています。このチェックポイントでは、完了した探索を開いたままにしてください。次に、編集する前に現在の店舗へ戻ります。
現在の店舗を修復して見積書を届ける
このステップでは、過去の探索を終了し、現在のブランチに目的を絞った修正をコミットして、後から加わった商品や受け取り情報を失わずに、完全な見積書を届けます。
引き続き Terminal の /home/labex/project/quote-desk/shop で作業します。学んだ探索の reset を使い、元のブランチに戻ります。
git bisect reset
git branch --show-current
git --no-pager log -1 --oneline --decorate
git status --short
main、incident-tip が示す引き継いだ先端が表示され、変更ファイルの出力はないはずです。log の -1 オプションは最新のコミットだけに出力を制限します。切り離された過去のバージョンを修復したり、店舗全体を known-good に置き換えたりしないでください。後続の正しい作業も結果に含める必要があります。
小さな特典ファイルと、保持すべき現在の詳細を読みます。
cat discounts.txt
cat welcome.txt pickup.txt care.txt
cat catalog.json
現在のツリーには、Trail pin、新しい案内文、土曜日16:00までの受け取り情報が含まれています。誤っているのは FRIEND10 の割合だけです。特典ファイルは、1行につき CODE=integer-percent の設定を1つ記述します。TRAIL15 は15、STUDENT5 は5のままにします。
この小さなファイルだけを置き換えます。printf は書式に従って値を出力します。%s はテキストを表し、\n は渡した各値の後に改行を加えます。単一引用符はリテラルのテキストを保ちます。> は指定ファイルに出力を書き込み、そのファイルだけを置き換えます。
printf '%s\n' 'FRIEND10=10' 'TRAIL15=15' 'STUDENT5=5' > discounts.txt
実際の修正を確認します。通常の diff は、作業ファイルと、Git のステージングエリアであるインデックスを比較します。
git --no-pager diff -- discounts.txt
FRIEND10 が1から10に戻る変更だけがあるはずです。修復を記録する前に、このリポジトリ内だけで使う作成者情報を設定します。--local はグローバル設定を避け、引用符は空白を含む名前を1つにまとめます。架空の例は置き換えても構いません。
git config --local user.name "Little Pine Maintainer"
git config --local user.email "maintainer@little-pine.example"
add でそのファイルをステージします。--cached でステージされたパッチを確認します。この指定はインデックスを現在のコミットと比較します。
git add discounts.txt
git --no-pager diff --cached -- discounts.txt
目的を絞った修正をコミットします。-m は意味のあるメッセージを直接指定します。
git commit -m "Restore the promised FRIEND10 discount"
引き継いだ固定の先端と比較します。履歴を書き換えず、元のすべてのコミットを保持する、新しい修正コミット1つです。
git --no-pager diff incident-tip HEAD
git --no-pager log -2 --oneline --decorate
git status --short
割合の修正だけがあり、incident-tip の直後に修復があり、追跡済みの状態がクリーンであるはずです。メッセージや作成者情報の具体的な値は成功条件ではありません。
実際の現在のプログラムに対して、用意されたテストを実行します。
python3 /opt/labex/quote-desk/check.py
すべてのケースがステータス0で合格します。顧客の見積書を生成します。
python3 quote.py ../orders/gift.order
現在のマグの説明と案内文、数量3、FRIEND10、小計 EUR 48.00、割引 EUR 4.80、合計 EUR 43.20、現在の土曜日16:00までの受け取り情報が含まれるはずです。
その実際の出力を、用意された quotes ディレクトリに届けます。学んだとおり、> は標準出力を指定したファイルに保存します。注文やプログラムを変えることはありません。
python3 quote.py ../orders/gift.order > ../quotes/mug-gift.txt
cat ../quotes/mug-gift.txt
ファイル名だけでなく、保存された見積書全体を読みます。ファイルが顧客にとって使える成果であり、コミットによって修正を再利用できます。
引き続き利用できなければならない、通常の注文と後から追加された商品を確認します。
python3 quote.py ../orders/plain.order
python3 quote.py ../orders/new-item.order
通常のマグは EUR 16.00 のままです。STUDENT5 を使う Trail pin 2個は、小計 EUR 10.00、割引 EUR 0.50、合計 EUR 9.50 です。どちらも現在の案内と受け取り情報を保持します。チェックポイントでは、実際の現在のコミット、完全に保持されたツリー、過去の境界での動作、届けた見積書を観察します。コマンドの実行記録やメッセージのキーワードを採点するわけではありません。
まとめ
通常の注文と不具合のある注文で業務テストを定め、過去のバージョンを手動で分類して、Git bisect で同じ探索を自動化しました。最初の不良コミットを指す参照と HEAD を区別し、候補とその親を確認して main に戻り、後続の店舗更新を失わずに、目的を絞った修復をコミットしました。顧客には完全で正しい見積書が届きました。
独立したチャレンジでは、用意された同じプログラム、方針、テスト形式を使いつつ、クーポン、顧客の注文、不具合の位置が変わります。コミットのオフセットをコピーするのではなく、実際の証拠から選んでください。



