alissa-tools-github-revloop 0.31.25__tar.gz → 0.31.26__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.
Files changed (40) hide show
  1. {alissa_tools_github_revloop-0.31.25/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.31.26}/PKG-INFO +1 -1
  2. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/janitor.py +90 -24
  3. alissa_tools_github_revloop-0.31.26/src/main/alissa/tools/github/revloop/version +1 -0
  4. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
  5. alissa_tools_github_revloop-0.31.25/src/main/alissa/tools/github/revloop/version +0 -1
  6. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/LICENSE +0 -0
  7. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/MANIFEST.in +0 -0
  8. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/NOTICE +0 -0
  9. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/README.md +0 -0
  10. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/requirements.txt +0 -0
  11. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/setup.cfg +0 -0
  12. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/setup.py +0 -0
  13. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
  14. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/__main__.py +0 -0
  15. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/alissa.py +0 -0
  16. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/alissa_client.py +0 -0
  17. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/bows.py +0 -0
  18. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/config.py +0 -0
  19. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/fleet_vitals.py +0 -0
  20. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
  21. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/loop.py +0 -0
  22. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/loop_events.py +0 -0
  23. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/parking.py +0 -0
  24. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/proc.py +0 -0
  25. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/prompts.py +0 -0
  26. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
  27. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/state.py +0 -0
  28. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/trust.py +0 -0
  29. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/version.py +0 -0
  30. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/webui/__init__.py +0 -0
  31. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
  32. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
  33. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/webui/page.py +0 -0
  34. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/webui/server.py +0 -0
  35. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/webui/sources.py +0 -0
  36. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +0 -0
  37. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
  38. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
  39. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
  40. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.26}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-revloop
3
- Version: 0.31.25
3
+ Version: 0.31.26
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -124,9 +124,26 @@ BACKSTOP_PCT = 95.0
124
124
  # or not.
125
125
  SIZE_CACHE_SECONDS = 60 * 60
126
126
 
127
- # One held folder's cached size: (newest mtime, bytes, the sweep `now` it
128
- # was walked at).
129
- SizeCache = dict[Path, tuple[float, int, float]]
127
+
128
+ @dataclass
129
+ class HeldWalk:
130
+ """A held folder's size walk that ran out of budget (issue #189), kept
131
+ so the next sweep resumes it instead of starting over: the folder's
132
+ newest mtime when the walk began, the sweep `now` it began at, the bytes
133
+ counted so far, and the directories still to read. A resumed walk's
134
+ total may count a directory removed since it was read; it never counts a
135
+ directory twice."""
136
+
137
+ mtime: float
138
+ started: float
139
+ total: int
140
+ stack: "list[str]"
141
+
142
+
143
+ # One held folder's cached size, by path: either a finished walk, as
144
+ # (newest mtime, bytes, the sweep `now` the walk began at), or a `HeldWalk`
145
+ # that the time budget cut short (issue #189).
146
+ SizeCache = dict[Path, "tuple[float, int, float] | HeldWalk"]
130
147
 
131
148
  # The two reasons a hub-wide hold gives, so the sweep summary groups them.
132
149
  HUB_HOLD_BOUNDED = "a live round in this hub names no review task and spawned before this checkout"
@@ -274,6 +291,9 @@ class SweepResult:
274
291
  # this sweep, so the summary can say its figure may be up to
275
292
  # SIZE_CACHE_SECONDS old.
276
293
  held_cached: int = 0
294
+ # Issue #189: unsized held folders whose walk is kept to resume next
295
+ # sweep (a subset of `held_unsized`).
296
+ held_resuming: int = 0
277
297
  backstop: bool = False
278
298
  backstop_released: int = 0
279
299
 
@@ -321,6 +341,8 @@ def held_summary(result: SweepResult) -> str:
321
341
  )
322
342
  if result.held_unsized:
323
343
  tail += f"; {result.held_unsized} held folder(s) not sized this sweep"
344
+ if result.held_resuming:
345
+ tail += f" ({result.held_resuming} walk(s) resume next sweep)"
324
346
  return tail
325
347
 
326
348
 
@@ -438,19 +460,31 @@ def dir_bytes(path: "Path | str") -> int:
438
460
  return _walk_bytes(path, None) or 0
439
461
 
440
462
 
441
- def held_bytes_of(path: "Path | str", stop: "Callable[[], bool]") -> "int | None":
463
+ def held_bytes_of(
464
+ path: "Path | str",
465
+ stop: "Callable[[], bool]",
466
+ progress: "HeldWalk | None" = None,
467
+ ) -> "int | None":
442
468
  """`dir_bytes` for a held folder (issue #187): `stop` is asked before
443
469
  each directory is read, and the walk gives up -- None, never a partial
444
470
  figure -- the moment it answers True, so one large tree cannot run the
445
- sweep past its time budget."""
446
- return _walk_bytes(path, stop)
447
-
448
-
449
- def _walk_bytes(path: "Path | str", stop: "Callable[[], bool] | None") -> "int | None":
450
- total = 0
451
- stack = [os.fspath(path)]
471
+ sweep past its time budget. With `progress` (issue #189) the walk starts
472
+ from its `total` and `stack` rather than from `path`, and an abandoned
473
+ walk leaves its place there, so a later call carries on from it."""
474
+ return _walk_bytes(path, stop, progress)
475
+
476
+
477
+ def _walk_bytes(
478
+ path: "Path | str",
479
+ stop: "Callable[[], bool] | None",
480
+ progress: "HeldWalk | None" = None,
481
+ ) -> "int | None":
482
+ total = progress.total if progress is not None else 0
483
+ stack = progress.stack if progress is not None else [os.fspath(path)]
452
484
  while stack:
453
485
  if stop is not None and stop():
486
+ if progress is not None:
487
+ progress.total = total # `stack` is progress.stack, in place
454
488
  return None
455
489
  current = stack.pop()
456
490
  try:
@@ -600,7 +634,11 @@ def sweep(
600
634
  `held_unsized`. `sizes` (a `SizeCache`), when given, caches those walks
601
635
  across sweeps and is re-keyed to this sweep's held folders; an entry is
602
636
  reused while the folder's mtime is unchanged AND it is younger than
603
- SIZE_CACHE_SECONDS by `now`, and each reuse is counted in `held_cached`."""
637
+ SIZE_CACHE_SECONDS by `now`, and each reuse is counted in `held_cached`.
638
+ An abandoned walk is kept there too (issue #189): the next sweep sizes
639
+ every other held folder first and then resumes it where it stopped, so
640
+ one large tree neither starves the folders behind it nor restarts from
641
+ scratch every sweep."""
604
642
  result = SweepResult()
605
643
  started = clock()
606
644
  bounded = False
@@ -702,24 +740,52 @@ def _size_held(
702
740
  """Size the sweep's held folders into `result.held_bytes`, after the
703
741
  removals (issue #187). A fresh cache entry costs nothing and is always
704
742
  used; anything else is walked only while `out_of_time()` answers False,
705
- and a walk is abandoned the moment it does. `sizes` is then re-keyed to
706
- what this sweep sized, so entries for gone or unsized folders drop out."""
743
+ and a walk is abandoned the moment it does.
744
+
745
+ An abandoned walk is kept in `sizes` as a `HeldWalk` (issue #189). Next
746
+ sweep its folder goes to the back of the queue, behind every folder
747
+ whose walk was not cut short, and its walk resumes from where it stopped
748
+ -- from scratch if the folder's mtime moved. So a tree too big for one
749
+ sweep's leftover budget no longer leaves the folders behind it unsized
750
+ on every sweep, and still gets its figure after a few. A finished
751
+ resumed walk is cached as of the sweep it began in, so the hourly
752
+ re-walk counts from the oldest directory it read.
753
+
754
+ `sizes` is then re-keyed to this sweep's held folders: entries for gone
755
+ folders drop out, and so do unsized folders whose walk never began."""
756
+ previous = sizes or {}
757
+
758
+ def cut_short(item: "tuple[float, Candidate, str]") -> bool:
759
+ return isinstance(previous.get(item[1].path), HeldWalk)
760
+
707
761
  seen: SizeCache = {}
708
- for mtime, candidate, key in queue:
709
- cached = (sizes or {}).get(candidate.path)
710
- if (cached is not None and cached[0] == mtime
762
+ # Stable: oldest first within each group, as the sweep queued them.
763
+ for mtime, candidate, key in sorted(queue, key=cut_short):
764
+ cached = previous.get(candidate.path)
765
+ if (isinstance(cached, tuple) and cached[0] == mtime
711
766
  and now - cached[2] < SIZE_CACHE_SECONDS):
712
767
  size = cached[1]
713
768
  seen[candidate.path] = cached
714
769
  result.held_cached += 1
770
+ result.held_bytes[key] = result.held_bytes.get(key, 0) + size
771
+ continue
772
+ resumable = isinstance(cached, HeldWalk)
773
+ if isinstance(cached, HeldWalk) and cached.mtime == mtime:
774
+ progress = cached
715
775
  else:
716
- walked = None if out_of_time() else held_bytes_of(candidate.path, out_of_time)
717
- if walked is None:
718
- result.held_unsized += 1
719
- continue
720
- size = walked
721
- seen[candidate.path] = (mtime, size, now)
722
- result.held_bytes[key] = result.held_bytes.get(key, 0) + size
776
+ progress = HeldWalk(mtime, now, 0, [os.fspath(candidate.path)])
777
+ walked = None
778
+ if not out_of_time():
779
+ walked = held_bytes_of(candidate.path, out_of_time, progress)
780
+ resumable = resumable or walked is None
781
+ if walked is None:
782
+ result.held_unsized += 1
783
+ if resumable:
784
+ seen[candidate.path] = progress
785
+ result.held_resuming += 1
786
+ continue
787
+ seen[candidate.path] = (mtime, walked, progress.started)
788
+ result.held_bytes[key] = result.held_bytes.get(key, 0) + walked
723
789
  if sizes is not None:
724
790
  sizes.clear()
725
791
  sizes.update(seen)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-revloop
3
- Version: 0.31.25
3
+ Version: 0.31.26
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera