devlaunch 0.0.21__tar.gz → 0.0.22__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 (29) hide show
  1. {devlaunch-0.0.21 → devlaunch-0.0.22}/PKG-INFO +9 -3
  2. {devlaunch-0.0.21 → devlaunch-0.0.22}/README.md +8 -2
  3. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/workspace_state.py +32 -3
  4. {devlaunch-0.0.21 → devlaunch-0.0.22}/pyproject.toml +1 -1
  5. {devlaunch-0.0.21 → devlaunch-0.0.22}/.gitignore +0 -0
  6. {devlaunch-0.0.21 → devlaunch-0.0.22}/LICENSE +0 -0
  7. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/__init__.py +0 -0
  8. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/aid.py +0 -0
  9. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/completion.py +0 -0
  10. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/completion_loader.py +0 -0
  11. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/completions/__init__.py +0 -0
  12. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/completions/dl.bash +0 -0
  13. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/devpod_provider.py +0 -0
  14. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/devpod_ssh.py +0 -0
  15. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/dl.py +0 -0
  16. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/gh_auth.py +0 -0
  17. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/tools.py +0 -0
  18. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/tty_session.py +0 -0
  19. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/workspace_id.py +0 -0
  20. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/__init__.py +0 -0
  21. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/branch_manager.py +0 -0
  22. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/config.py +0 -0
  23. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/locks.py +0 -0
  24. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/migration.py +0 -0
  25. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/models.py +0 -0
  26. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/repo_manager.py +0 -0
  27. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/storage.py +0 -0
  28. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/worktree/workspace_clone.py +0 -0
  29. {devlaunch-0.0.21 → devlaunch-0.0.22}/devlaunch/xdg.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devlaunch
3
- Version: 0.0.21
3
+ Version: 0.0.22
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
@@ -427,13 +427,19 @@ cleanup tool must not ignore — `unsaved`:
427
427
  "repo": "blooop/devlaunch",
428
428
  "branch": "wayfinder/devlaunch-80",
429
429
  "state": "Stopped",
430
- "unsaved": "2 uncommitted change(s) and 1 unpushed commit(s)"
430
+ "unsaved": "2 uncommitted change(s) (pixi.lock, notes.md) and 1 unpushed commit(s)"
431
431
  }
432
432
  ```
433
433
 
434
434
  `unsaved` is a description of what deleting would destroy, or `null` when the
435
435
  clone holds nothing that does not also exist on a remote — uncommitted changes
436
- (untracked files included) and commits no remote has. A workspace `dl` did not
436
+ (untracked files included) and commits no remote has. The changed paths are
437
+ named, not just counted, and that matters more than it looks: a devcontainer
438
+ that runs a package install in its `postCreateCommand` can leave a tracked
439
+ lockfile modified in *every* workspace it builds — this repo's own does — and as
440
+ a bare count that is indistinguishable from an hour of unsaved work. A cleanup
441
+ tool believing the count would then never clean anything. Named, it is
442
+ judgeable. A workspace `dl` did not
437
443
  create reports `devlaunch: false` with no repo, branch or `unsaved`: there is no
438
444
  clone of `dl`'s to protect, and it has no business inspecting your checkout.
439
445
 
@@ -405,13 +405,19 @@ cleanup tool must not ignore — `unsaved`:
405
405
  "repo": "blooop/devlaunch",
406
406
  "branch": "wayfinder/devlaunch-80",
407
407
  "state": "Stopped",
408
- "unsaved": "2 uncommitted change(s) and 1 unpushed commit(s)"
408
+ "unsaved": "2 uncommitted change(s) (pixi.lock, notes.md) and 1 unpushed commit(s)"
409
409
  }
410
410
  ```
411
411
 
412
412
  `unsaved` is a description of what deleting would destroy, or `null` when the
413
413
  clone holds nothing that does not also exist on a remote — uncommitted changes
414
- (untracked files included) and commits no remote has. A workspace `dl` did not
414
+ (untracked files included) and commits no remote has. The changed paths are
415
+ named, not just counted, and that matters more than it looks: a devcontainer
416
+ that runs a package install in its `postCreateCommand` can leave a tracked
417
+ lockfile modified in *every* workspace it builds — this repo's own does — and as
418
+ a bare count that is indistinguishable from an hour of unsaved work. A cleanup
419
+ tool believing the count would then never clean anything. Named, it is
420
+ judgeable. A workspace `dl` did not
415
421
  create reports `devlaunch: false` with no repo, branch or `unsaved`: there is no
416
422
  clone of `dl`'s to protect, and it has no business inspecting your checkout.
417
423
 
@@ -56,6 +56,13 @@ def _git(repo: Path, *args: str) -> Optional[str]:
56
56
  never be reported as *safe to delete* on the strength of a failed command —
57
57
  every caller here treats ``None`` as "no information", and the one place
58
58
  that matters (:func:`holds_unsaved_work`) fails safe explicitly.
59
+
60
+ Only trailing newlines are trimmed, never leading whitespace. A full
61
+ ``strip()`` here was wrong in a way that took real use to notice: the first
62
+ line of ``git status --porcelain`` for a *modified tracked* file begins with
63
+ a space (`` M pixi.lock``), so stripping ate the status column and
64
+ :func:`_name_a_few` then reported ``ixi.lock``. Untracked entries start
65
+ ``??`` and were unharmed, which is exactly why the tests missed it.
59
66
  """
60
67
  try:
61
68
  result = subprocess.run(
@@ -67,7 +74,7 @@ def _git(repo: Path, *args: str) -> Optional[str]:
67
74
  if result.returncode != 0:
68
75
  logger.debug(f"git {' '.join(args)} in {repo}: {result.stderr.strip()}")
69
76
  return None
70
- return result.stdout.strip()
77
+ return result.stdout.rstrip("\n")
71
78
 
72
79
 
73
80
  def read_clone(clone: Path) -> CloneState:
@@ -84,6 +91,26 @@ def read_clone(clone: Path) -> CloneState:
84
91
  return CloneState(branch=branch or None, unsaved=_unsaved(clone, branch))
85
92
 
86
93
 
94
+ def _name_a_few(changed: List[str], limit: int = 3) -> str:
95
+ """Name the first few changed paths from `git status --porcelain` lines.
96
+
97
+ A count alone is not enough to decide anything with. A devcontainer that
98
+ runs `pixi install` in its `postCreateCommand` leaves the tracked lockfile
99
+ modified in *every* workspace it builds, so "1 uncommitted change(s)" is the
100
+ permanent state of an otherwise untouched clone — and a person told only the
101
+ count has no way to tell that from an hour of unsaved work. Told the name,
102
+ they can.
103
+
104
+ The porcelain format is two status characters, a space, then the path, so
105
+ the path starts at offset 3; a rename reads `old -> new`, and the whole
106
+ field is kept rather than split, because both halves are the news.
107
+ """
108
+ names = [line[3:].strip() for line in changed[:limit] if len(line) > 3]
109
+ if len(changed) > limit:
110
+ names.append("…")
111
+ return ", ".join(names)
112
+
113
+
87
114
  def _unsaved(clone: Path, branch: Optional[str]) -> Optional[str]:
88
115
  """What deleting *clone* would destroy, in words, or ``None`` if nothing.
89
116
 
@@ -91,7 +118,8 @@ def _unsaved(clone: Path, branch: Optional[str]) -> Optional[str]:
91
118
  force a delete wants both:
92
119
 
93
120
  - a dirty tree, **untracked files included** — an agent's scratch notes are
94
- not less lost for never having been added;
121
+ not less lost for never having been added — with the first few paths
122
+ named, because a count alone cannot be judged (see :func:`_name_a_few`);
95
123
  - commits no remote-tracking ref contains. ``--not --remotes`` asks about
96
124
  *any* remote ref rather than this branch's upstream, so work that was
97
125
  pushed under another name, or merged and fetched back, is correctly not
@@ -100,7 +128,8 @@ def _unsaved(clone: Path, branch: Optional[str]) -> Optional[str]:
100
128
  losses: List[str] = []
101
129
  status = _git(clone, "status", "--porcelain")
102
130
  if status:
103
- losses.append(f"{len(status.splitlines())} uncommitted change(s)")
131
+ changed = status.splitlines()
132
+ losses.append(f"{len(changed)} uncommitted change(s) ({_name_a_few(changed)})")
104
133
  if branch:
105
134
  # Argument order is load-bearing: `--not` flips the sense of every ref
106
135
  # *after* it, so the branch has to be named before it. `log --not
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devlaunch"
3
- version = "0.0.21"
3
+ version = "0.0.22"
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