tmuxp-plugin-worktree 0.1.0__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.
- tmuxp_plugin_worktree-0.1.0/LICENSE +21 -0
- tmuxp_plugin_worktree-0.1.0/PKG-INFO +208 -0
- tmuxp_plugin_worktree-0.1.0/README.md +179 -0
- tmuxp_plugin_worktree-0.1.0/pyproject.toml +46 -0
- tmuxp_plugin_worktree-0.1.0/setup.cfg +4 -0
- tmuxp_plugin_worktree-0.1.0/src/tmuxp_plugin_worktree.egg-info/PKG-INFO +208 -0
- tmuxp_plugin_worktree-0.1.0/src/tmuxp_plugin_worktree.egg-info/SOURCES.txt +11 -0
- tmuxp_plugin_worktree-0.1.0/src/tmuxp_plugin_worktree.egg-info/dependency_links.txt +1 -0
- tmuxp_plugin_worktree-0.1.0/src/tmuxp_plugin_worktree.egg-info/requires.txt +4 -0
- tmuxp_plugin_worktree-0.1.0/src/tmuxp_plugin_worktree.egg-info/top_level.txt +1 -0
- tmuxp_plugin_worktree-0.1.0/src/tmuxp_plugin_worktree.py +425 -0
- tmuxp_plugin_worktree-0.1.0/tests/test_e2e.py +505 -0
- tmuxp_plugin_worktree-0.1.0/tests/test_helpers.py +164 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robbert Helling
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tmuxp-plugin-worktree
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: tmuxp plugin: pick the git worktree a session starts in
|
|
5
|
+
Author: Robbert Helling
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rhelling/tmuxp-plugin-worktree
|
|
8
|
+
Project-URL: Issues, https://github.com/rhelling/tmuxp-plugin-worktree/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/rhelling/tmuxp-plugin-worktree/releases
|
|
10
|
+
Keywords: tmux,tmuxp,git,worktree,plugin
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Environment :: Plugins
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Classifier: Topic :: Terminals
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: tmuxp>=1.73
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# tmuxp-plugin-worktree
|
|
31
|
+
|
|
32
|
+
A [tmuxp](https://github.com/tmux-python/tmuxp) plugin that lets you pick the git
|
|
33
|
+
worktree a session starts in.
|
|
34
|
+
|
|
35
|
+
When the repository of a session's `start_directory` has more than one worktree,
|
|
36
|
+
`tmuxp load <session>` first shows a picker with the configured directory
|
|
37
|
+
preselected:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
worktree>
|
|
41
|
+
Session myapp: pick a worktree (Enter = start, Esc = cancel)
|
|
42
|
+
> myrepo [main] (default)
|
|
43
|
+
myrepo-feature-x [feature/x]
|
|
44
|
+
myrepo-hotfix [hotfix/login]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Picking another worktree:
|
|
48
|
+
|
|
49
|
+
- moves every session, window and pane `start_directory` inside the repository
|
|
50
|
+
into that worktree (relative `cd` commands in your panes keep working), and a
|
|
51
|
+
`before_script` that lives in the repository;
|
|
52
|
+
- renames the session to `<session>-<worktree>` (for example `myapp-feature-x`),
|
|
53
|
+
so it can run next to the default session.
|
|
54
|
+
|
|
55
|
+
A branch may not have every directory your workspace uses. A directory that
|
|
56
|
+
exists in the default worktree but is missing in the chosen worktree is replaced
|
|
57
|
+
by its nearest existing parent, with a note on stderr (tmux would otherwise
|
|
58
|
+
silently start the pane in `$HOME`). Window and pane directories are checked
|
|
59
|
+
after `before_script`, so setup can create them first. The session directory is
|
|
60
|
+
resolved earlier to provide setup with a working directory.
|
|
61
|
+
|
|
62
|
+
A `before_script` the chosen worktree does not have, such as an untracked file,
|
|
63
|
+
keeps running from the default worktree. Its arguments are preserved, including
|
|
64
|
+
quoting and spaces; only the executable's path is rewritten.
|
|
65
|
+
|
|
66
|
+
With a single worktree, or when the start directory is not a git repository,
|
|
67
|
+
nothing changes and no picker is shown.
|
|
68
|
+
|
|
69
|
+
## Requirements
|
|
70
|
+
|
|
71
|
+
- tmuxp 1.73 or newer for the no-install setup (`workspace_builder_paths`);
|
|
72
|
+
tested with tmuxp 1.73, 1.74 and 1.76
|
|
73
|
+
- git
|
|
74
|
+
- fzf (optional; without it you get a numbered menu)
|
|
75
|
+
- Python 3.10 or newer (the Python that tmuxp runs on)
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
### Option A: install from PyPI (recommended)
|
|
80
|
+
|
|
81
|
+
Install the package into the Python environment tmuxp runs in; then only the
|
|
82
|
+
`plugins` key is needed in your workspace files.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# tmuxp installed with pipx
|
|
86
|
+
pipx inject tmuxp tmuxp-plugin-worktree
|
|
87
|
+
|
|
88
|
+
# tmuxp installed with uv
|
|
89
|
+
uv tool install tmuxp --with tmuxp-plugin-worktree
|
|
90
|
+
|
|
91
|
+
# tmuxp installed with pip in a virtualenv (activate it first)
|
|
92
|
+
python -m pip install tmuxp-plugin-worktree
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
session_name: myapp
|
|
97
|
+
start_directory: ~/code/myrepo
|
|
98
|
+
plugins:
|
|
99
|
+
- tmuxp_plugin_worktree.WorktreePlugin
|
|
100
|
+
windows:
|
|
101
|
+
- window_name: editor
|
|
102
|
+
panes:
|
|
103
|
+
- nvim
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Option B: no install (works with Homebrew's tmuxp)
|
|
107
|
+
|
|
108
|
+
Homebrew installs tmuxp in a private virtualenv without pip. Since 1.73, tmuxp
|
|
109
|
+
can add import paths per workspace with `workspace_builder_paths`, so you can
|
|
110
|
+
point it at a checkout instead:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/rhelling/tmuxp-plugin-worktree.git ~/Documents/Projects/tmuxp-plugin-worktree
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
session_name: myapp
|
|
118
|
+
start_directory: ~/code/myrepo
|
|
119
|
+
workspace_builder_paths:
|
|
120
|
+
- ~/Documents/Projects/tmuxp-plugin-worktree/src
|
|
121
|
+
plugins:
|
|
122
|
+
- tmuxp_plugin_worktree.WorktreePlugin
|
|
123
|
+
windows:
|
|
124
|
+
- window_name: editor
|
|
125
|
+
panes:
|
|
126
|
+
- nvim
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Relative entries in `workspace_builder_paths` are resolved against the directory
|
|
130
|
+
of the workspace file.
|
|
131
|
+
|
|
132
|
+
### Check that it works
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
tmuxp load myapp # picker when the repo has 2+ worktrees
|
|
136
|
+
WT_PATH=myrepo-feature-x tmuxp load -d myapp # straight into that worktree, detached
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Usage
|
|
140
|
+
|
|
141
|
+
- **fzf picker:** Enter starts the highlighted entry (the default is preselected),
|
|
142
|
+
typing filters the list, Esc or Ctrl-C cancels without starting anything.
|
|
143
|
+
`FZF_DEFAULT_OPTS` and `FZF_DEFAULT_OPTS_FILE` are ignored for this picker to
|
|
144
|
+
keep its output format predictable.
|
|
145
|
+
- **Without fzf:** type a number and press Enter, or press Enter for the default.
|
|
146
|
+
Esc, Ctrl-C or Ctrl-D cancels.
|
|
147
|
+
- **No picker:** `WT_PATH=myrepo-feature-x tmuxp load myapp`. A relative
|
|
148
|
+
`WT_PATH` is resolved against the directory that contains the default
|
|
149
|
+
repository; absolute paths and `~/...` work too. An unknown worktree aborts
|
|
150
|
+
with the list of valid choices. A session whose start directory is not in a
|
|
151
|
+
git repository (or that has no `start_directory`) ignores `WT_PATH`, with a
|
|
152
|
+
note.
|
|
153
|
+
- **No terminal** (scripts, cron, CI): the default worktree is used unless
|
|
154
|
+
`WT_PATH` is set.
|
|
155
|
+
- Prunable worktrees (their directory is gone) and bare repositories are not
|
|
156
|
+
offered.
|
|
157
|
+
|
|
158
|
+
## How it works
|
|
159
|
+
|
|
160
|
+
tmuxp creates plugins after it has expanded the workspace file, but before it
|
|
161
|
+
checks for an existing session and builds the windows. The plugin uses that
|
|
162
|
+
moment to rewrite the expanded configuration: every path inside the default
|
|
163
|
+
repository is moved into the chosen worktree and the session name gets the
|
|
164
|
+
worktree suffix. tmuxp does not pass the configuration to plugins, so the plugin
|
|
165
|
+
finds it in the calling frames; this is verified against tmuxp 1.73, 1.74 and 1.76.
|
|
166
|
+
|
|
167
|
+
When a directory fallback needs to wait for `before_script`, the plugin runs
|
|
168
|
+
setup once through tmuxp's script runner in its `before_workspace_builder` hook,
|
|
169
|
+
then finalizes the window and pane paths. Existing sessions do not rerun setup.
|
|
170
|
+
For this case, tmuxp's script-progress callbacks are bypassed. If using other
|
|
171
|
+
plugins, list this plugin last so their preparation hooks still run before setup.
|
|
172
|
+
|
|
173
|
+
## Limitations
|
|
174
|
+
|
|
175
|
+
- Absolute paths inside `shell_command` or `shell_command_before` are not
|
|
176
|
+
rewritten; use `start_directory` and relative `cd` commands instead.
|
|
177
|
+
- The session-name suffix comes from the worktree's directory name:
|
|
178
|
+
`myrepo-feature-x` becomes `feature-x`, any other name is used as is. Dots and
|
|
179
|
+
colons become `-`, because tmux does not allow them in session names. If
|
|
180
|
+
multiple worktrees produce the same session name, selecting one aborts with
|
|
181
|
+
a list of the conflicting paths. Rename those worktree directories rather
|
|
182
|
+
than silently reusing another worktree's session.
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
python3 -m venv .venv
|
|
188
|
+
.venv/bin/pip install -e '.[test]'
|
|
189
|
+
.venv/bin/pytest
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The end-to-end tests need `tmux` and `git`. They create throwaway repositories
|
|
193
|
+
and run tmuxp against a private tmux server, so your own sessions are never
|
|
194
|
+
touched. The fzf tests are skipped when fzf is not installed.
|
|
195
|
+
|
|
196
|
+
See [TESTING.md](TESTING.md) for what the tests cover, running a subset or
|
|
197
|
+
another tmuxp version, a manual test checklist and troubleshooting.
|
|
198
|
+
|
|
199
|
+
## Releasing
|
|
200
|
+
|
|
201
|
+
1. Bump `__version__` in `src/tmuxp_plugin_worktree.py` and commit.
|
|
202
|
+
2. Tag and push: `git tag v0.1.0 && git push origin main v0.1.0`.
|
|
203
|
+
3. The `publish` workflow builds the package and uploads it to PyPI with
|
|
204
|
+
trusted publishing; no API token is needed.
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
[MIT](LICENSE).
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# tmuxp-plugin-worktree
|
|
2
|
+
|
|
3
|
+
A [tmuxp](https://github.com/tmux-python/tmuxp) plugin that lets you pick the git
|
|
4
|
+
worktree a session starts in.
|
|
5
|
+
|
|
6
|
+
When the repository of a session's `start_directory` has more than one worktree,
|
|
7
|
+
`tmuxp load <session>` first shows a picker with the configured directory
|
|
8
|
+
preselected:
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
worktree>
|
|
12
|
+
Session myapp: pick a worktree (Enter = start, Esc = cancel)
|
|
13
|
+
> myrepo [main] (default)
|
|
14
|
+
myrepo-feature-x [feature/x]
|
|
15
|
+
myrepo-hotfix [hotfix/login]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Picking another worktree:
|
|
19
|
+
|
|
20
|
+
- moves every session, window and pane `start_directory` inside the repository
|
|
21
|
+
into that worktree (relative `cd` commands in your panes keep working), and a
|
|
22
|
+
`before_script` that lives in the repository;
|
|
23
|
+
- renames the session to `<session>-<worktree>` (for example `myapp-feature-x`),
|
|
24
|
+
so it can run next to the default session.
|
|
25
|
+
|
|
26
|
+
A branch may not have every directory your workspace uses. A directory that
|
|
27
|
+
exists in the default worktree but is missing in the chosen worktree is replaced
|
|
28
|
+
by its nearest existing parent, with a note on stderr (tmux would otherwise
|
|
29
|
+
silently start the pane in `$HOME`). Window and pane directories are checked
|
|
30
|
+
after `before_script`, so setup can create them first. The session directory is
|
|
31
|
+
resolved earlier to provide setup with a working directory.
|
|
32
|
+
|
|
33
|
+
A `before_script` the chosen worktree does not have, such as an untracked file,
|
|
34
|
+
keeps running from the default worktree. Its arguments are preserved, including
|
|
35
|
+
quoting and spaces; only the executable's path is rewritten.
|
|
36
|
+
|
|
37
|
+
With a single worktree, or when the start directory is not a git repository,
|
|
38
|
+
nothing changes and no picker is shown.
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- tmuxp 1.73 or newer for the no-install setup (`workspace_builder_paths`);
|
|
43
|
+
tested with tmuxp 1.73, 1.74 and 1.76
|
|
44
|
+
- git
|
|
45
|
+
- fzf (optional; without it you get a numbered menu)
|
|
46
|
+
- Python 3.10 or newer (the Python that tmuxp runs on)
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
### Option A: install from PyPI (recommended)
|
|
51
|
+
|
|
52
|
+
Install the package into the Python environment tmuxp runs in; then only the
|
|
53
|
+
`plugins` key is needed in your workspace files.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# tmuxp installed with pipx
|
|
57
|
+
pipx inject tmuxp tmuxp-plugin-worktree
|
|
58
|
+
|
|
59
|
+
# tmuxp installed with uv
|
|
60
|
+
uv tool install tmuxp --with tmuxp-plugin-worktree
|
|
61
|
+
|
|
62
|
+
# tmuxp installed with pip in a virtualenv (activate it first)
|
|
63
|
+
python -m pip install tmuxp-plugin-worktree
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
session_name: myapp
|
|
68
|
+
start_directory: ~/code/myrepo
|
|
69
|
+
plugins:
|
|
70
|
+
- tmuxp_plugin_worktree.WorktreePlugin
|
|
71
|
+
windows:
|
|
72
|
+
- window_name: editor
|
|
73
|
+
panes:
|
|
74
|
+
- nvim
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Option B: no install (works with Homebrew's tmuxp)
|
|
78
|
+
|
|
79
|
+
Homebrew installs tmuxp in a private virtualenv without pip. Since 1.73, tmuxp
|
|
80
|
+
can add import paths per workspace with `workspace_builder_paths`, so you can
|
|
81
|
+
point it at a checkout instead:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/rhelling/tmuxp-plugin-worktree.git ~/Documents/Projects/tmuxp-plugin-worktree
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
session_name: myapp
|
|
89
|
+
start_directory: ~/code/myrepo
|
|
90
|
+
workspace_builder_paths:
|
|
91
|
+
- ~/Documents/Projects/tmuxp-plugin-worktree/src
|
|
92
|
+
plugins:
|
|
93
|
+
- tmuxp_plugin_worktree.WorktreePlugin
|
|
94
|
+
windows:
|
|
95
|
+
- window_name: editor
|
|
96
|
+
panes:
|
|
97
|
+
- nvim
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Relative entries in `workspace_builder_paths` are resolved against the directory
|
|
101
|
+
of the workspace file.
|
|
102
|
+
|
|
103
|
+
### Check that it works
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
tmuxp load myapp # picker when the repo has 2+ worktrees
|
|
107
|
+
WT_PATH=myrepo-feature-x tmuxp load -d myapp # straight into that worktree, detached
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Usage
|
|
111
|
+
|
|
112
|
+
- **fzf picker:** Enter starts the highlighted entry (the default is preselected),
|
|
113
|
+
typing filters the list, Esc or Ctrl-C cancels without starting anything.
|
|
114
|
+
`FZF_DEFAULT_OPTS` and `FZF_DEFAULT_OPTS_FILE` are ignored for this picker to
|
|
115
|
+
keep its output format predictable.
|
|
116
|
+
- **Without fzf:** type a number and press Enter, or press Enter for the default.
|
|
117
|
+
Esc, Ctrl-C or Ctrl-D cancels.
|
|
118
|
+
- **No picker:** `WT_PATH=myrepo-feature-x tmuxp load myapp`. A relative
|
|
119
|
+
`WT_PATH` is resolved against the directory that contains the default
|
|
120
|
+
repository; absolute paths and `~/...` work too. An unknown worktree aborts
|
|
121
|
+
with the list of valid choices. A session whose start directory is not in a
|
|
122
|
+
git repository (or that has no `start_directory`) ignores `WT_PATH`, with a
|
|
123
|
+
note.
|
|
124
|
+
- **No terminal** (scripts, cron, CI): the default worktree is used unless
|
|
125
|
+
`WT_PATH` is set.
|
|
126
|
+
- Prunable worktrees (their directory is gone) and bare repositories are not
|
|
127
|
+
offered.
|
|
128
|
+
|
|
129
|
+
## How it works
|
|
130
|
+
|
|
131
|
+
tmuxp creates plugins after it has expanded the workspace file, but before it
|
|
132
|
+
checks for an existing session and builds the windows. The plugin uses that
|
|
133
|
+
moment to rewrite the expanded configuration: every path inside the default
|
|
134
|
+
repository is moved into the chosen worktree and the session name gets the
|
|
135
|
+
worktree suffix. tmuxp does not pass the configuration to plugins, so the plugin
|
|
136
|
+
finds it in the calling frames; this is verified against tmuxp 1.73, 1.74 and 1.76.
|
|
137
|
+
|
|
138
|
+
When a directory fallback needs to wait for `before_script`, the plugin runs
|
|
139
|
+
setup once through tmuxp's script runner in its `before_workspace_builder` hook,
|
|
140
|
+
then finalizes the window and pane paths. Existing sessions do not rerun setup.
|
|
141
|
+
For this case, tmuxp's script-progress callbacks are bypassed. If using other
|
|
142
|
+
plugins, list this plugin last so their preparation hooks still run before setup.
|
|
143
|
+
|
|
144
|
+
## Limitations
|
|
145
|
+
|
|
146
|
+
- Absolute paths inside `shell_command` or `shell_command_before` are not
|
|
147
|
+
rewritten; use `start_directory` and relative `cd` commands instead.
|
|
148
|
+
- The session-name suffix comes from the worktree's directory name:
|
|
149
|
+
`myrepo-feature-x` becomes `feature-x`, any other name is used as is. Dots and
|
|
150
|
+
colons become `-`, because tmux does not allow them in session names. If
|
|
151
|
+
multiple worktrees produce the same session name, selecting one aborts with
|
|
152
|
+
a list of the conflicting paths. Rename those worktree directories rather
|
|
153
|
+
than silently reusing another worktree's session.
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python3 -m venv .venv
|
|
159
|
+
.venv/bin/pip install -e '.[test]'
|
|
160
|
+
.venv/bin/pytest
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The end-to-end tests need `tmux` and `git`. They create throwaway repositories
|
|
164
|
+
and run tmuxp against a private tmux server, so your own sessions are never
|
|
165
|
+
touched. The fzf tests are skipped when fzf is not installed.
|
|
166
|
+
|
|
167
|
+
See [TESTING.md](TESTING.md) for what the tests cover, running a subset or
|
|
168
|
+
another tmuxp version, a manual test checklist and troubleshooting.
|
|
169
|
+
|
|
170
|
+
## Releasing
|
|
171
|
+
|
|
172
|
+
1. Bump `__version__` in `src/tmuxp_plugin_worktree.py` and commit.
|
|
173
|
+
2. Tag and push: `git tag v0.1.0 && git push origin main v0.1.0`.
|
|
174
|
+
3. The `publish` workflow builds the package and uploads it to PyPI with
|
|
175
|
+
trusted publishing; no API token is needed.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
[MIT](LICENSE).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tmuxp-plugin-worktree"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "tmuxp plugin: pick the git worktree a session starts in"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "Robbert Helling" }]
|
|
14
|
+
dependencies = ["tmuxp>=1.73"]
|
|
15
|
+
keywords = ["tmux", "tmuxp", "git", "worktree", "plugin"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Environment :: Plugins",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Operating System :: MacOS",
|
|
22
|
+
"Operating System :: POSIX :: Linux",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
25
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
26
|
+
"Topic :: Terminals",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/rhelling/tmuxp-plugin-worktree"
|
|
32
|
+
Issues = "https://github.com/rhelling/tmuxp-plugin-worktree/issues"
|
|
33
|
+
Changelog = "https://github.com/rhelling/tmuxp-plugin-worktree/releases"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
test = ["pytest>=7"]
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
package-dir = { "" = "src" }
|
|
40
|
+
py-modules = ["tmuxp_plugin_worktree"]
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.dynamic]
|
|
43
|
+
version = { attr = "tmuxp_plugin_worktree.__version__" }
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tmuxp-plugin-worktree
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: tmuxp plugin: pick the git worktree a session starts in
|
|
5
|
+
Author: Robbert Helling
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rhelling/tmuxp-plugin-worktree
|
|
8
|
+
Project-URL: Issues, https://github.com/rhelling/tmuxp-plugin-worktree/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/rhelling/tmuxp-plugin-worktree/releases
|
|
10
|
+
Keywords: tmux,tmuxp,git,worktree,plugin
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Environment :: Plugins
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Classifier: Topic :: Terminals
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: tmuxp>=1.73
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# tmuxp-plugin-worktree
|
|
31
|
+
|
|
32
|
+
A [tmuxp](https://github.com/tmux-python/tmuxp) plugin that lets you pick the git
|
|
33
|
+
worktree a session starts in.
|
|
34
|
+
|
|
35
|
+
When the repository of a session's `start_directory` has more than one worktree,
|
|
36
|
+
`tmuxp load <session>` first shows a picker with the configured directory
|
|
37
|
+
preselected:
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
worktree>
|
|
41
|
+
Session myapp: pick a worktree (Enter = start, Esc = cancel)
|
|
42
|
+
> myrepo [main] (default)
|
|
43
|
+
myrepo-feature-x [feature/x]
|
|
44
|
+
myrepo-hotfix [hotfix/login]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Picking another worktree:
|
|
48
|
+
|
|
49
|
+
- moves every session, window and pane `start_directory` inside the repository
|
|
50
|
+
into that worktree (relative `cd` commands in your panes keep working), and a
|
|
51
|
+
`before_script` that lives in the repository;
|
|
52
|
+
- renames the session to `<session>-<worktree>` (for example `myapp-feature-x`),
|
|
53
|
+
so it can run next to the default session.
|
|
54
|
+
|
|
55
|
+
A branch may not have every directory your workspace uses. A directory that
|
|
56
|
+
exists in the default worktree but is missing in the chosen worktree is replaced
|
|
57
|
+
by its nearest existing parent, with a note on stderr (tmux would otherwise
|
|
58
|
+
silently start the pane in `$HOME`). Window and pane directories are checked
|
|
59
|
+
after `before_script`, so setup can create them first. The session directory is
|
|
60
|
+
resolved earlier to provide setup with a working directory.
|
|
61
|
+
|
|
62
|
+
A `before_script` the chosen worktree does not have, such as an untracked file,
|
|
63
|
+
keeps running from the default worktree. Its arguments are preserved, including
|
|
64
|
+
quoting and spaces; only the executable's path is rewritten.
|
|
65
|
+
|
|
66
|
+
With a single worktree, or when the start directory is not a git repository,
|
|
67
|
+
nothing changes and no picker is shown.
|
|
68
|
+
|
|
69
|
+
## Requirements
|
|
70
|
+
|
|
71
|
+
- tmuxp 1.73 or newer for the no-install setup (`workspace_builder_paths`);
|
|
72
|
+
tested with tmuxp 1.73, 1.74 and 1.76
|
|
73
|
+
- git
|
|
74
|
+
- fzf (optional; without it you get a numbered menu)
|
|
75
|
+
- Python 3.10 or newer (the Python that tmuxp runs on)
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
### Option A: install from PyPI (recommended)
|
|
80
|
+
|
|
81
|
+
Install the package into the Python environment tmuxp runs in; then only the
|
|
82
|
+
`plugins` key is needed in your workspace files.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# tmuxp installed with pipx
|
|
86
|
+
pipx inject tmuxp tmuxp-plugin-worktree
|
|
87
|
+
|
|
88
|
+
# tmuxp installed with uv
|
|
89
|
+
uv tool install tmuxp --with tmuxp-plugin-worktree
|
|
90
|
+
|
|
91
|
+
# tmuxp installed with pip in a virtualenv (activate it first)
|
|
92
|
+
python -m pip install tmuxp-plugin-worktree
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
session_name: myapp
|
|
97
|
+
start_directory: ~/code/myrepo
|
|
98
|
+
plugins:
|
|
99
|
+
- tmuxp_plugin_worktree.WorktreePlugin
|
|
100
|
+
windows:
|
|
101
|
+
- window_name: editor
|
|
102
|
+
panes:
|
|
103
|
+
- nvim
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Option B: no install (works with Homebrew's tmuxp)
|
|
107
|
+
|
|
108
|
+
Homebrew installs tmuxp in a private virtualenv without pip. Since 1.73, tmuxp
|
|
109
|
+
can add import paths per workspace with `workspace_builder_paths`, so you can
|
|
110
|
+
point it at a checkout instead:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
git clone https://github.com/rhelling/tmuxp-plugin-worktree.git ~/Documents/Projects/tmuxp-plugin-worktree
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
session_name: myapp
|
|
118
|
+
start_directory: ~/code/myrepo
|
|
119
|
+
workspace_builder_paths:
|
|
120
|
+
- ~/Documents/Projects/tmuxp-plugin-worktree/src
|
|
121
|
+
plugins:
|
|
122
|
+
- tmuxp_plugin_worktree.WorktreePlugin
|
|
123
|
+
windows:
|
|
124
|
+
- window_name: editor
|
|
125
|
+
panes:
|
|
126
|
+
- nvim
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Relative entries in `workspace_builder_paths` are resolved against the directory
|
|
130
|
+
of the workspace file.
|
|
131
|
+
|
|
132
|
+
### Check that it works
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
tmuxp load myapp # picker when the repo has 2+ worktrees
|
|
136
|
+
WT_PATH=myrepo-feature-x tmuxp load -d myapp # straight into that worktree, detached
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Usage
|
|
140
|
+
|
|
141
|
+
- **fzf picker:** Enter starts the highlighted entry (the default is preselected),
|
|
142
|
+
typing filters the list, Esc or Ctrl-C cancels without starting anything.
|
|
143
|
+
`FZF_DEFAULT_OPTS` and `FZF_DEFAULT_OPTS_FILE` are ignored for this picker to
|
|
144
|
+
keep its output format predictable.
|
|
145
|
+
- **Without fzf:** type a number and press Enter, or press Enter for the default.
|
|
146
|
+
Esc, Ctrl-C or Ctrl-D cancels.
|
|
147
|
+
- **No picker:** `WT_PATH=myrepo-feature-x tmuxp load myapp`. A relative
|
|
148
|
+
`WT_PATH` is resolved against the directory that contains the default
|
|
149
|
+
repository; absolute paths and `~/...` work too. An unknown worktree aborts
|
|
150
|
+
with the list of valid choices. A session whose start directory is not in a
|
|
151
|
+
git repository (or that has no `start_directory`) ignores `WT_PATH`, with a
|
|
152
|
+
note.
|
|
153
|
+
- **No terminal** (scripts, cron, CI): the default worktree is used unless
|
|
154
|
+
`WT_PATH` is set.
|
|
155
|
+
- Prunable worktrees (their directory is gone) and bare repositories are not
|
|
156
|
+
offered.
|
|
157
|
+
|
|
158
|
+
## How it works
|
|
159
|
+
|
|
160
|
+
tmuxp creates plugins after it has expanded the workspace file, but before it
|
|
161
|
+
checks for an existing session and builds the windows. The plugin uses that
|
|
162
|
+
moment to rewrite the expanded configuration: every path inside the default
|
|
163
|
+
repository is moved into the chosen worktree and the session name gets the
|
|
164
|
+
worktree suffix. tmuxp does not pass the configuration to plugins, so the plugin
|
|
165
|
+
finds it in the calling frames; this is verified against tmuxp 1.73, 1.74 and 1.76.
|
|
166
|
+
|
|
167
|
+
When a directory fallback needs to wait for `before_script`, the plugin runs
|
|
168
|
+
setup once through tmuxp's script runner in its `before_workspace_builder` hook,
|
|
169
|
+
then finalizes the window and pane paths. Existing sessions do not rerun setup.
|
|
170
|
+
For this case, tmuxp's script-progress callbacks are bypassed. If using other
|
|
171
|
+
plugins, list this plugin last so their preparation hooks still run before setup.
|
|
172
|
+
|
|
173
|
+
## Limitations
|
|
174
|
+
|
|
175
|
+
- Absolute paths inside `shell_command` or `shell_command_before` are not
|
|
176
|
+
rewritten; use `start_directory` and relative `cd` commands instead.
|
|
177
|
+
- The session-name suffix comes from the worktree's directory name:
|
|
178
|
+
`myrepo-feature-x` becomes `feature-x`, any other name is used as is. Dots and
|
|
179
|
+
colons become `-`, because tmux does not allow them in session names. If
|
|
180
|
+
multiple worktrees produce the same session name, selecting one aborts with
|
|
181
|
+
a list of the conflicting paths. Rename those worktree directories rather
|
|
182
|
+
than silently reusing another worktree's session.
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
python3 -m venv .venv
|
|
188
|
+
.venv/bin/pip install -e '.[test]'
|
|
189
|
+
.venv/bin/pytest
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The end-to-end tests need `tmux` and `git`. They create throwaway repositories
|
|
193
|
+
and run tmuxp against a private tmux server, so your own sessions are never
|
|
194
|
+
touched. The fzf tests are skipped when fzf is not installed.
|
|
195
|
+
|
|
196
|
+
See [TESTING.md](TESTING.md) for what the tests cover, running a subset or
|
|
197
|
+
another tmuxp version, a manual test checklist and troubleshooting.
|
|
198
|
+
|
|
199
|
+
## Releasing
|
|
200
|
+
|
|
201
|
+
1. Bump `__version__` in `src/tmuxp_plugin_worktree.py` and commit.
|
|
202
|
+
2. Tag and push: `git tag v0.1.0 && git push origin main v0.1.0`.
|
|
203
|
+
3. The `publish` workflow builds the package and uploads it to PyPI with
|
|
204
|
+
trusted publishing; no API token is needed.
|
|
205
|
+
|
|
206
|
+
## License
|
|
207
|
+
|
|
208
|
+
[MIT](LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/tmuxp_plugin_worktree.py
|
|
5
|
+
src/tmuxp_plugin_worktree.egg-info/PKG-INFO
|
|
6
|
+
src/tmuxp_plugin_worktree.egg-info/SOURCES.txt
|
|
7
|
+
src/tmuxp_plugin_worktree.egg-info/dependency_links.txt
|
|
8
|
+
src/tmuxp_plugin_worktree.egg-info/requires.txt
|
|
9
|
+
src/tmuxp_plugin_worktree.egg-info/top_level.txt
|
|
10
|
+
tests/test_e2e.py
|
|
11
|
+
tests/test_helpers.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tmuxp_plugin_worktree
|