workforest 0.2.2__tar.gz → 0.2.3__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 (47) hide show
  1. {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/publish_aur.yml +4 -25
  2. {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/publish_homebrew.yml +19 -34
  3. {workforest-0.2.2 → workforest-0.2.3}/.gitignore +4 -1
  4. {workforest-0.2.2 → workforest-0.2.3}/PKG-INFO +11 -8
  5. {workforest-0.2.2 → workforest-0.2.3}/README.md +10 -7
  6. workforest-0.2.2/packaging/AUR/PKGBUILD → workforest-0.2.3/packaging/AUR/PKGBUILD.template +6 -2
  7. workforest-0.2.2/packaging/homebrew/workforest.rb → workforest-0.2.3/packaging/homebrew/workforest.rb.template +7 -5
  8. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/__init__.py +1 -1
  9. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/launch.py +44 -2
  10. {workforest-0.2.2 → workforest-0.2.3}/tests/conftest.py +2 -1
  11. {workforest-0.2.2 → workforest-0.2.3}/tests/test_launch.py +66 -0
  12. workforest-0.2.2/packaging/AUR/.SRCINFO +0 -19
  13. {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/ci.yml +0 -0
  14. {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/publish_pypi.yml +0 -0
  15. {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/release.yml +0 -0
  16. {workforest-0.2.2 → workforest-0.2.3}/LICENSE +0 -0
  17. {workforest-0.2.2 → workforest-0.2.3}/Makefile +0 -0
  18. {workforest-0.2.2 → workforest-0.2.3}/completions/_workforest +0 -0
  19. {workforest-0.2.2 → workforest-0.2.3}/pyproject.toml +0 -0
  20. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/__main__.py +0 -0
  21. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/cli.py +0 -0
  22. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/commands.py +0 -0
  23. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/completions.py +0 -0
  24. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/config.py +0 -0
  25. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/errors.py +0 -0
  26. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/examples/config.yaml +0 -0
  27. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/gitutil.py +0 -0
  28. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/hooks.py +0 -0
  29. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/integrations/__init__.py +0 -0
  30. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/integrations/claude.py +0 -0
  31. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/output.py +0 -0
  32. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shell/completion.bash +0 -0
  33. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shell/completion.zsh +0 -0
  34. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shell/workforest.sh +0 -0
  35. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shellinit.py +0 -0
  36. {workforest-0.2.2 → workforest-0.2.3}/src/workforest/tui.py +0 -0
  37. {workforest-0.2.2 → workforest-0.2.3}/tests/__init__.py +0 -0
  38. {workforest-0.2.2 → workforest-0.2.3}/tests/test_claude.py +0 -0
  39. {workforest-0.2.2 → workforest-0.2.3}/tests/test_cli.py +0 -0
  40. {workforest-0.2.2 → workforest-0.2.3}/tests/test_commands.py +0 -0
  41. {workforest-0.2.2 → workforest-0.2.3}/tests/test_config.py +0 -0
  42. {workforest-0.2.2 → workforest-0.2.3}/tests/test_gitutil.py +0 -0
  43. {workforest-0.2.2 → workforest-0.2.3}/tests/test_harness.py +0 -0
  44. {workforest-0.2.2 → workforest-0.2.3}/tests/test_hooks.py +0 -0
  45. {workforest-0.2.2 → workforest-0.2.3}/tests/test_shell.py +0 -0
  46. {workforest-0.2.2 → workforest-0.2.3}/tests/test_tui.py +0 -0
  47. {workforest-0.2.2 → workforest-0.2.3}/uv.lock +0 -0
@@ -12,7 +12,7 @@ jobs:
12
12
  name: aur
13
13
  url: https://aur.archlinux.org/packages/workforest
14
14
  permissions:
15
- contents: write # commit the generated PKGBUILD back to main
15
+ contents: read
16
16
  steps:
17
17
  - name: Install system dependencies
18
18
  run: >
@@ -21,15 +21,15 @@ jobs:
21
21
 
22
22
  - uses: actions/checkout@v4
23
23
 
24
- - name: Update PKGBUILD to released version
24
+ - name: Render PKGBUILD from template
25
25
  run: |
26
26
  version="${GITHUB_REF_NAME#v}"
27
27
  cd packaging/AUR
28
- sed -i "s/^pkgver=.*/pkgver=$version/" PKGBUILD
29
- sed -i "s/^pkgrel=.*/pkgrel=1/" PKGBUILD
28
+ sed "s/@VERSION@/$version/g" PKGBUILD.template > PKGBUILD
30
29
  # makepkg refuses to run as root
31
30
  useradd -m builder
32
31
  chown -R builder .
32
+ # updpkgsums replaces the @SHA256@ placeholder with the real checksum
33
33
  runuser -u builder -- updpkgsums
34
34
  runuser -u builder -- sh -c 'makepkg --printsrcinfo > .SRCINFO'
35
35
 
@@ -70,24 +70,3 @@ jobs:
70
70
  git commit -m "Update to $version"
71
71
  git push origin HEAD:master
72
72
  fi
73
-
74
- - name: Commit generated PKGBUILD back to main
75
- run: |
76
- version="${GITHUB_REF_NAME#v}"
77
- cp packaging/AUR/PKGBUILD packaging/AUR/.SRCINFO /tmp/
78
- # The workspace is created by the host runner under a different UID
79
- # than the container's root, so git rejects it without this.
80
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
81
- # The workflow runs detached at the release tag; move to main to commit.
82
- # -f discards the in-place edits we just copied to /tmp; build
83
- # artifacts in packaging/AUR are untracked and never git-added.
84
- git fetch origin main
85
- git checkout -f -B main origin/main
86
- cp /tmp/PKGBUILD /tmp/.SRCINFO packaging/AUR/
87
- git add packaging/AUR/PKGBUILD packaging/AUR/.SRCINFO
88
- if git diff --cached --quiet; then
89
- echo "Repo copy already up to date."
90
- else
91
- git commit -m "Update PKGBUILD to $version"
92
- git push origin main
93
- fi
@@ -3,32 +3,43 @@ name: Publish to Homebrew tap
3
3
  on:
4
4
  release:
5
5
  types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ version:
9
+ description: "Release version to publish (e.g. 0.2.1)"
10
+ required: true
6
11
 
7
12
  jobs:
8
13
  publish:
9
14
  runs-on: macos-latest
15
+ env:
16
+ RELEASE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
10
17
  environment:
11
18
  name: homebrew-tap
12
19
  url: https://github.com/ArkadyBuryakov/homebrew-tap
13
20
  permissions:
14
- contents: write # commit the generated formula back to main
21
+ contents: read
15
22
  steps:
16
23
  - uses: actions/checkout@v4
17
24
 
18
- - name: Update formula to released version
25
+ - name: Render formula from template
19
26
  run: |
20
- version="${GITHUB_REF_NAME#v}"
27
+ version="${RELEASE_VERSION#v}"
21
28
  url="https://github.com/ArkadyBuryakov/workforest/archive/v$version.tar.gz"
22
29
  curl -fsSL "$url" -o /tmp/workforest.tar.gz
23
30
  sha256=$(shasum -a 256 /tmp/workforest.tar.gz | cut -d' ' -f1)
24
31
  cd packaging/homebrew
25
- sed -i '' "s|^ url .*| url \"$url\"|" workforest.rb
26
- sed -i '' "s|^ sha256 .*| sha256 \"$sha256\"|" workforest.rb
32
+ sed -e "s/@VERSION@/$version/g" -e "s/@SHA256@/$sha256/g" \
33
+ workforest.rb.template > workforest.rb
27
34
 
28
35
  - name: Build, test, and smoke test
29
36
  run: |
30
- brew install --build-from-source --formula packaging/homebrew/workforest.rb
31
- brew test --formula packaging/homebrew/workforest.rb
37
+ # Homebrew >= 4.6.4 refuses to install from a bare file path —
38
+ # formulas must live in a tap, so stage ours in a throwaway one.
39
+ brew tap-new --no-git arkadyburyakov/test
40
+ cp packaging/homebrew/workforest.rb "$(brew --repository arkadyburyakov/test)/Formula/"
41
+ brew install --build-from-source arkadyburyakov/test/workforest
42
+ brew test arkadyburyakov/test/workforest
32
43
  workforest --version
33
44
  wf --version
34
45
  workforest shell-init bash | bash -n
@@ -38,7 +49,7 @@ jobs:
38
49
  env:
39
50
  TAP_SSH_PRIVATE_KEY: ${{ secrets.TAP_SSH_PRIVATE_KEY }}
40
51
  run: |
41
- version="${GITHUB_REF_NAME#v}"
52
+ version="${RELEASE_VERSION#v}"
42
53
  # Host keys are pinned rather than keyscanned (same policy as the
43
54
  # AUR workflow); these are GitHub's published SSH fingerprints.
44
55
  mkdir -p /tmp/ssh
@@ -63,29 +74,3 @@ jobs:
63
74
  git commit -m "workforest $version"
64
75
  git push origin main
65
76
  fi
66
-
67
- - name: Commit generated formula back to main
68
- run: |
69
- version="${GITHUB_REF_NAME#v}"
70
- cp packaging/homebrew/workforest.rb /tmp/workforest.rb
71
- git config --global user.name "Arkady Buryakov"
72
- git config --global user.email "arkady@buryakov.pro"
73
- # The workflow runs detached at the release tag; move to main to
74
- # commit. Retry because the AUR publisher pushes to main from the
75
- # same release event and the two can race.
76
- for attempt in 1 2 3; do
77
- git fetch origin main
78
- git checkout -f -B main origin/main
79
- cp /tmp/workforest.rb packaging/homebrew/workforest.rb
80
- git add packaging/homebrew/workforest.rb
81
- if git diff --cached --quiet; then
82
- echo "Repo copy already up to date."
83
- break
84
- fi
85
- git commit -m "Update Homebrew formula to $version"
86
- if git push origin main; then
87
- break
88
- fi
89
- echo "Push raced with another workflow, retrying ($attempt)..."
90
- sleep 10
91
- done
@@ -13,9 +13,12 @@ build/
13
13
  .coverage
14
14
  htmlcov/
15
15
 
16
- # Packaging artifacts
16
+ # Packaging artifacts (rendered from *.template at release time)
17
17
  *.pkg.tar.zst
18
18
  pkg/
19
+ packaging/AUR/PKGBUILD
20
+ packaging/AUR/.SRCINFO
21
+ packaging/homebrew/workforest.rb
19
22
 
20
23
  # Tooling config
21
24
  .workforest.yaml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: workforest
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Git worktree forest management: create, open, and clean up per-branch worktrees with project-defined setup hooks
5
5
  Project-URL: Homepage, https://github.com/ArkadyBuryakov/workforest
6
6
  Author-email: Arkady Buryakov <arkady@buryakov.pro>
@@ -136,7 +136,9 @@ word-splits into multiple arguments, while `{x}` — `{worktree}`, `{target}`,
136
136
  Openers run with the worktree root as working directory; in
137
137
  `window_command` the resolved opener command is additionally available as
138
138
  `$WF_COMMAND` (spliced into argv words) or `{command}` (one argument, for
139
- `$SHELL -c` wrappers).
139
+ `$SHELL -c` wrappers). Spawned windows shed activation state inherited from
140
+ the invoking shell (Python venv, conda, nvm, rvm) so the new session starts
141
+ clean instead of carrying an environment it cannot deactivate.
140
142
 
141
143
  Fully commented reference configs:
142
144
  [`config.yaml`](src/workforest/examples/config.yaml) (user/system) and
@@ -200,12 +202,13 @@ make install # install this checkout as a uv tool (~/.local/bin/workforest)
200
202
  make uninstall # remove it again
201
203
  ```
202
204
 
203
- Packaging recipes live under `packaging/` (one directory per package
204
- manager: `packaging/AUR/`, `packaging/homebrew/`).
205
- Release: bump `__version__` and push to main — CI tags the release and
206
- publishes to PyPI, the AUR, and the
207
- [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap), committing
208
- the regenerated recipes back to the repo.
205
+ Packaging templates live under `packaging/` (one directory per package
206
+ manager: `packaging/AUR/`, `packaging/homebrew/`); the `@VERSION@` and
207
+ `@SHA256@` placeholders are filled in at release time.
208
+ Release: bump `__version__` and push to main — CI tags the release,
209
+ renders the templates, and publishes to PyPI, the AUR, and the
210
+ [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap). The
211
+ published AUR package and tap are the only places rendered recipes exist.
209
212
 
210
213
  ## License
211
214
 
@@ -116,7 +116,9 @@ word-splits into multiple arguments, while `{x}` — `{worktree}`, `{target}`,
116
116
  Openers run with the worktree root as working directory; in
117
117
  `window_command` the resolved opener command is additionally available as
118
118
  `$WF_COMMAND` (spliced into argv words) or `{command}` (one argument, for
119
- `$SHELL -c` wrappers).
119
+ `$SHELL -c` wrappers). Spawned windows shed activation state inherited from
120
+ the invoking shell (Python venv, conda, nvm, rvm) so the new session starts
121
+ clean instead of carrying an environment it cannot deactivate.
120
122
 
121
123
  Fully commented reference configs:
122
124
  [`config.yaml`](src/workforest/examples/config.yaml) (user/system) and
@@ -180,12 +182,13 @@ make install # install this checkout as a uv tool (~/.local/bin/workforest)
180
182
  make uninstall # remove it again
181
183
  ```
182
184
 
183
- Packaging recipes live under `packaging/` (one directory per package
184
- manager: `packaging/AUR/`, `packaging/homebrew/`).
185
- Release: bump `__version__` and push to main — CI tags the release and
186
- publishes to PyPI, the AUR, and the
187
- [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap), committing
188
- the regenerated recipes back to the repo.
185
+ Packaging templates live under `packaging/` (one directory per package
186
+ manager: `packaging/AUR/`, `packaging/homebrew/`); the `@VERSION@` and
187
+ `@SHA256@` placeholders are filled in at release time.
188
+ Release: bump `__version__` and push to main — CI tags the release,
189
+ renders the templates, and publishes to PyPI, the AUR, and the
190
+ [Homebrew tap](https://github.com/ArkadyBuryakov/homebrew-tap). The
191
+ published AUR package and tap are the only places rendered recipes exist.
189
192
 
190
193
  ## License
191
194
 
@@ -1,6 +1,10 @@
1
1
  # Maintainer: Arkady Buryakov <arkady@buryakov.pro>
2
+ #
3
+ # Template — not a buildable PKGBUILD. On release the publish_aur workflow
4
+ # substitutes @VERSION@, fills sha256sums via updpkgsums, generates .SRCINFO,
5
+ # and pushes the rendered files to the AUR; nothing is committed back here.
2
6
  pkgname=workforest
3
- pkgver=0.2.1
7
+ pkgver=@VERSION@
4
8
  pkgrel=1
5
9
  pkgdesc="Git worktree forest management: per-branch worktrees with project-defined setup hooks"
6
10
  arch=(any)
@@ -10,7 +14,7 @@ depends=(python python-yaml git)
10
14
  makedepends=(python-build python-installer python-wheel python-hatchling)
11
15
  optdepends=('fzf: interactive TUI (workforest tui)')
12
16
  source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
13
- sha256sums=('47062e51ae374a9afce50ee0be6c39b08da6c96064450b3c2c58566d05fb7078')
17
+ sha256sums=('@SHA256@')
14
18
 
15
19
  build() {
16
20
  cd "$pkgname-$pkgver"
@@ -1,13 +1,15 @@
1
- # The publish_homebrew workflow rewrites url/sha256 on release and pushes
2
- # this file to the ArkadyBuryakov/homebrew-tap repo; this copy is the source
3
- # of truth for everything else (deps, completions, caveats, test).
1
+ # Template — not an installable formula. On release the publish_homebrew
2
+ # workflow substitutes @VERSION@ and @SHA256@ and pushes the rendered
3
+ # Formula/workforest.rb to the ArkadyBuryakov/homebrew-tap repo; nothing is
4
+ # committed back here. This copy is the source of truth for everything else
5
+ # (deps, completions, caveats, test).
4
6
  class Workforest < Formula
5
7
  include Language::Python::Virtualenv
6
8
 
7
9
  desc "Git worktree forest management with per-branch setup hooks"
8
10
  homepage "https://github.com/ArkadyBuryakov/workforest"
9
- url "https://github.com/ArkadyBuryakov/workforest/archive/v0.2.1.tar.gz"
10
- sha256 "47062e51ae374a9afce50ee0be6c39b08da6c96064450b3c2c58566d05fb7078"
11
+ url "https://github.com/ArkadyBuryakov/workforest/archive/v@VERSION@.tar.gz"
12
+ sha256 "@SHA256@"
11
13
  license "MIT"
12
14
  head "https://github.com/ArkadyBuryakov/workforest.git", branch: "main"
13
15
 
@@ -1,3 +1,3 @@
1
1
  """Workforest — git worktree forest management."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.3"
@@ -26,6 +26,19 @@ from workforest.errors import WorkforestError
26
26
 
27
27
  _PLACEHOLDER = re.compile(r"\{([a-z_]+)\}")
28
28
 
29
+ # Shell-session activation state that must not leak into a spawned window:
30
+ # (prefix variable, bin subdirs under it that activation put on PATH,
31
+ # companion variables set alongside it). An empty subdir means the prefix
32
+ # variable is itself the PATH entry.
33
+ _ACTIVATION_STATE: tuple[tuple[str, tuple[str, ...], tuple[str, ...]], ...] = (
34
+ ("VIRTUAL_ENV", ("bin", "Scripts"), ("VIRTUAL_ENV_PROMPT",)),
35
+ ("CONDA_PREFIX", ("bin",), ("CONDA_DEFAULT_ENV", "CONDA_PROMPT_MODIFIER", "CONDA_SHLVL")),
36
+ ("NVM_BIN", ("",), ("NVM_INC",)),
37
+ ("GEM_HOME", ("bin",), ("GEM_PATH",)),
38
+ ("MY_RUBY_HOME", ("bin",), ("RUBY_VERSION",)),
39
+ )
40
+ _CONDA_STACK = re.compile(r"CONDA_PREFIX_\d+")
41
+
29
42
 
30
43
  @dataclass(slots=True, frozen=True)
31
44
  class ShellAction:
@@ -131,6 +144,34 @@ def launch(
131
144
  return ShellAction(f"cd {shlex.quote(str(worktree))} && {assignments} {command}")
132
145
 
133
146
 
147
+ def scrub_activation_state(env: dict[str, str]) -> dict[str, str]:
148
+ """Drop venv/conda/nvm/rvm activation inherited from the invoking shell.
149
+
150
+ A spawned window is a fresh context: the tools' `deactivate` counterparts
151
+ are shell functions that don't exist there, so inherited activation is
152
+ unremovable and points at the wrong worktree's environment. Prompt-hook
153
+ managers (direnv, mise, asdf) re-derive their state in the new shell and
154
+ need no help; nix-shell is left alone because on NixOS its PATH entries
155
+ are indistinguishable from the system PATH.
156
+ """
157
+ env = dict(env)
158
+ stale_dirs: set[str] = set()
159
+ for var, subdirs, companions in _ACTIVATION_STATE:
160
+ prefix = env.pop(var, None)
161
+ for name in companions:
162
+ env.pop(name, None)
163
+ if prefix:
164
+ stale_dirs.update(str(Path(prefix) / sub) if sub else prefix for sub in subdirs)
165
+ for var in [name for name in env if _CONDA_STACK.fullmatch(name)]:
166
+ stale_dirs.add(str(Path(env.pop(var)) / "bin"))
167
+ path = env.get("PATH")
168
+ if path and stale_dirs:
169
+ env["PATH"] = os.pathsep.join(
170
+ entry for entry in path.split(os.pathsep) if entry not in stale_dirs
171
+ )
172
+ return env
173
+
174
+
134
175
  def spawn_window(
135
176
  window_template: str,
136
177
  *,
@@ -142,7 +183,8 @@ def spawn_window(
142
183
 
143
184
  The resolved opener command joins the family as {command} (one argument,
144
185
  e.g. for `$SHELL -c {command}`) / `$WF_COMMAND` (spliced into argv words);
145
- it is template-only and not exported to the environment.
186
+ it is template-only and not exported to the environment. The inherited
187
+ environment is passed through scrub_activation_state first.
146
188
  """
147
189
  expanded = expand_template(window_template, {**variables, "WF_COMMAND": command})
148
190
  argv = shlex.split(expanded)
@@ -152,7 +194,7 @@ def spawn_window(
152
194
  subprocess.Popen(
153
195
  argv,
154
196
  cwd=cwd,
155
- env={**os.environ, **variables},
197
+ env=scrub_activation_state({**os.environ, **variables}),
156
198
  start_new_session=True,
157
199
  stdin=subprocess.DEVNULL,
158
200
  stdout=subprocess.DEVNULL,
@@ -150,7 +150,8 @@ def recorder(tmp_path: Path) -> Recorder:
150
150
  log = tmp_path / "recorder.log"
151
151
  script = tmp_path / "recorder"
152
152
  script.write_text(
153
- f'#!/bin/sh\necho "argv=$* argc=$# cwd=$PWD wf_worktree=$WF_WORKTREE" >> {log}\n'
153
+ f'#!/bin/sh\necho "argv=$* argc=$# cwd=$PWD wf_worktree=$WF_WORKTREE'
154
+ f' virtual_env=$VIRTUAL_ENV" >> {log}\n'
154
155
  )
155
156
  script.chmod(script.stat().st_mode | stat.S_IXUSR)
156
157
  return Recorder(path=script, log=log)
@@ -1,5 +1,6 @@
1
1
  """launch: opener/window template resolution and the cd protocol."""
2
2
 
3
+ import os
3
4
  import shlex
4
5
  from pathlib import Path
5
6
 
@@ -101,6 +102,61 @@ class TestTemplateExpansion:
101
102
  )
102
103
 
103
104
 
105
+ class TestScrubActivationState:
106
+ def test_venv_vars_and_path_entry_removed(self) -> None:
107
+ env = {
108
+ "VIRTUAL_ENV": "/repo/.venv",
109
+ "VIRTUAL_ENV_PROMPT": "(.venv)",
110
+ "PATH": "/repo/.venv/bin:/usr/local/bin:/usr/bin",
111
+ "HOME": "/home/u",
112
+ }
113
+ assert launch.scrub_activation_state(env) == {
114
+ "PATH": "/usr/local/bin:/usr/bin",
115
+ "HOME": "/home/u",
116
+ }
117
+
118
+ def test_clean_environment_untouched(self) -> None:
119
+ env = {"PATH": "/usr/local/bin:/usr/bin", "HOME": "/home/u", "WF_BRANCH": "feat"}
120
+ assert launch.scrub_activation_state(env) == env
121
+
122
+ def test_conda_including_stacked_prefixes(self) -> None:
123
+ env = {
124
+ "CONDA_PREFIX": "/opt/conda/envs/proj",
125
+ "CONDA_PREFIX_1": "/opt/conda",
126
+ "CONDA_DEFAULT_ENV": "proj",
127
+ "CONDA_SHLVL": "2",
128
+ "CONDA_PROMPT_MODIFIER": "(proj) ",
129
+ "PATH": "/opt/conda/envs/proj/bin:/opt/conda/bin:/opt/conda/condabin:/usr/bin",
130
+ }
131
+ # condabin survives so `conda` itself keeps working in the window.
132
+ assert launch.scrub_activation_state(env) == {
133
+ "PATH": "/opt/conda/condabin:/usr/bin",
134
+ }
135
+
136
+ def test_nvm_bin_is_itself_the_path_entry(self) -> None:
137
+ env = {
138
+ "NVM_BIN": "/home/u/.nvm/versions/node/v22.0.0/bin",
139
+ "NVM_INC": "/home/u/.nvm/versions/node/v22.0.0/include/node",
140
+ "PATH": "/home/u/.nvm/versions/node/v22.0.0/bin:/usr/bin",
141
+ }
142
+ assert launch.scrub_activation_state(env) == {"PATH": "/usr/bin"}
143
+
144
+ def test_rvm_ruby(self) -> None:
145
+ env = {
146
+ "GEM_HOME": "/home/u/.rvm/gems/ruby-3.3.0",
147
+ "GEM_PATH": "/home/u/.rvm/gems/ruby-3.3.0:/home/u/.rvm/gems/ruby-3.3.0@global",
148
+ "MY_RUBY_HOME": "/home/u/.rvm/rubies/ruby-3.3.0",
149
+ "RUBY_VERSION": "ruby-3.3.0",
150
+ "PATH": (
151
+ "/home/u/.rvm/gems/ruby-3.3.0/bin:/home/u/.rvm/rubies/ruby-3.3.0/bin:/usr/bin"
152
+ ),
153
+ }
154
+ assert launch.scrub_activation_state(env) == {"PATH": "/usr/bin"}
155
+
156
+ def test_missing_path_is_fine(self) -> None:
157
+ assert launch.scrub_activation_state({"VIRTUAL_ENV": "/repo/.venv"}) == {}
158
+
159
+
104
160
  def run_launch(
105
161
  cfg: Config,
106
162
  tmp_path: Path,
@@ -186,6 +242,16 @@ class TestLaunch:
186
242
  (line,) = recorder.wait_for_lines(1)
187
243
  assert f"wf_worktree={worktree}" in line
188
244
 
245
+ def test_window_process_sheds_inherited_venv(
246
+ self, tmp_path: Path, recorder: Recorder, monkeypatch: pytest.MonkeyPatch
247
+ ) -> None:
248
+ monkeypatch.setenv("VIRTUAL_ENV", str(tmp_path / ".venv"))
249
+ monkeypatch.setenv("PATH", f"{tmp_path / '.venv' / 'bin'}:{os.environ['PATH']}")
250
+ cfg = Config(window_command=f"{recorder.path} $WF_COMMAND")
251
+ run_launch(cfg, tmp_path, opener_arg="x")
252
+ (line,) = recorder.wait_for_lines(1)
253
+ assert line.endswith("virtual_env=")
254
+
189
255
  def test_empty_window_command_after_expansion(
190
256
  self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
191
257
  ) -> None:
@@ -1,19 +0,0 @@
1
- pkgbase = workforest
2
- pkgdesc = Git worktree forest management: per-branch worktrees with project-defined setup hooks
3
- pkgver = 0.2.1
4
- pkgrel = 1
5
- url = https://github.com/ArkadyBuryakov/workforest
6
- arch = any
7
- license = MIT
8
- makedepends = python-build
9
- makedepends = python-installer
10
- makedepends = python-wheel
11
- makedepends = python-hatchling
12
- depends = python
13
- depends = python-yaml
14
- depends = git
15
- optdepends = fzf: interactive TUI (workforest tui)
16
- source = workforest-0.2.1.tar.gz::https://github.com/ArkadyBuryakov/workforest/archive/v0.2.1.tar.gz
17
- sha256sums = 47062e51ae374a9afce50ee0be6c39b08da6c96064450b3c2c58566d05fb7078
18
-
19
- pkgname = workforest
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes