git-bisectlib 0.16.1__tar.gz → 0.16.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git_bisectlib
3
- Version: 0.16.1
3
+ Version: 0.16.2
4
4
  Summary: Write tiny git bisect recipes in Python; watch progress live in .bisect/status.md.
5
5
  Author-email: Martin Leitner-Ankerl <martin.ankerl@gmail.com>
6
6
  License: MIT
@@ -152,7 +152,7 @@ Date: 2026-06-15 11:40:00 +0200
152
152
  1 file changed, 6 insertions(+), 6 deletions(-)
153
153
  ```
154
154
 
155
- | good | bad | midpoint | range | status |
155
+ | good | bad | probe | range | status |
156
156
  |------|-----|----------|-------|--------|
157
157
  | `2801e9572` …, Bob | `79cb050c2` …, Alice | `cb5394973` …, Carol | 27d 15h · 11 commits | 🟢 good |
158
158
  | `cb5394973` …, Carol | `79cb050c2` …, Alice | `95345541b` …, Dan | 12d 7h · 6 commits | 🔴 bad · 81.2s |
@@ -160,7 +160,7 @@ Date: 2026-06-15 11:40:00 +0200
160
160
  | `cb5394973` …, Carol | `5c9dcafb3` …, Eve | `19d89b121` …, Fay | 3d 5h · 2 commits | 🟢 good |
161
161
  ````
162
162
 
163
- Each row reads in causal order — the **input range** (`good`/`bad`) → the **midpoint** git
163
+ Each row reads in causal order — the **input range** (`good`/`bad`) → the **probe** git
164
164
  chose → the **result** — so you see the range funnel down as you scan. The report is
165
165
  re-rendered the moment each command *starts*, links every step to its live-streamed log
166
166
  under `.bisect/<sha>/`, and — when the search resolves — shows the culprit the way
@@ -135,7 +135,7 @@ Date: 2026-06-15 11:40:00 +0200
135
135
  1 file changed, 6 insertions(+), 6 deletions(-)
136
136
  ```
137
137
 
138
- | good | bad | midpoint | range | status |
138
+ | good | bad | probe | range | status |
139
139
  |------|-----|----------|-------|--------|
140
140
  | `2801e9572` …, Bob | `79cb050c2` …, Alice | `cb5394973` …, Carol | 27d 15h · 11 commits | 🟢 good |
141
141
  | `cb5394973` …, Carol | `79cb050c2` …, Alice | `95345541b` …, Dan | 12d 7h · 6 commits | 🔴 bad · 81.2s |
@@ -143,7 +143,7 @@ Date: 2026-06-15 11:40:00 +0200
143
143
  | `cb5394973` …, Carol | `5c9dcafb3` …, Eve | `19d89b121` …, Fay | 3d 5h · 2 commits | 🟢 good |
144
144
  ````
145
145
 
146
- Each row reads in causal order — the **input range** (`good`/`bad`) → the **midpoint** git
146
+ Each row reads in causal order — the **input range** (`good`/`bad`) → the **probe** git
147
147
  chose → the **result** — so you see the range funnel down as you scan. The report is
148
148
  re-rendered the moment each command *starts*, links every step to its live-streamed log
149
149
  under `.bisect/<sha>/`, and — when the search resolves — shows the culprit the way
@@ -65,7 +65,7 @@ from typing import Callable, Literal, NoReturn, Optional, Union
65
65
  _BadWhen = Literal["fail", "pass"]
66
66
  _OnTimeout = Literal["abort", "skip", "bad"]
67
67
 
68
- __version__ = "0.16.1"
68
+ __version__ = "0.16.2"
69
69
 
70
70
  __all__ = [
71
71
  "run", "test", "hammer", "check", # the verbs
@@ -1428,7 +1428,7 @@ def replace(path: str, old: Union[str, "re.Pattern"], new: str, *,
1428
1428
  old_str = old.pattern if isinstance(old, re.Pattern) else str(old)
1429
1429
  _final.setdefault("fixups", []).append(
1430
1430
  {"kind": "replace", "path": path,
1431
- "detail": f"{old_str} {new}"})
1431
+ "old": old_str, "new": new})
1432
1432
  sys.stderr.write(f" edit {path}: {n} replacement(s)\n")
1433
1433
 
1434
1434
 
@@ -123,7 +123,7 @@ class Sidecar:
123
123
  class Row:
124
124
  bad: str # input-range bad bound (sha) before this evaluation
125
125
  good: str # input-range good bound (sha) before this evaluation
126
- midpoint: str # the commit evaluated this step
126
+ probe: str # the commit evaluated this step
127
127
  status: str # good | bad | skip | todo
128
128
  n_commits: int = 0 # candidate commits still in range at this step
129
129
  span_seconds: int = 0 # wall span between good and bad commit dates
@@ -229,7 +229,7 @@ def build_report(
229
229
  current_good: Optional[str] = None
230
230
  active_goods: list[str] = [] # every good marked so far (defines the range)
231
231
  rows: list[Row] = []
232
- seen_midpoints: set[str] = set()
232
+ seen_probes: set[str] = set()
233
233
 
234
234
  def ready() -> bool:
235
235
  return current_bad is not None and current_good is not None
@@ -259,17 +259,17 @@ def build_report(
259
259
  if current_good is None or is_ancestor(repo, current_good, sha):
260
260
  current_good = sha
261
261
 
262
- def add_row(midpoint: str, status: str) -> None:
262
+ def add_row(probe: str, status: str) -> None:
263
263
  rows.append(
264
264
  Row(
265
265
  bad=current_bad,
266
266
  good=current_good,
267
- midpoint=midpoint,
267
+ probe=probe,
268
268
  status=status,
269
269
  goods=list(active_goods),
270
270
  )
271
271
  )
272
- seen_midpoints.add(midpoint)
272
+ seen_probes.add(probe)
273
273
 
274
274
  for verb, args in ops:
275
275
  revs = [a for a in args if not a.startswith("-")]
@@ -338,14 +338,14 @@ def build_report(
338
338
  if n_remaining <= 1:
339
339
  first_bad = current_bad
340
340
 
341
- # In-flight row: HEAD is the midpoint git currently has checked out, awaiting a
341
+ # In-flight row: HEAD is the probe git currently has checked out, awaiting a
342
342
  # verdict, and is not yet a logged marking.
343
343
  in_progress = False
344
344
  if (
345
345
  ready()
346
346
  and first_bad is None
347
347
  and head
348
- and head not in seen_midpoints
348
+ and head not in seen_probes
349
349
  and head != current_good
350
350
  ):
351
351
  within_range = (
@@ -355,7 +355,7 @@ def build_report(
355
355
  # A per-commit sidecar written by the engine is proof HEAD is the commit
356
356
  # being evaluated right now — trust it when the ancestry checks can't
357
357
  # confirm the range (shallow/grafted clone, or an anchor good that isn't a
358
- # topological ancestor of the midpoint). This keeps status.md live (the
358
+ # topological ancestor of the probe). This keeps status.md live (the
359
359
  # in-flight row and its steps refresh after every command) on such repos.
360
360
  has_sidecar = bool(logs_dir) and (Path(logs_dir) / head / "eval.json").is_file()
361
361
  if within_range or has_sidecar:
@@ -365,7 +365,7 @@ def build_report(
365
365
  # Gather subjects + dates for every sha we reference.
366
366
  shas = set()
367
367
  for r in rows:
368
- shas.update([r.bad, r.good, r.midpoint])
368
+ shas.update([r.bad, r.good, r.probe])
369
369
  shas.update([orig_bad, current_bad, current_good, *orig_goods])
370
370
  shas.discard(None)
371
371
  subjects, dates, authors = _commit_meta(repo, shas)
@@ -381,7 +381,7 @@ def build_report(
381
381
  r.bad_date = dates.get(r.bad, "")
382
382
  r.span_seconds = _date_delta_seconds(r.good_date, r.bad_date)
383
383
  if logs_dir:
384
- r.sidecar = _load_sidecar(logs_dir, r.midpoint)
384
+ r.sidecar = _load_sidecar(logs_dir, r.probe)
385
385
 
386
386
  # The in-flight commit (HEAD) is a `todo` row because git hasn't recorded its
387
387
  # mark yet — but if the recipe already finished and its sidecar carries a
@@ -640,7 +640,7 @@ def render_markdown(rep: Report, details: bool = False, color: bool = True) -> s
640
640
  lines.append(f"> ⚠️ {rep.note}")
641
641
  lines.append("")
642
642
 
643
- lines.append("| good | bad | midpoint | range | status |")
643
+ lines.append("| good | bad | probe | range | status |")
644
644
  lines.append("|------|-----|----------|-------|--------|")
645
645
  for r in rep.rows:
646
646
  rng = f"{fmt_duration(r.span_seconds)} · {r.n_commits} commits"
@@ -649,7 +649,7 @@ def render_markdown(rep: Report, details: bool = False, color: bool = True) -> s
649
649
  if extra:
650
650
  status += f" · {extra}"
651
651
  lines.append(
652
- f"| {cell(r.good)} | {cell(r.bad)} | {cell(r.midpoint)} | {rng} | {status} |"
652
+ f"| {cell(r.good)} | {cell(r.bad)} | {cell(r.probe)} | {rng} | {status} |"
653
653
  )
654
654
  lines.append("")
655
655
 
@@ -660,15 +660,20 @@ def render_markdown(rep: Report, details: bool = False, color: bool = True) -> s
660
660
  lines.append("")
661
661
  for r in detail_rows:
662
662
  lines.append(
663
- f"### `{rep.short(r.midpoint)}` — {rep.subject(r.midpoint)} "
663
+ f"### `{rep.short(r.probe)}` — {rep.subject(r.probe)} "
664
664
  f"({icon(r.status)})"
665
665
  )
666
- if r.sidecar.fixups:
667
- fx = ", ".join(
668
- f"{f.get('kind')}: `{f.get('detail', f.get('path',''))}`"
669
- for f in r.sidecar.fixups
670
- )
671
- lines.append(f"- fixups: {fx}")
666
+ for f in r.sidecar.fixups:
667
+ kind = f.get("kind", "")
668
+ if kind == "replace":
669
+ lines.append(
670
+ f"- replace: in `{f['path']}`: "
671
+ f"`{f['old']}` `{f['new']}`"
672
+ )
673
+ else:
674
+ lines.append(
675
+ f"- {kind}: `{f.get('detail', f.get('path', ''))}`"
676
+ )
672
677
  for s in r.sidecar.steps:
673
678
  if s.verb != "hammer":
674
679
  continue
@@ -693,7 +698,7 @@ def render_markdown(rep: Report, details: bool = False, color: bool = True) -> s
693
698
  # link the step to its captured log file (relative to status.md,
694
699
  # which sits alongside the per-commit <sha>/ log dirs); the log
695
700
  # streams live, so the link is watchable while the step runs
696
- step = f"[{s.verb}]({r.midpoint}/{s.log})" if s.log else s.verb
701
+ step = f"[{s.verb}]({r.probe}/{s.log})" if s.log else s.verb
697
702
  lines.append(
698
703
  f"| {step} | `{s.cmd}` | {code} | {dur} |"
699
704
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git_bisectlib
3
- Version: 0.16.1
3
+ Version: 0.16.2
4
4
  Summary: Write tiny git bisect recipes in Python; watch progress live in .bisect/status.md.
5
5
  Author-email: Martin Leitner-Ankerl <martin.ankerl@gmail.com>
6
6
  License: MIT
@@ -152,7 +152,7 @@ Date: 2026-06-15 11:40:00 +0200
152
152
  1 file changed, 6 insertions(+), 6 deletions(-)
153
153
  ```
154
154
 
155
- | good | bad | midpoint | range | status |
155
+ | good | bad | probe | range | status |
156
156
  |------|-----|----------|-------|--------|
157
157
  | `2801e9572` …, Bob | `79cb050c2` …, Alice | `cb5394973` …, Carol | 27d 15h · 11 commits | 🟢 good |
158
158
  | `cb5394973` …, Carol | `79cb050c2` …, Alice | `95345541b` …, Dan | 12d 7h · 6 commits | 🔴 bad · 81.2s |
@@ -160,7 +160,7 @@ Date: 2026-06-15 11:40:00 +0200
160
160
  | `cb5394973` …, Carol | `5c9dcafb3` …, Eve | `19d89b121` …, Fay | 3d 5h · 2 commits | 🟢 good |
161
161
  ````
162
162
 
163
- Each row reads in causal order — the **input range** (`good`/`bad`) → the **midpoint** git
163
+ Each row reads in causal order — the **input range** (`good`/`bad`) → the **probe** git
164
164
  chose → the **result** — so you see the range funnel down as you scan. The report is
165
165
  re-rendered the moment each command *starts*, links every step to its live-streamed log
166
166
  under `.bisect/<sha>/`, and — when the search resolves — shows the culprit the way
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "git_bisectlib"
7
- version = "0.16.1"
7
+ version = "0.16.2"
8
8
  description = "Write tiny git bisect recipes in Python; watch progress live in .bisect/status.md."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -83,9 +83,9 @@ class TestReport(unittest.TestCase):
83
83
  rep = _report.build_report(d)
84
84
  self.assertIsNotNone(rep)
85
85
  self.assertGreaterEqual(len(rep.rows), 1)
86
- # first row's midpoint is the first commit git checked out
86
+ # first row's probe is the first commit git checked out
87
87
  first = rep.rows[0]
88
- self.assertEqual(first.midpoint, head1)
88
+ self.assertEqual(first.probe, head1)
89
89
  self.assertEqual(first.status, "bad" if has_bug else "good")
90
90
  # there should be an in-flight todo row for the new HEAD
91
91
  self.assertTrue(rep.in_progress)
@@ -106,7 +106,7 @@ class TestReport(unittest.TestCase):
106
106
  def test_range_count_excludes_all_goods(self):
107
107
  # In a merge DAG, git's candidate range excludes ancestors of EVERY good,
108
108
  # not just the latest. With a good anchor on a side branch that diverges
109
- # from the mainline midpoint, counting only `latest_good..bad` overcounts.
109
+ # from the mainline probe, counting only `latest_good..bad` overcounts.
110
110
  d = tempfile.mkdtemp(prefix="bisect-report-dag-")
111
111
  run(d, "git", "init", "-q")
112
112
  run(d, "git", "config", "user.email", "t@t.t")
@@ -184,8 +184,8 @@ class TestReport(unittest.TestCase):
184
184
  without_sc = _report.build_report(d) # no sidecar → no invented row
185
185
  finally:
186
186
  _report.is_ancestor = orig
187
- self.assertIn(head, [r.midpoint for r in with_sc.rows])
188
- self.assertNotIn(head, [r.midpoint for r in without_sc.rows])
187
+ self.assertIn(head, [r.probe for r in with_sc.rows])
188
+ self.assertNotIn(head, [r.probe for r in without_sc.rows])
189
189
  run(d, "git", "bisect", "reset")
190
190
 
191
191
  def test_cells_show_date_and_author_not_subject(self):
@@ -256,12 +256,12 @@ class TestReport(unittest.TestCase):
256
256
  # a finalized sidecar surfaces the real verdict on the in-flight row
257
257
  write(pending=False, outcome="bad")
258
258
  rep = _report.build_report(d, logs_dir=logs)
259
- row = next(r for r in rep.rows if r.midpoint == head)
259
+ row = next(r for r in rep.rows if r.probe == head)
260
260
  self.assertEqual(row.status, "bad")
261
261
  # while still pending, it stays `todo`
262
262
  write(pending=True, outcome="good")
263
263
  rep = _report.build_report(d, logs_dir=logs)
264
- row = next(r for r in rep.rows if r.midpoint == head)
264
+ row = next(r for r in rep.rows if r.probe == head)
265
265
  self.assertEqual(row.status, "todo")
266
266
  run(d, "git", "bisect", "reset")
267
267
 
@@ -280,7 +280,7 @@ class TestReport(unittest.TestCase):
280
280
  rep = _report.build_report(d)
281
281
  finally:
282
282
  _report.is_ancestor = orig
283
- self.assertIn(m1, [r.midpoint for r in rep.rows])
283
+ self.assertIn(m1, [r.probe for r in rep.rows])
284
284
  run(d, "git", "bisect", "reset")
285
285
 
286
286
  def test_render_format(self):
@@ -291,13 +291,13 @@ class TestReport(unittest.TestCase):
291
291
  rep = _report.build_report(d)
292
292
  rep.rows[0].sidecar = _report.Sidecar(
293
293
  fixups=[{"kind": "replace", "path": "f",
294
- "detail": "OLD_VALUE NEW_VALUE"}], steps=[])
294
+ "old": "OLD_VALUE", "new": "NEW_VALUE"}], steps=[])
295
295
  md = _report.render_markdown(rep, details=True)
296
- self.assertIn("| good | bad | midpoint | range | status |", md)
296
+ self.assertIn("| good | bad | probe | range | status |", md)
297
297
  self.assertRegex(md, r"🟢|🔴") # status icons
298
298
  self.assertNotIn("✅", md)
299
299
  self.assertNotIn("→ ", md.split("## Details")[0]) # no dates/arrows in range
300
- self.assertIn("`OLD_VALUE → NEW_VALUE`", md) # full fixup in backticks
300
+ self.assertIn("in `f`: `OLD_VALUE``NEW_VALUE`", md)
301
301
  run(d, "git", "bisect", "reset")
302
302
 
303
303
  def test_no_bisect_returns_none(self):
File without changes
File without changes