devlaunch 0.0.27__tar.gz → 0.0.28__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 (32) hide show
  1. {devlaunch-0.0.27 → devlaunch-0.0.28}/PKG-INFO +1 -1
  2. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/workspace_clone.py +79 -7
  3. {devlaunch-0.0.27 → devlaunch-0.0.28}/pyproject.toml +1 -1
  4. {devlaunch-0.0.27 → devlaunch-0.0.28}/.gitignore +0 -0
  5. {devlaunch-0.0.27 → devlaunch-0.0.28}/LICENSE +0 -0
  6. {devlaunch-0.0.27 → devlaunch-0.0.28}/README.md +0 -0
  7. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/__init__.py +0 -0
  8. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/aid.py +0 -0
  9. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/completion.py +0 -0
  10. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/completion_loader.py +0 -0
  11. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/completions/__init__.py +0 -0
  12. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/completions/dl.bash +0 -0
  13. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/devpod_provider.py +0 -0
  14. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/devpod_ssh.py +0 -0
  15. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/disk_usage.py +0 -0
  16. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/dl.py +0 -0
  17. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/gh_auth.py +0 -0
  18. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/timing.py +0 -0
  19. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/tools.py +0 -0
  20. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/tty_session.py +0 -0
  21. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/workspace_id.py +0 -0
  22. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/workspace_state.py +0 -0
  23. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/__init__.py +0 -0
  24. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/branch_manager.py +0 -0
  25. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/config.py +0 -0
  26. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/git_errors.py +0 -0
  27. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/locks.py +0 -0
  28. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/migration.py +0 -0
  29. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/models.py +0 -0
  30. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/repo_manager.py +0 -0
  31. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/worktree/storage.py +0 -0
  32. {devlaunch-0.0.27 → devlaunch-0.0.28}/devlaunch/xdg.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: devlaunch
3
- Version: 0.0.27
3
+ Version: 0.0.28
4
4
  Summary: DevLaunch - A streamlined CLI for devpod workspaces
5
5
  Project-URL: Source, https://github.com/blooop/devlaunch
6
6
  Project-URL: Home, https://github.com/blooop/devlaunch
@@ -19,8 +19,9 @@ import logging
19
19
  import os
20
20
  import shutil
21
21
  import subprocess
22
+ from dataclasses import dataclass
22
23
  from pathlib import Path
23
- from typing import Optional
24
+ from typing import Optional, Union
24
25
 
25
26
  from .. import timing
26
27
  from ..workspace_id import WorkspaceId, validate_ref_name
@@ -44,6 +45,42 @@ _LFS_POINTER_PREFIX = b"version https://git-lfs"
44
45
  logger = logging.getLogger(__name__)
45
46
 
46
47
 
48
+ @dataclass(frozen=True)
49
+ class FreshBase:
50
+ """The ref this launch runs on was fetched from the remote this call.
51
+
52
+ Either the requested branch itself was refreshed, or the branch is new and
53
+ the base it was cut from was. Carries no payload: "your tip is current" is
54
+ the whole message.
55
+ """
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class StaleBase:
60
+ """The launch proceeds on a ref nothing refreshed this call.
61
+
62
+ *base* names that ref — the default branch a new branch was cut from, the
63
+ cache's own ``HEAD`` when no default branch could even be named, or the
64
+ requested branch itself when its fetch failed. *reason* is why nothing
65
+ refreshed it, carried rather than reconstructed at the print site for the
66
+ same reason :class:`devlaunch.worktree.repo_manager.FetchFailed` carries
67
+ its own.
68
+
69
+ Deliberately not an error (devlaunch#144: launch-from-cache is the offline
70
+ contract) and deliberately not only a log line (devlaunch#245: the caller —
71
+ and wf, reading dl's output — must be able to tell a fresh base from a
72
+ stale one).
73
+ """
74
+
75
+ base: str
76
+ reason: str
77
+
78
+
79
+ # Two arms and no third: every path through ensure_branch answers one of these,
80
+ # so "the fetch quietly did not back the base" cannot exist as an unnamed state.
81
+ BranchBase = Union[FreshBase, StaleBase]
82
+
83
+
47
84
  def _on_disk(path: Path) -> Optional[bool]:
48
85
  """Whether *path* is there: True, False, or None for "could not look".
49
86
 
@@ -460,7 +497,17 @@ class WorkspaceCloneManager:
460
497
  workspace = WorkspaceId(owner, repo, branch)
461
498
  with self.repo_manager.hold_repo_lock(owner, repo) as lock:
462
499
  self.repo_manager.clone_if_missing(lock, owner, repo, remote_url)
463
- self.ensure_branch(lock, owner, repo, branch)
500
+ base = self.ensure_branch(lock, owner, repo, branch)
501
+ if isinstance(base, StaleBase):
502
+ # The one consequence-stating line for the whole degraded
503
+ # family, in dl's own output because wf reads that output: the
504
+ # fetch-level warnings above it say what failed, this says what
505
+ # it means for the tree the agent is about to work on.
506
+ logger.warning(
507
+ f"Prepared '{owner}/{repo}@{branch}' from the cache's '{base.base}', "
508
+ f"which could not be refreshed ({base.reason}); "
509
+ f"it may be behind the remote."
510
+ )
464
511
  return self._prepare_workspace(
465
512
  lock,
466
513
  workspace,
@@ -469,9 +516,15 @@ class WorkspaceCloneManager:
469
516
  remote_url,
470
517
  )
471
518
 
472
- def ensure_branch(self, lock: RepoLock, owner: str, repo: str, branch: str) -> None:
519
+ def ensure_branch(self, lock: RepoLock, owner: str, repo: str, branch: str) -> BranchBase:
473
520
  """Ensure a branch exists in the bare repo, at the remote's current tip.
474
521
 
522
+ Returns which of those two things it actually delivered: a
523
+ :class:`FreshBase` when the tip the launch runs on was fetched this
524
+ call, a :class:`StaleBase` naming the unrefreshed ref and the reason
525
+ when it was not (devlaunch#245). The degraded arms all proceed — that
526
+ is the devlaunch#144 contract — but they no longer proceed silently.
527
+
475
528
  This is the whole of the launch path's network use, and the staleness
476
529
  contract devlaunch#144 settled is stated here because this is the code
477
530
  that keeps it:
@@ -505,14 +558,17 @@ class WorkspaceCloneManager:
505
558
 
506
559
  try:
507
560
  default_branch = self.repo_manager.get_default_branch(owner, repo)
561
+ default_branch_error = None
508
562
  except (RuntimeError, subprocess.CalledProcessError, OSError) as e:
509
563
  logger.warning(f"Failed to resolve default branch: {e}")
510
564
  default_branch = None
565
+ default_branch_error = str(e)
511
566
 
512
567
  # Each arm named, so a fourth one cannot be silently read as one of
513
568
  # these three.
569
+ base: BranchBase
514
570
  if isinstance(outcome, Updated):
515
- pass
571
+ base = FreshBase()
516
572
  elif isinstance(outcome, RefMissingOnRemote):
517
573
  # The remote answered, so the branch really is new: base it on the
518
574
  # default branch, and fetch *that* so it is based on something
@@ -529,18 +585,23 @@ class WorkspaceCloneManager:
529
585
  # dl's launch path guards it with (RuntimeError, OSError),
530
586
  # so a ValueError here would surface as a traceback.
531
587
  logger.warning(f"Cannot fetch recorded default branch: {e}")
588
+ base = StaleBase(base=default_branch, reason=str(e))
532
589
  else:
533
590
  # Named arm by arm for the same reason as the dispatch
534
591
  # around it: the sum exists so a fourth outcome cannot be
535
592
  # quietly read as one of these three.
536
593
  if isinstance(base_outcome, Updated):
537
- pass
594
+ base = FreshBase()
538
595
  elif isinstance(base_outcome, RefMissingOnRemote):
539
596
  # The remote has no default branch under that name
540
597
  # either. Nothing more to try -- there is no third ref
541
598
  # -- and the branch creation below still has the cache's
542
- # own default branch to start from.
543
- pass
599
+ # own default branch to start from. Unverifiable is
600
+ # stale here: nothing fetched backs it.
601
+ base = StaleBase(
602
+ base=default_branch,
603
+ reason=f"the remote has no branch '{default_branch}' to refresh from",
604
+ )
544
605
  elif isinstance(base_outcome, FetchFailed):
545
606
  # Same condition as the arm below, warned the same
546
607
  # way: the new branch is about to be cut from a
@@ -550,13 +611,23 @@ class WorkspaceCloneManager:
550
611
  f"Could not fetch {default_branch} for {owner}/{repo}: "
551
612
  f"{base_outcome.reason}"
552
613
  )
614
+ base = StaleBase(base=default_branch, reason=base_outcome.reason)
553
615
  else:
554
616
  unhandled_fetch_outcome(base_outcome)
617
+ else:
618
+ # No default branch could even be named, so nothing was
619
+ # fetched and the creation below starts from the bare cache's
620
+ # own HEAD -- a ref of unbounded age, and the report says so.
621
+ base = StaleBase(
622
+ base="HEAD",
623
+ reason=default_branch_error or "no default branch is recorded",
624
+ )
555
625
  elif isinstance(outcome, FetchFailed):
556
626
  # Not an error here: a cached branch still launches. Deliberately
557
627
  # no default-branch fetch -- nothing was learned about the remote,
558
628
  # so nothing licenses treating this branch as new.
559
629
  logger.warning(f"Could not fetch {branch} for {owner}/{repo}: {outcome.reason}")
630
+ base = StaleBase(base=branch, reason=outcome.reason)
560
631
  else:
561
632
  unhandled_fetch_outcome(outcome)
562
633
 
@@ -567,6 +638,7 @@ class WorkspaceCloneManager:
567
638
  start_point=default_branch or "HEAD",
568
639
  use_local_refs=True,
569
640
  )
641
+ return base
570
642
 
571
643
  def _prepare_workspace(
572
644
  self,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devlaunch"
3
- version = "0.0.27"
3
+ version = "0.0.28"
4
4
  authors = [{ name = "Austin Gregg-Smith", email = "blooop@gmail.com" }]
5
5
  description = "DevLaunch - A streamlined CLI for devpod workspaces"
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes