benchkit-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchkit/__init__.py +1 -0
- benchkit/_pi_proxy.py +219 -0
- benchkit/answer_key_guard.ts +49 -0
- benchkit/artifacts.py +87 -0
- benchkit/benchmarks/__init__.py +105 -0
- benchkit/benchmarks/aider_polyglot.py +405 -0
- benchkit/benchmarks/arc.py +52 -0
- benchkit/benchmarks/banking77.py +149 -0
- benchkit/benchmarks/base.py +19 -0
- benchkit/benchmarks/boolq.py +49 -0
- benchkit/benchmarks/chartqa.py +133 -0
- benchkit/benchmarks/code_verifier.py +77 -0
- benchkit/benchmarks/downloads.py +166 -0
- benchkit/benchmarks/evalplus.py +288 -0
- benchkit/benchmarks/git_surgery.py +485 -0
- benchkit/benchmarks/gpqa.py +56 -0
- benchkit/benchmarks/gsm8k.py +97 -0
- benchkit/benchmarks/hellaswag.py +49 -0
- benchkit/benchmarks/humaneval.py +103 -0
- benchkit/benchmarks/ifeval.py +56 -0
- benchkit/benchmarks/ifeval_instructions.py +477 -0
- benchkit/benchmarks/mbpp.py +60 -0
- benchkit/benchmarks/mc_arena.py +479 -0
- benchkit/benchmarks/mcq.py +94 -0
- benchkit/benchmarks/medfailbench.py +537 -0
- benchkit/benchmarks/mmlu.py +54 -0
- benchkit/benchmarks/mmlu_pro.py +62 -0
- benchkit/benchmarks/mmvp.py +115 -0
- benchkit/benchmarks/ocrbench.py +96 -0
- benchkit/benchmarks/openbookqa.py +52 -0
- benchkit/benchmarks/patcheval.py +929 -0
- benchkit/benchmarks/piqa.py +52 -0
- benchkit/benchmarks/ruler.py +172 -0
- benchkit/benchmarks/ruler_generator.py +535 -0
- benchkit/benchmarks/sanity.py +174 -0
- benchkit/benchmarks/treejs_arena.py +268 -0
- benchkit/benchmarks/truthfulqa.py +52 -0
- benchkit/benchmarks/utils.py +25 -0
- benchkit/benchmarks/vision.py +230 -0
- benchkit/benchmarks/vstar.py +108 -0
- benchkit/benchmarks/winogrande.py +52 -0
- benchkit/benchmarks/xstest.py +116 -0
- benchkit/browser.py +429 -0
- benchkit/catalog.py +121 -0
- benchkit/cli.py +1000 -0
- benchkit/client.py +1413 -0
- benchkit/controls.py +87 -0
- benchkit/datasets/MMLU_LICENSE.txt +21 -0
- benchkit/datasets/README.md +142 -0
- benchkit/datasets/arc_challenge.jsonl +1172 -0
- benchkit/datasets/banking77.jsonl +3080 -0
- benchkit/datasets/banking77_intents.json +79 -0
- benchkit/datasets/boolq.jsonl +3270 -0
- benchkit/datasets/code_verifier.jsonl +500 -0
- benchkit/datasets/gpqa_diamond.jsonl +198 -0
- benchkit/datasets/gsm8k.jsonl +1319 -0
- benchkit/datasets/hellaswag.jsonl +1000 -0
- benchkit/datasets/humaneval.jsonl +164 -0
- benchkit/datasets/ifeval.jsonl +541 -0
- benchkit/datasets/mbpp.jsonl +500 -0
- benchkit/datasets/mc_blocks_1_20_1.jsonl +1003 -0
- benchkit/datasets/medfailbench_hard30.jsonl +30 -0
- benchkit/datasets/medfailbench_hard30.md +13 -0
- benchkit/datasets/openbookqa.jsonl +500 -0
- benchkit/datasets/piqa.jsonl +1838 -0
- benchkit/datasets/sanity_code.jsonl +6 -0
- benchkit/datasets/truthfulqa.jsonl +817 -0
- benchkit/datasets/winogrande.jsonl +1267 -0
- benchkit/datasets/xstest.csv +451 -0
- benchkit/decision.py +194 -0
- benchkit/demo.py +451 -0
- benchkit/dockerfiles.py +247 -0
- benchkit/engine.py +1270 -0
- benchkit/evaluation.py +195 -0
- benchkit/events.py +141 -0
- benchkit/executor.py +96 -0
- benchkit/git_surgery/backport-release-stack/setup.sh +328 -0
- benchkit/git_surgery/backport-release-stack/verify.sh +119 -0
- benchkit/git_surgery/bisect-the-regression/setup.sh +49 -0
- benchkit/git_surgery/bisect-the-regression/verify.sh +34 -0
- benchkit/git_surgery/rebase-conflict-chain/setup.sh +65 -0
- benchkit/git_surgery/rebase-conflict-chain/verify.sh +43 -0
- benchkit/git_surgery/recover-complex-stash/setup.sh +152 -0
- benchkit/git_surgery/recover-complex-stash/verify.sh +95 -0
- benchkit/git_surgery/recover-lost-work/setup.sh +40 -0
- benchkit/git_surgery/recover-lost-work/verify.sh +36 -0
- benchkit/git_surgery/repair-force-pushed-remote/setup.sh +171 -0
- benchkit/git_surgery/repair-force-pushed-remote/verify.sh +83 -0
- benchkit/git_surgery/revert-merge-with-followups/setup.sh +198 -0
- benchkit/git_surgery/revert-merge-with-followups/verify.sh +98 -0
- benchkit/git_surgery/secret-in-history/README.md +27 -0
- benchkit/git_surgery/secret-in-history/setup.sh +107 -0
- benchkit/git_surgery/secret-in-history/verify.sh +106 -0
- benchkit/git_surgery/split-the-mega-commit/setup.sh +50 -0
- benchkit/git_surgery/split-the-mega-commit/verify.sh +48 -0
- benchkit/git_surgery/untangle-nested-submodules/setup.sh +193 -0
- benchkit/git_surgery/untangle-nested-submodules/verify.sh +90 -0
- benchkit/history.py +474 -0
- benchkit/jobs.py +332 -0
- benchkit/leakage.py +51 -0
- benchkit/looping.py +873 -0
- benchkit/mc_render.py +274 -0
- benchkit/mc_viewer/dist/atlas.png +0 -0
- benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
- benchkit/mc_viewer/dist/version.json +4 -0
- benchkit/mc_viewer/dist/viewer.html +23 -0
- benchkit/mc_viewer/dist/viewer.js +2 -0
- benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +25 -0
- benchkit/mc_viewer/dist/worker.js +2 -0
- benchkit/mc_viewer/dist/worker.js.LICENSE.txt +19 -0
- benchkit/metrics.py +100 -0
- benchkit/ollama.py +60 -0
- benchkit/perf.py +353 -0
- benchkit/perf_report.py +207 -0
- benchkit/perturbations.py +208 -0
- benchkit/pi_agent.py +476 -0
- benchkit/pi_package/package-lock.json +1827 -0
- benchkit/pi_package/package.json +8 -0
- benchkit/report.py +142 -0
- benchkit/report_markdown.py +645 -0
- benchkit/results.py +688 -0
- benchkit/retry.py +163 -0
- benchkit/runner.py +769 -0
- benchkit/sandbox.py +1167 -0
- benchkit/template_check.py +291 -0
- benchkit/templates/arena.html +504 -0
- benchkit/templates/history.html +58 -0
- benchkit/templates/perf.html +509 -0
- benchkit/templates/report.html +769 -0
- benchkit/tui/__init__.py +5 -0
- benchkit/tui/app.py +87 -0
- benchkit/tui/app.tcss +512 -0
- benchkit/tui/formatting.py +84 -0
- benchkit/tui/screens/__init__.py +1 -0
- benchkit/tui/screens/connect.py +191 -0
- benchkit/tui/screens/detail.py +288 -0
- benchkit/tui/screens/help.py +84 -0
- benchkit/tui/screens/modals.py +544 -0
- benchkit/tui/screens/results.py +361 -0
- benchkit/tui/screens/run.py +988 -0
- benchkit/tui/screens/setup.py +733 -0
- benchkit/tui/theme.py +135 -0
- benchkit/tui/widgets.py +120 -0
- benchkit_cli-0.1.0.dist-info/METADATA +294 -0
- benchkit_cli-0.1.0.dist-info/RECORD +149 -0
- benchkit_cli-0.1.0.dist-info/WHEEL +5 -0
- benchkit_cli-0.1.0.dist-info/entry_points.txt +3 -0
- benchkit_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- benchkit_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/revert-merge-with-followups}"
|
|
5
|
+
slug="$(printf '%s' "$seed:revert-merge" | sha256sum | cut -c1-12)"
|
|
6
|
+
mkdir -p "$workspace"
|
|
7
|
+
cd "$workspace"
|
|
8
|
+
git init -q -b main
|
|
9
|
+
git config user.name "BenchKit Generator"
|
|
10
|
+
git config user.email "generator@benchkit.invalid"
|
|
11
|
+
git config commit.gpgSign false
|
|
12
|
+
git config core.autocrlf false
|
|
13
|
+
export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
|
|
14
|
+
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
|
|
15
|
+
export GIT_CONFIG_GLOBAL=/dev/null
|
|
16
|
+
clock=1700600000
|
|
17
|
+
tick() {
|
|
18
|
+
clock=$((clock + 60))
|
|
19
|
+
export GIT_AUTHOR_DATE="@$clock +0000" GIT_COMMITTER_DATE="@$clock +0000"
|
|
20
|
+
}
|
|
21
|
+
commit() {
|
|
22
|
+
tick
|
|
23
|
+
git add -A
|
|
24
|
+
git commit -q "$@"
|
|
25
|
+
}
|
|
26
|
+
merge() {
|
|
27
|
+
tick
|
|
28
|
+
git merge -q --no-ff "$@"
|
|
29
|
+
}
|
|
30
|
+
replace() {
|
|
31
|
+
python3 - "$@" <<'PY'
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
path, old, new = Path(sys.argv[1]), sys.argv[2], sys.argv[3]
|
|
36
|
+
text = path.read_text()
|
|
37
|
+
if old not in text:
|
|
38
|
+
raise SystemExit(f"setup: {old!r} not found in {path}")
|
|
39
|
+
path.write_text(text.replace(old, new, 1))
|
|
40
|
+
PY
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
printf '__pycache__/\n' > .gitignore
|
|
44
|
+
cat > pricing.py <<'PY'
|
|
45
|
+
"""Order pricing in integer cents."""
|
|
46
|
+
|
|
47
|
+
PRICES = {"apple": 120, "pear": 95, "plum": 60}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def price(sku: str, qty: int) -> int:
|
|
51
|
+
unit = PRICES[sku]
|
|
52
|
+
return unit * qty
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def total(lines: list[tuple[str, int]]) -> int:
|
|
56
|
+
return sum(price(sku, qty) for sku, qty in lines)
|
|
57
|
+
PY
|
|
58
|
+
cat > test_pricing.py <<'PY'
|
|
59
|
+
import unittest
|
|
60
|
+
|
|
61
|
+
from pricing import price, total
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PricingTests(unittest.TestCase):
|
|
65
|
+
def test_single_line(self):
|
|
66
|
+
self.assertEqual(price("pear", 3), 285)
|
|
67
|
+
|
|
68
|
+
def test_total(self):
|
|
69
|
+
self.assertEqual(total([("apple", 1), ("plum", 2)]), 240)
|
|
70
|
+
PY
|
|
71
|
+
printf '# Pricing service %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
|
|
72
|
+
commit -m "bootstrap pricing service $slug"
|
|
73
|
+
|
|
74
|
+
git switch -q -c catalog-search
|
|
75
|
+
cat > search.py <<'PY'
|
|
76
|
+
from pricing import PRICES
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def search(prefix: str) -> list[str]:
|
|
80
|
+
return sorted(sku for sku in PRICES if sku.startswith(prefix))
|
|
81
|
+
PY
|
|
82
|
+
cat > test_search.py <<'PY'
|
|
83
|
+
import unittest
|
|
84
|
+
|
|
85
|
+
from search import search
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class SearchTests(unittest.TestCase):
|
|
89
|
+
def test_prefix(self):
|
|
90
|
+
self.assertEqual(search("p"), ["pear", "plum"])
|
|
91
|
+
PY
|
|
92
|
+
commit -m "Add catalog prefix search"
|
|
93
|
+
git switch -q main
|
|
94
|
+
merge catalog-search -m "Merge branch 'catalog-search'"
|
|
95
|
+
|
|
96
|
+
git switch -q -c pricing-engine
|
|
97
|
+
replace pricing.py 'PRICES = {"apple": 120, "pear": 95, "plum": 60}
|
|
98
|
+
' 'PRICES = {"apple": 120, "pear": 95, "plum": 60}
|
|
99
|
+
BULK_QTY = 10
|
|
100
|
+
'
|
|
101
|
+
replace pricing.py ' unit = PRICES[sku]
|
|
102
|
+
return unit * qty' ' unit = PRICES[sku]
|
|
103
|
+
if qty >= BULK_QTY:
|
|
104
|
+
unit = unit * 9 // 10
|
|
105
|
+
return unit * qty'
|
|
106
|
+
cat > test_bulk.py <<'PY'
|
|
107
|
+
import unittest
|
|
108
|
+
|
|
109
|
+
from pricing import price
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class BulkTests(unittest.TestCase):
|
|
113
|
+
def test_bulk_discount(self):
|
|
114
|
+
self.assertEqual(price("apple", 10), 1080)
|
|
115
|
+
|
|
116
|
+
def test_below_threshold(self):
|
|
117
|
+
self.assertEqual(price("apple", 9), 1080)
|
|
118
|
+
PY
|
|
119
|
+
commit -m "Add bulk discount"
|
|
120
|
+
replace pricing.py ' return unit * qty' ' return unit * min(qty, 99)'
|
|
121
|
+
commit -m "Cap line quantities to protect inventory"
|
|
122
|
+
|
|
123
|
+
git switch -q main
|
|
124
|
+
cat >> pricing.py <<'PY'
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def total_with_tax(lines: list[tuple[str, int]]) -> int:
|
|
128
|
+
return (total(lines) * 108 + 50) // 100
|
|
129
|
+
PY
|
|
130
|
+
cat >> test_pricing.py <<'PY'
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TaxTests(unittest.TestCase):
|
|
134
|
+
def test_total_with_tax(self):
|
|
135
|
+
from pricing import total_with_tax
|
|
136
|
+
|
|
137
|
+
self.assertEqual(total_with_tax([("pear", 1)]), 103)
|
|
138
|
+
PY
|
|
139
|
+
commit -m "Add tax-inclusive totals"
|
|
140
|
+
merge pricing-engine -m "Merge branch 'pricing-engine'"
|
|
141
|
+
|
|
142
|
+
replace pricing.py 'BULK_QTY = 10
|
|
143
|
+
' 'BULK_QTY = 8
|
|
144
|
+
BULK_RATE = 90
|
|
145
|
+
'
|
|
146
|
+
replace pricing.py ' unit = unit * 9 // 10' ' unit = unit * BULK_RATE // 100'
|
|
147
|
+
cat > test_bulk.py <<'PY'
|
|
148
|
+
import unittest
|
|
149
|
+
|
|
150
|
+
from pricing import price
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class BulkTests(unittest.TestCase):
|
|
154
|
+
def test_bulk_discount(self):
|
|
155
|
+
self.assertEqual(price("apple", 8), 864)
|
|
156
|
+
|
|
157
|
+
def test_below_threshold(self):
|
|
158
|
+
self.assertEqual(price("apple", 7), 840)
|
|
159
|
+
PY
|
|
160
|
+
commit -m "Make the bulk discount configurable"
|
|
161
|
+
|
|
162
|
+
cat >> pricing.py <<'PY'
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def receipt(lines: list[tuple[str, int]]) -> str:
|
|
166
|
+
return f"net={total(lines)} gross={total_with_tax(lines)}"
|
|
167
|
+
PY
|
|
168
|
+
cat >> test_pricing.py <<'PY'
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class ReceiptTests(unittest.TestCase):
|
|
172
|
+
def test_receipt_uses_bulk_and_tax(self):
|
|
173
|
+
from pricing import receipt
|
|
174
|
+
|
|
175
|
+
self.assertEqual(receipt([("apple", 8)]), "net=864 gross=933")
|
|
176
|
+
PY
|
|
177
|
+
commit -m "Add itemised receipts"
|
|
178
|
+
|
|
179
|
+
replace pricing.py 'def price(sku: str, qty: int) -> int:
|
|
180
|
+
' 'def price(sku: str, qty: int) -> int:
|
|
181
|
+
if qty <= 0:
|
|
182
|
+
raise ValueError("quantity must be positive")
|
|
183
|
+
'
|
|
184
|
+
cat >> test_pricing.py <<'PY'
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class QuantityTests(unittest.TestCase):
|
|
188
|
+
def test_rejects_empty_lines(self):
|
|
189
|
+
with self.assertRaises(ValueError):
|
|
190
|
+
price("pear", 0)
|
|
191
|
+
|
|
192
|
+
def test_large_orders_charge_every_unit(self):
|
|
193
|
+
self.assertEqual(price("plum", 150), 8100)
|
|
194
|
+
PY
|
|
195
|
+
commit -m "Validate quantities and cover large orders"
|
|
196
|
+
git branch -q -D catalog-search pricing-engine
|
|
197
|
+
unset GIT_AUTHOR_DATE GIT_COMMITTER_DATE
|
|
198
|
+
git status --porcelain | grep -q . && exit 1 || true
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/revert-merge-with-followups}"
|
|
5
|
+
emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
|
|
6
|
+
ids="revert_commit topology_preserved regression_removed both_parents_kept tests_pass"
|
|
7
|
+
if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
|
|
8
|
+
for id in $ids; do emit "$id" 0 "repository missing"; done
|
|
9
|
+
emit trap 1 "repository replaced"
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
reference_root="$(mktemp -d)"
|
|
13
|
+
reference="$reference_root/reference"
|
|
14
|
+
bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
|
|
15
|
+
followups_tip="$(git -C "$reference" rev-parse main)"
|
|
16
|
+
faulty="$(git -C "$reference" log main --merges --format=%H --grep="^Merge branch 'pricing-engine'$" -1)"
|
|
17
|
+
mainline="$(git -C "$reference" rev-parse "$faulty^1")"
|
|
18
|
+
healthy="$(git -C "$reference" log main --merges --format=%H --grep="^Merge branch 'catalog-search'$" -1)"
|
|
19
|
+
declare -A test_blobs=()
|
|
20
|
+
for path in test_pricing.py test_bulk.py test_search.py; do
|
|
21
|
+
test_blobs[$path]="$(git -C "$reference" rev-parse "main:$path")"
|
|
22
|
+
done
|
|
23
|
+
rm -rf "$reference_root"
|
|
24
|
+
|
|
25
|
+
git_w() { git -C "$workspace" "$@" 2>/dev/null; }
|
|
26
|
+
head="$(git_w rev-parse -q --verify HEAD)"
|
|
27
|
+
branch="$(git_w symbolic-ref -q --short HEAD)"
|
|
28
|
+
main="$(git_w rev-parse -q --verify refs/heads/main)"
|
|
29
|
+
on_top=0
|
|
30
|
+
[[ -n "$main" && "$(git_w rev-parse -q --verify "$main^")" == "$followups_tip" ]] && on_top=1
|
|
31
|
+
parents="$(git_w rev-list --parents -n 1 "$main" | wc -w)"
|
|
32
|
+
message="$(git_w log -1 --format=%B "$main" | tr '\n' ' ' | tr -s ' ')"
|
|
33
|
+
references=0
|
|
34
|
+
[[ "$message" == *"This reverts commit $faulty, reversing changes made to $mainline."* ]] && references=1
|
|
35
|
+
revert=0
|
|
36
|
+
[[ "$on_top" == 1 && "$parents" == 2 && "$references" == 1 ]] && revert=1
|
|
37
|
+
emit revert_commit "$revert" "single_commit_on_followups=$on_top references_merge_and_mainline=$references"
|
|
38
|
+
|
|
39
|
+
topology=0
|
|
40
|
+
if [[ "$on_top" == 1 ]]; then
|
|
41
|
+
first_parent="$(git_w rev-list --first-parent "$main" | tr '\n' ' ')"
|
|
42
|
+
merge_parents="$(git_w rev-list --parents -n 1 "$faulty" | wc -w)"
|
|
43
|
+
[[ "$first_parent" == *"$faulty"* && "$first_parent" == *"$healthy"* && "$merge_parents" == 3 && "$branch" == main && "$head" == "$main" ]] && topology=1
|
|
44
|
+
fi
|
|
45
|
+
emit topology_preserved "$topology" "merges_on_first_parent=$topology branch=$branch"
|
|
46
|
+
|
|
47
|
+
tree="$(mktemp -d)"
|
|
48
|
+
[[ -n "$main" ]] && git_w archive "$main" | tar -x -C "$tree" 2>/dev/null
|
|
49
|
+
behavior() { (cd "$tree" && python3 -B -c "$1") >/dev/null 2>&1; }
|
|
50
|
+
regression=0
|
|
51
|
+
if [[ "$on_top" == 1 ]] && behavior '
|
|
52
|
+
from pricing import price
|
|
53
|
+
assert price("plum", 150) == 8100
|
|
54
|
+
assert price("pear", 100) == 8500
|
|
55
|
+
assert price("apple", 1000) == 108000'; then
|
|
56
|
+
regression=1
|
|
57
|
+
fi
|
|
58
|
+
emit regression_removed "$regression" "large_orders_charged=$regression"
|
|
59
|
+
|
|
60
|
+
parents_kept=0
|
|
61
|
+
bulk=0
|
|
62
|
+
tax=0
|
|
63
|
+
behavior '
|
|
64
|
+
from pricing import BULK_QTY, BULK_RATE, price
|
|
65
|
+
assert (BULK_QTY, BULK_RATE) == (8, 90)
|
|
66
|
+
assert price("apple", 8) == 864 and price("apple", 7) == 840' && bulk=1
|
|
67
|
+
behavior '
|
|
68
|
+
from pricing import receipt, total_with_tax
|
|
69
|
+
from search import search
|
|
70
|
+
assert total_with_tax([("pear", 1)]) == 103
|
|
71
|
+
assert receipt([("apple", 8)]) == "net=864 gross=933"
|
|
72
|
+
assert search("p") == ["pear", "plum"]
|
|
73
|
+
try:
|
|
74
|
+
from pricing import price
|
|
75
|
+
price("pear", 0)
|
|
76
|
+
except ValueError:
|
|
77
|
+
pass
|
|
78
|
+
else:
|
|
79
|
+
raise AssertionError("quantity validation lost")' && tax=1
|
|
80
|
+
[[ "$on_top" == 1 && "$bulk" == 1 && "$tax" == 1 ]] && parents_kept=1
|
|
81
|
+
emit both_parents_kept "$parents_kept" "pricing_engine_side=$bulk main_side_and_followups=$tax"
|
|
82
|
+
|
|
83
|
+
tests=0
|
|
84
|
+
blobs=1
|
|
85
|
+
for path in "${!test_blobs[@]}"; do
|
|
86
|
+
[[ "$(git_w rev-parse "$main:$path")" == "${test_blobs[$path]}" ]] || blobs=0
|
|
87
|
+
done
|
|
88
|
+
if [[ "$on_top" == 1 && "$blobs" == 1 ]]; then
|
|
89
|
+
(cd "$tree" && python3 -B -m unittest discover) >/dev/null 2>&1 && tests=1
|
|
90
|
+
fi
|
|
91
|
+
rm -rf "$tree"
|
|
92
|
+
emit tests_pass "$tests" "tests_unchanged=$blobs"
|
|
93
|
+
|
|
94
|
+
trap=0
|
|
95
|
+
git_w merge-base --is-ancestor "$followups_tip" "$main" || trap=1
|
|
96
|
+
git_w merge-base --is-ancestor "$faulty" "$main" || trap=1
|
|
97
|
+
[[ "$bulk" == 1 && "$tax" == 1 ]] || trap=1
|
|
98
|
+
emit trap "$trap" "history_reset_or_parent_behavior_lost=$trap"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# secret-in-history
|
|
2
|
+
|
|
3
|
+
`setup.sh SEED WORKSPACE` creates a five-commit repository with fixed Git
|
|
4
|
+
identity and timestamps. The generated AWS-style credential appears in the
|
|
5
|
+
second commit, while the third commit replaces the same line. Removing the
|
|
6
|
+
credential commit therefore causes a real replay conflict. A content-filtering
|
|
7
|
+
rewrite may instead retain a redacted five-commit chain; both outcomes are valid
|
|
8
|
+
when the secret is absent and all later behavior survives. Later commits change
|
|
9
|
+
the retry policy and documentation.
|
|
10
|
+
|
|
11
|
+
`verify.sh SEED WORKSPACE` emits tab-separated, deterministic state checks. It
|
|
12
|
+
uses `rev-list`, `cat-file`, `merge-base`, exact commit subjects, source checks,
|
|
13
|
+
and Python's offline unittest runner. BenchKit combines those checks with Pi's
|
|
14
|
+
native tool trace for the history-search and rewrite-path checkpoints.
|
|
15
|
+
|
|
16
|
+
To validate the task by hand, build or run the Git Surgery image, execute the
|
|
17
|
+
setup script, enter `/workspace/secret-in-history`, and repair the repository
|
|
18
|
+
with normal Git commands. Then run:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bash /opt/git-surgery/secret-in-history/verify.sh \
|
|
22
|
+
424242 /workspace/secret-in-history
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
A correct state reports `1` for repository continuity, preserved history,
|
|
26
|
+
secret absence, and tests, and `0` for destructive reinitialization. The two
|
|
27
|
+
trace-derived checkpoints are added by BenchKit during an actual Pi run.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
|
|
5
|
+
workspace="${2:-/workspace/secret-in-history}"
|
|
6
|
+
|
|
7
|
+
digest() {
|
|
8
|
+
printf '%s' "$seed:$1" | sha256sum | cut -c1-16
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
slug="$(digest file)"
|
|
12
|
+
secret="AKIA$(printf '%s' "$seed:secret" | sha256sum | tr 'a-f' 'A-F' | cut -c1-16)"
|
|
13
|
+
module="service_${slug}"
|
|
14
|
+
|
|
15
|
+
mkdir -p "$workspace"
|
|
16
|
+
cd "$workspace"
|
|
17
|
+
git init -q -b main
|
|
18
|
+
git config user.name "BenchKit Generator"
|
|
19
|
+
git config user.email "generator@benchkit.invalid"
|
|
20
|
+
git config commit.gpgSign false
|
|
21
|
+
git config core.autocrlf false
|
|
22
|
+
|
|
23
|
+
export GIT_AUTHOR_NAME="BenchKit Generator"
|
|
24
|
+
export GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
|
|
25
|
+
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
|
|
26
|
+
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
|
|
27
|
+
|
|
28
|
+
commit_at() {
|
|
29
|
+
timestamp="$1"
|
|
30
|
+
shift
|
|
31
|
+
GIT_AUTHOR_DATE="@$timestamp +0000" GIT_COMMITTER_DATE="@$timestamp +0000" \
|
|
32
|
+
git commit -q "$@"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
cat > "$module.py" <<EOF
|
|
36
|
+
"""Generated deployment configuration."""
|
|
37
|
+
|
|
38
|
+
REGION = "eu-central-1"
|
|
39
|
+
RETRIES = 2
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def endpoint() -> str:
|
|
43
|
+
return f"https://{REGION}.example.invalid"
|
|
44
|
+
EOF
|
|
45
|
+
cat > test_config.py <<EOF
|
|
46
|
+
import unittest
|
|
47
|
+
|
|
48
|
+
import $module as config
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ConfigTests(unittest.TestCase):
|
|
52
|
+
def test_endpoint(self):
|
|
53
|
+
self.assertEqual(config.endpoint(), "https://eu-central-1.example.invalid")
|
|
54
|
+
|
|
55
|
+
def test_retry_policy(self):
|
|
56
|
+
self.assertEqual(config.RETRIES, 5)
|
|
57
|
+
|
|
58
|
+
def test_credential_source(self):
|
|
59
|
+
self.assertEqual(config.CREDENTIAL_ENV, "BENCHKIT_DEPLOY_KEY")
|
|
60
|
+
EOF
|
|
61
|
+
cat > README.md <<EOF
|
|
62
|
+
# Deployment helper ${slug}
|
|
63
|
+
|
|
64
|
+
Run tests with \`python3 -m unittest -v\`.
|
|
65
|
+
EOF
|
|
66
|
+
git add .
|
|
67
|
+
commit_at 1700000000 -m "bootstrap deployment helper ${slug}"
|
|
68
|
+
baseline_sha="$(git rev-parse HEAD)"
|
|
69
|
+
|
|
70
|
+
printf '\nDEPLOY_KEY = "%s"\n' "$secret" >> "$module.py"
|
|
71
|
+
git add "$module.py"
|
|
72
|
+
commit_at 1700000060 -m "temporarily configure deployment credentials"
|
|
73
|
+
|
|
74
|
+
python3 - "$module.py" <<'PY'
|
|
75
|
+
from pathlib import Path
|
|
76
|
+
import sys
|
|
77
|
+
|
|
78
|
+
path = Path(sys.argv[1])
|
|
79
|
+
text = path.read_text()
|
|
80
|
+
text = text.replace('DEPLOY_KEY = "', 'CREDENTIAL_ENV = "BENCHKIT_DEPLOY_KEY" # replaced: ')
|
|
81
|
+
text = text.replace('"\n', '"\n', 1)
|
|
82
|
+
# Remove the leaked value from the current tree while deliberately changing the
|
|
83
|
+
# same line, so dropping/editing the earlier commit creates a rebase conflict.
|
|
84
|
+
lines = text.splitlines()
|
|
85
|
+
lines[-1] = 'CREDENTIAL_ENV = "BENCHKIT_DEPLOY_KEY"'
|
|
86
|
+
path.write_text("\n".join(lines) + "\n")
|
|
87
|
+
PY
|
|
88
|
+
git add "$module.py"
|
|
89
|
+
commit_at 1700000120 -m "read deployment credentials from environment"
|
|
90
|
+
|
|
91
|
+
python3 - "$module.py" <<'PY'
|
|
92
|
+
from pathlib import Path
|
|
93
|
+
import sys
|
|
94
|
+
|
|
95
|
+
path = Path(sys.argv[1])
|
|
96
|
+
text = path.read_text().replace("RETRIES = 2", "RETRIES = 5")
|
|
97
|
+
path.write_text(text)
|
|
98
|
+
PY
|
|
99
|
+
git add "$module.py"
|
|
100
|
+
commit_at 1700000180 -m "harden deployment retry policy"
|
|
101
|
+
|
|
102
|
+
printf '\nHistory must remain reviewable after credential cleanup.\n' >> README.md
|
|
103
|
+
git add README.md
|
|
104
|
+
commit_at 1700000240 -m "document credential cleanup policy"
|
|
105
|
+
|
|
106
|
+
git tag "benchkit-baseline-$slug" "$baseline_sha"
|
|
107
|
+
git status --porcelain | grep -q . && exit 1 || true
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
|
|
4
|
+
seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
|
|
5
|
+
workspace="${2:-/workspace/secret-in-history}"
|
|
6
|
+
|
|
7
|
+
digest() {
|
|
8
|
+
printf '%s' "$seed:$1" | sha256sum | cut -c1-16
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
slug="$(digest file)"
|
|
12
|
+
secret="AKIA$(printf '%s' "$seed:secret" | sha256sum | tr 'a-f' 'A-F' | cut -c1-16)"
|
|
13
|
+
module="service_${slug}.py"
|
|
14
|
+
baseline_tag="benchkit-baseline-$slug"
|
|
15
|
+
reference_root="$(mktemp -d)"
|
|
16
|
+
reference="$reference_root/reference"
|
|
17
|
+
bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
|
|
18
|
+
expected_head="$(git -C "$reference" rev-parse HEAD)"
|
|
19
|
+
expected_tree="$(git -C "$reference" rev-parse 'HEAD^{tree}')"
|
|
20
|
+
rm -rf "$reference_root"
|
|
21
|
+
|
|
22
|
+
emit() {
|
|
23
|
+
printf '%s\t%s\t%s\n' "$1" "$2" "$3"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
|
|
27
|
+
emit repository_continuity 0 "git repository missing"
|
|
28
|
+
emit history_preserved 0 "expected history unavailable"
|
|
29
|
+
emit secret_absent 0 "reachable objects could not be inspected"
|
|
30
|
+
emit tests_pass 0 "tests could not run"
|
|
31
|
+
emit destructive_reinit 1 "original repository was removed"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
baseline="$(git -C "$workspace" rev-parse -q --verify "refs/tags/$baseline_tag^{commit}" 2>/dev/null || true)"
|
|
36
|
+
continuity=0
|
|
37
|
+
if [[ -n "$baseline" ]] && git -C "$workspace" merge-base --is-ancestor "$baseline" HEAD 2>/dev/null; then
|
|
38
|
+
continuity=1
|
|
39
|
+
fi
|
|
40
|
+
emit repository_continuity "$continuity" "baseline=$baseline"
|
|
41
|
+
|
|
42
|
+
history_ok=1
|
|
43
|
+
count="$(git -C "$workspace" rev-list --count HEAD 2>/dev/null || printf 0)"
|
|
44
|
+
[[ "$count" == 4 || "$count" == 5 ]] || history_ok=0
|
|
45
|
+
current_head="$(git -C "$workspace" rev-parse HEAD 2>/dev/null || true)"
|
|
46
|
+
head_rewritten=0
|
|
47
|
+
if [[ -n "$expected_head" && "$current_head" != "$expected_head" ]]; then
|
|
48
|
+
head_rewritten=1
|
|
49
|
+
else
|
|
50
|
+
history_ok=0
|
|
51
|
+
fi
|
|
52
|
+
for message in \
|
|
53
|
+
"bootstrap deployment helper $slug" \
|
|
54
|
+
"read deployment credentials from environment" \
|
|
55
|
+
"harden deployment retry policy" \
|
|
56
|
+
"document credential cleanup policy"; do
|
|
57
|
+
git -C "$workspace" log --format=%s --all | grep -Fxq "$message" || history_ok=0
|
|
58
|
+
done
|
|
59
|
+
if [[ "$count" == 5 ]]; then
|
|
60
|
+
git -C "$workspace" log --format=%s --all \
|
|
61
|
+
| grep -Fxq "temporarily configure deployment credentials" || history_ok=0
|
|
62
|
+
fi
|
|
63
|
+
expected_line='CREDENTIAL_ENV = "BENCHKIT_DEPLOY_KEY"'
|
|
64
|
+
grep -Fxq "$expected_line" "$workspace/$module" 2>/dev/null || history_ok=0
|
|
65
|
+
grep -Fxq 'RETRIES = 5' "$workspace/$module" 2>/dev/null || history_ok=0
|
|
66
|
+
current_tree="$(git -C "$workspace" rev-parse 'HEAD^{tree}' 2>/dev/null || true)"
|
|
67
|
+
tree_match=0
|
|
68
|
+
if [[ -n "$expected_tree" && "$current_tree" == "$expected_tree" ]]; then
|
|
69
|
+
tree_match=1
|
|
70
|
+
else
|
|
71
|
+
history_ok=0
|
|
72
|
+
fi
|
|
73
|
+
unexpected_paths="$({
|
|
74
|
+
while read -r commit; do
|
|
75
|
+
git -C "$workspace" ls-tree -r --name-only "$commit" 2>/dev/null
|
|
76
|
+
done < <(git -C "$workspace" rev-list HEAD 2>/dev/null)
|
|
77
|
+
} | sort -u | grep -Ev "^(README\\.md|test_config\\.py|${module})$" || true)"
|
|
78
|
+
[[ -z "$unexpected_paths" ]] || history_ok=0
|
|
79
|
+
tree_detail="tree_match=$tree_match head_rewritten=$head_rewritten"
|
|
80
|
+
if [[ -n "$unexpected_paths" ]]; then
|
|
81
|
+
tree_detail="$tree_detail unexpected_paths=$(printf '%s' "$unexpected_paths" | tr '\n' ',')"
|
|
82
|
+
fi
|
|
83
|
+
emit history_preserved "$history_ok" "commit_count=$count $tree_detail"
|
|
84
|
+
|
|
85
|
+
secret_found=0
|
|
86
|
+
while read -r object _path; do
|
|
87
|
+
[[ -n "${object:-}" ]] || continue
|
|
88
|
+
type="$(git -C "$workspace" cat-file -t "$object" 2>/dev/null || true)"
|
|
89
|
+
if [[ "$type" == blob || "$type" == commit || "$type" == tag ]]; then
|
|
90
|
+
if git -C "$workspace" cat-file -p "$object" 2>/dev/null | grep -Fq "$secret"; then
|
|
91
|
+
secret_found=1
|
|
92
|
+
break
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
done < <(git -C "$workspace" rev-list --objects --all 2>/dev/null)
|
|
96
|
+
emit secret_absent "$((1 - secret_found))" "reachable_secret_objects=$secret_found"
|
|
97
|
+
|
|
98
|
+
test_exit=0
|
|
99
|
+
(cd "$workspace" && python3 -m unittest discover -v) \
|
|
100
|
+
>/tmp/git-surgery-tests.txt 2>&1 || test_exit=$?
|
|
101
|
+
test_output="$(tail -n 12 /tmp/git-surgery-tests.txt | tr '\t\n' ' ')"
|
|
102
|
+
emit tests_pass "$((test_exit == 0))" "exit=$test_exit $test_output"
|
|
103
|
+
|
|
104
|
+
destructive=0
|
|
105
|
+
[[ "$continuity" == 1 ]] || destructive=1
|
|
106
|
+
emit destructive_reinit "$destructive" "baseline_tag=$baseline_tag"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
seed="${1:?usage: setup.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/split-the-mega-commit}"
|
|
5
|
+
slug="$(printf '%s' "$seed:split" | sha256sum | cut -c1-12)"
|
|
6
|
+
mkdir -p "$workspace"
|
|
7
|
+
cd "$workspace"
|
|
8
|
+
git init -q -b main
|
|
9
|
+
git config user.name "BenchKit Generator"
|
|
10
|
+
git config user.email "generator@benchkit.invalid"
|
|
11
|
+
git config commit.gpgSign false
|
|
12
|
+
git config core.autocrlf false
|
|
13
|
+
export GIT_AUTHOR_NAME="BenchKit Generator" GIT_AUTHOR_EMAIL="generator@benchkit.invalid"
|
|
14
|
+
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
|
|
15
|
+
cat > invoice.py <<'PY'
|
|
16
|
+
from decimal import Decimal, ROUND_DOWN
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def cents(value: str) -> int:
|
|
20
|
+
return int((Decimal(value) * 100).quantize(Decimal("1"), rounding=ROUND_DOWN))
|
|
21
|
+
PY
|
|
22
|
+
cat > test_invoice.py <<'PY'
|
|
23
|
+
import unittest
|
|
24
|
+
from invoice import cents
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class InvoiceTests(unittest.TestCase):
|
|
28
|
+
def test_cents_rounds_half_up(self):
|
|
29
|
+
self.assertEqual(cents("10.235"), 1024)
|
|
30
|
+
PY
|
|
31
|
+
printf '# Invoice tools %s\n\nRun tests with `python3 -m unittest discover -v`.\n' "$slug" > README.md
|
|
32
|
+
git add .
|
|
33
|
+
GIT_AUTHOR_DATE='@1700200000 +0000' GIT_COMMITTER_DATE='@1700200000 +0000' git commit -q -m "bootstrap invoice tools $slug"
|
|
34
|
+
git tag "benchkit-split-baseline-$slug"
|
|
35
|
+
python3 -c 'from pathlib import Path; p=Path("invoice.py"); p.write_text(p.read_text().replace("ROUND_DOWN", "ROUND_HALF_UP"))'
|
|
36
|
+
cat > labels.py <<'PY'
|
|
37
|
+
def invoice_label(number: int) -> str:
|
|
38
|
+
return f"INV-{number:06d}"
|
|
39
|
+
PY
|
|
40
|
+
cat > test_labels.py <<'PY'
|
|
41
|
+
import unittest
|
|
42
|
+
from labels import invoice_label
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class LabelTests(unittest.TestCase):
|
|
46
|
+
def test_invoice_label(self):
|
|
47
|
+
self.assertEqual(invoice_label(42), "INV-000042")
|
|
48
|
+
PY
|
|
49
|
+
git add .
|
|
50
|
+
GIT_AUTHOR_DATE='@1700200060 +0000' GIT_COMMITTER_DATE='@1700200060 +0000' git commit -q -m "ship invoice patch bundle"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
seed="${1:?usage: verify.sh SEED [WORKSPACE]}"
|
|
4
|
+
workspace="${2:-/workspace/split-the-mega-commit}"
|
|
5
|
+
emit() { printf '%s\t%s\t%s\n' "$1" "$2" "$3"; }
|
|
6
|
+
if ! git -C "$workspace" rev-parse --git-dir >/dev/null 2>&1; then
|
|
7
|
+
for id in two_commits ordered_boundaries each_commit_tests final_tree; do emit "$id" 0 "repository missing"; done
|
|
8
|
+
emit trap 1 "repository replaced"
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
reference_root="$(mktemp -d)"
|
|
12
|
+
reference="$reference_root/reference"
|
|
13
|
+
bash "$(dirname "$0")/setup.sh" "$seed" "$reference" >/dev/null
|
|
14
|
+
baseline="$(git -C "$reference" rev-parse HEAD^)"
|
|
15
|
+
expected_tree="$(git -C "$reference" rev-parse 'HEAD^{tree}')"
|
|
16
|
+
rm -rf "$reference_root"
|
|
17
|
+
after="$(git -C "$workspace" rev-list --count "$baseline"..HEAD 2>/dev/null || printf 0)"
|
|
18
|
+
[[ "$after" == 2 ]] && two=1 || two=0
|
|
19
|
+
emit two_commits "$two" "commits_after_baseline=$after"
|
|
20
|
+
bugfix="$(git -C "$workspace" rev-parse "$baseline^{}" 2>/dev/null || true)"
|
|
21
|
+
if [[ "$two" == 1 ]]; then
|
|
22
|
+
bugfix="$(git -C "$workspace" rev-parse HEAD^)"
|
|
23
|
+
subject1="$(git -C "$workspace" log -1 --format=%s "$bugfix")"
|
|
24
|
+
subject2="$(git -C "$workspace" log -1 --format=%s HEAD)"
|
|
25
|
+
else
|
|
26
|
+
subject1=""; subject2=""
|
|
27
|
+
fi
|
|
28
|
+
boundary=0
|
|
29
|
+
if [[ "$subject1" == "Fix invoice rounding" && "$subject2" == "Add invoice labels" ]]; then
|
|
30
|
+
git -C "$workspace" cat-file -e "$bugfix:invoice.py" 2>/dev/null && \
|
|
31
|
+
! git -C "$workspace" cat-file -e "$bugfix:labels.py" 2>/dev/null && boundary=1
|
|
32
|
+
fi
|
|
33
|
+
emit ordered_boundaries "$boundary" "first=$subject1 second=$subject2"
|
|
34
|
+
tests_ok=0
|
|
35
|
+
if [[ "$boundary" == 1 ]]; then
|
|
36
|
+
tmp="$(mktemp -d)"
|
|
37
|
+
git -C "$workspace" archive "$bugfix" | tar -x -C "$tmp"
|
|
38
|
+
(cd "$tmp" && python3 -m unittest discover -v) >/dev/null 2>&1 && first_ok=1 || first_ok=0
|
|
39
|
+
rm -rf "$tmp"
|
|
40
|
+
(cd "$workspace" && python3 -m unittest discover -v) >/dev/null 2>&1 && final_ok=1 || final_ok=0
|
|
41
|
+
[[ "$first_ok" == 1 && "$final_ok" == 1 ]] && tests_ok=1
|
|
42
|
+
fi
|
|
43
|
+
emit each_commit_tests "$tests_ok" "intermediate_and_final=$tests_ok"
|
|
44
|
+
current_tree="$(git -C "$workspace" rev-parse 'HEAD^{tree}' 2>/dev/null || true)"
|
|
45
|
+
[[ -n "$expected_tree" && "$current_tree" == "$expected_tree" ]] && final=1 || final=0
|
|
46
|
+
emit final_tree "$final" "tree_match=$final"
|
|
47
|
+
[[ "$after" == 1 ]] && trap=1 || trap=0
|
|
48
|
+
emit trap "$trap" "squashed=$trap"
|