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.
- {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/publish_aur.yml +4 -25
- {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/publish_homebrew.yml +19 -34
- {workforest-0.2.2 → workforest-0.2.3}/.gitignore +4 -1
- {workforest-0.2.2 → workforest-0.2.3}/PKG-INFO +11 -8
- {workforest-0.2.2 → workforest-0.2.3}/README.md +10 -7
- workforest-0.2.2/packaging/AUR/PKGBUILD → workforest-0.2.3/packaging/AUR/PKGBUILD.template +6 -2
- workforest-0.2.2/packaging/homebrew/workforest.rb → workforest-0.2.3/packaging/homebrew/workforest.rb.template +7 -5
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/__init__.py +1 -1
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/launch.py +44 -2
- {workforest-0.2.2 → workforest-0.2.3}/tests/conftest.py +2 -1
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_launch.py +66 -0
- workforest-0.2.2/packaging/AUR/.SRCINFO +0 -19
- {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/ci.yml +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/publish_pypi.yml +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/.github/workflows/release.yml +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/LICENSE +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/Makefile +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/completions/_workforest +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/pyproject.toml +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/__main__.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/cli.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/commands.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/completions.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/config.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/errors.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/examples/config.yaml +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/gitutil.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/hooks.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/integrations/__init__.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/integrations/claude.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/output.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shell/completion.bash +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shell/completion.zsh +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shell/workforest.sh +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/shellinit.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/src/workforest/tui.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/__init__.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_claude.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_cli.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_commands.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_config.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_gitutil.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_harness.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_hooks.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_shell.py +0 -0
- {workforest-0.2.2 → workforest-0.2.3}/tests/test_tui.py +0 -0
- {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:
|
|
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:
|
|
24
|
+
- name: Render PKGBUILD from template
|
|
25
25
|
run: |
|
|
26
26
|
version="${GITHUB_REF_NAME#v}"
|
|
27
27
|
cd packaging/AUR
|
|
28
|
-
sed
|
|
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:
|
|
21
|
+
contents: read
|
|
15
22
|
steps:
|
|
16
23
|
- uses: actions/checkout@v4
|
|
17
24
|
|
|
18
|
-
- name:
|
|
25
|
+
- name: Render formula from template
|
|
19
26
|
run: |
|
|
20
|
-
version="${
|
|
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 -
|
|
26
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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="${
|
|
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.
|
|
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
|
|
204
|
-
manager: `packaging/AUR/`, `packaging/homebrew/`)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
184
|
-
manager: `packaging/AUR/`, `packaging/homebrew/`)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
|
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=('
|
|
17
|
+
sha256sums=('@SHA256@')
|
|
14
18
|
|
|
15
19
|
build() {
|
|
16
20
|
cd "$pkgname-$pkgver"
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
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/
|
|
10
|
-
sha256 "
|
|
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
|
|
|
@@ -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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|