alissa-tools-github-revloop 0.31.25__tar.gz → 0.31.27__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.27}/PKG-INFO +1 -1
  2. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/janitor.py +108 -24
  3. alissa_tools_github_revloop-0.31.27/src/main/alissa/tools/github/revloop/version +1 -0
  4. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27/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.27}/LICENSE +0 -0
  7. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/MANIFEST.in +0 -0
  8. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/NOTICE +0 -0
  9. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/README.md +0 -0
  10. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/requirements.txt +0 -0
  11. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/setup.cfg +0 -0
  12. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/setup.py +0 -0
  13. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
  14. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/__main__.py +0 -0
  15. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/alissa.py +0 -0
  16. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/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.27}/src/main/alissa/tools/github/revloop/bows.py +0 -0
  18. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/config.py +0 -0
  19. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/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.27}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
  21. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/loop.py +0 -0
  22. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/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.27}/src/main/alissa/tools/github/revloop/parking.py +0 -0
  24. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/proc.py +0 -0
  25. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/prompts.py +0 -0
  26. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
  27. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/state.py +0 -0
  28. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/trust.py +0 -0
  29. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/src/main/alissa/tools/github/revloop/version.py +0 -0
  30. {alissa_tools_github_revloop-0.31.25 → alissa_tools_github_revloop-0.31.27}/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.27}/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.27}/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.27}/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.27}/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.27}/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.27}/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.27}/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.27}/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.27}/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.27}/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.27
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -124,9 +124,32 @@ 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
+ `advanced` is the sweep `now` the walk last read a directory (issue
138
+ #191), 0 until it has. Kept walks resume least-recently-advanced first,
139
+ so two walks that each need several sweeps take turns: the one that just
140
+ had the leftover budget waits behind the one that did not."""
141
+
142
+ mtime: float
143
+ started: float
144
+ total: int
145
+ stack: "list[str]"
146
+ advanced: float = 0.0
147
+
148
+
149
+ # One held folder's cached size, by path: either a finished walk, as
150
+ # (newest mtime, bytes, the sweep `now` the walk began at), or a `HeldWalk`
151
+ # that the time budget cut short (issue #189).
152
+ SizeCache = dict[Path, "tuple[float, int, float] | HeldWalk"]
130
153
 
131
154
  # The two reasons a hub-wide hold gives, so the sweep summary groups them.
132
155
  HUB_HOLD_BOUNDED = "a live round in this hub names no review task and spawned before this checkout"
@@ -274,6 +297,9 @@ class SweepResult:
274
297
  # this sweep, so the summary can say its figure may be up to
275
298
  # SIZE_CACHE_SECONDS old.
276
299
  held_cached: int = 0
300
+ # Issue #189: unsized held folders whose walk is kept to resume next
301
+ # sweep (a subset of `held_unsized`).
302
+ held_resuming: int = 0
277
303
  backstop: bool = False
278
304
  backstop_released: int = 0
279
305
 
@@ -321,6 +347,8 @@ def held_summary(result: SweepResult) -> str:
321
347
  )
322
348
  if result.held_unsized:
323
349
  tail += f"; {result.held_unsized} held folder(s) not sized this sweep"
350
+ if result.held_resuming:
351
+ tail += f" ({result.held_resuming} walk(s) resume next sweep)"
324
352
  return tail
325
353
 
326
354
 
@@ -438,19 +466,31 @@ def dir_bytes(path: "Path | str") -> int:
438
466
  return _walk_bytes(path, None) or 0
439
467
 
440
468
 
441
- def held_bytes_of(path: "Path | str", stop: "Callable[[], bool]") -> "int | None":
469
+ def held_bytes_of(
470
+ path: "Path | str",
471
+ stop: "Callable[[], bool]",
472
+ progress: "HeldWalk | None" = None,
473
+ ) -> "int | None":
442
474
  """`dir_bytes` for a held folder (issue #187): `stop` is asked before
443
475
  each directory is read, and the walk gives up -- None, never a partial
444
476
  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)]
477
+ sweep past its time budget. With `progress` (issue #189) the walk starts
478
+ from its `total` and `stack` rather than from `path`, and an abandoned
479
+ walk leaves its place there, so a later call carries on from it."""
480
+ return _walk_bytes(path, stop, progress)
481
+
482
+
483
+ def _walk_bytes(
484
+ path: "Path | str",
485
+ stop: "Callable[[], bool] | None",
486
+ progress: "HeldWalk | None" = None,
487
+ ) -> "int | None":
488
+ total = progress.total if progress is not None else 0
489
+ stack = progress.stack if progress is not None else [os.fspath(path)]
452
490
  while stack:
453
491
  if stop is not None and stop():
492
+ if progress is not None:
493
+ progress.total = total # `stack` is progress.stack, in place
454
494
  return None
455
495
  current = stack.pop()
456
496
  try:
@@ -600,7 +640,11 @@ def sweep(
600
640
  `held_unsized`. `sizes` (a `SizeCache`), when given, caches those walks
601
641
  across sweeps and is re-keyed to this sweep's held folders; an entry is
602
642
  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`."""
643
+ SIZE_CACHE_SECONDS by `now`, and each reuse is counted in `held_cached`.
644
+ An abandoned walk is kept there too (issue #189): the next sweep sizes
645
+ every other held folder first and then resumes it where it stopped, so
646
+ one large tree neither starves the folders behind it nor restarts from
647
+ scratch every sweep."""
604
648
  result = SweepResult()
605
649
  started = clock()
606
650
  bounded = False
@@ -702,24 +746,64 @@ def _size_held(
702
746
  """Size the sweep's held folders into `result.held_bytes`, after the
703
747
  removals (issue #187). A fresh cache entry costs nothing and is always
704
748
  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."""
749
+ and a walk is abandoned the moment it does.
750
+
751
+ An abandoned walk is kept in `sizes` as a `HeldWalk` (issue #189). Next
752
+ sweep its folder goes to the back of the queue, behind every folder
753
+ whose walk was not cut short, and its walk resumes from where it stopped
754
+ -- from scratch if the folder's mtime moved. So a tree too big for one
755
+ sweep's leftover budget no longer leaves the folders behind it unsized
756
+ on every sweep, and still gets its figure after a few. A finished
757
+ resumed walk is cached as of the sweep it began in, so the hourly
758
+ re-walk counts from the oldest directory it read.
759
+
760
+ Kept walks go least-recently-advanced first, not oldest first (issue
761
+ #191). Otherwise a walk that needs more than SIZE_CACHE_SECONDS of
762
+ sweeps -- whose figure has expired by the sweep after it lands, so it
763
+ starts over -- takes every sweep's leftover budget, and a younger kept
764
+ walk never reads a directory.
765
+
766
+ `sizes` is then re-keyed to this sweep's held folders: entries for gone
767
+ folders drop out, and so do unsized folders whose walk never began."""
768
+ previous = sizes or {}
769
+
770
+ def turn(item: "tuple[float, Candidate, str]") -> "tuple[bool, float]":
771
+ kept = previous.get(item[1].path)
772
+ if isinstance(kept, HeldWalk):
773
+ return True, kept.advanced
774
+ return False, 0.0
775
+
707
776
  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
777
+ # Stable: oldest first among the folders not cut short and among kept
778
+ # walks that last advanced in the same sweep, as the sweep queued them.
779
+ for mtime, candidate, key in sorted(queue, key=turn):
780
+ cached = previous.get(candidate.path)
781
+ if (isinstance(cached, tuple) and cached[0] == mtime
711
782
  and now - cached[2] < SIZE_CACHE_SECONDS):
712
783
  size = cached[1]
713
784
  seen[candidate.path] = cached
714
785
  result.held_cached += 1
786
+ result.held_bytes[key] = result.held_bytes.get(key, 0) + size
787
+ continue
788
+ resumable = isinstance(cached, HeldWalk)
789
+ if isinstance(cached, HeldWalk) and cached.mtime == mtime:
790
+ progress = cached
715
791
  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
792
+ progress = HeldWalk(mtime, now, 0, [os.fspath(candidate.path)],
793
+ cached.advanced if isinstance(cached, HeldWalk) else 0.0)
794
+ walked = None
795
+ if not out_of_time():
796
+ progress.advanced = now
797
+ walked = held_bytes_of(candidate.path, out_of_time, progress)
798
+ resumable = resumable or walked is None
799
+ if walked is None:
800
+ result.held_unsized += 1
801
+ if resumable:
802
+ seen[candidate.path] = progress
803
+ result.held_resuming += 1
804
+ continue
805
+ seen[candidate.path] = (mtime, walked, progress.started)
806
+ result.held_bytes[key] = result.held_bytes.get(key, 0) + walked
723
807
  if sizes is not None:
724
808
  sizes.clear()
725
809
  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.27
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera