git-worktree-wrapper 0.1.0__py3-none-any.whl
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.
- git_worktree_wrapper-0.1.0.dist-info/METADATA +473 -0
- git_worktree_wrapper-0.1.0.dist-info/RECORD +35 -0
- git_worktree_wrapper-0.1.0.dist-info/WHEEL +4 -0
- git_worktree_wrapper-0.1.0.dist-info/entry_points.txt +2 -0
- gww/__init__.py +3 -0
- gww/actions/__init__.py +224 -0
- gww/actions/types.py +187 -0
- gww/cli/__init__.py +1 -0
- gww/cli/commands/__init__.py +1 -0
- gww/cli/commands/add.py +122 -0
- gww/cli/commands/clone.py +97 -0
- gww/cli/commands/init.py +147 -0
- gww/cli/commands/migrate.py +81 -0
- gww/cli/commands/pull.py +62 -0
- gww/cli/commands/remove.py +153 -0
- gww/cli/context.py +382 -0
- gww/cli/main.py +285 -0
- gww/config/__init__.py +1 -0
- gww/config/loader.py +305 -0
- gww/config/resolver.py +188 -0
- gww/config/validator.py +344 -0
- gww/git/__init__.py +1 -0
- gww/git/branch.py +264 -0
- gww/git/repository.py +403 -0
- gww/git/worktree.py +395 -0
- gww/migration/__init__.py +44 -0
- gww/migration/executor.py +342 -0
- gww/migration/planner.py +260 -0
- gww/template/__init__.py +1 -0
- gww/template/evaluator.py +281 -0
- gww/template/functions.py +378 -0
- gww/utils/__init__.py +1 -0
- gww/utils/shell.py +894 -0
- gww/utils/uri.py +171 -0
- gww/utils/xdg.py +71 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: git-worktree-wrapper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Git Worktree Wrapper - CLI tool for managing git worktrees with configurable path templates
|
|
5
|
+
Author: Vadim Volk
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: cli,git,worktree,wrapper
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: ruamel-yaml>=0.18.0
|
|
21
|
+
Requires-Dist: simpleeval>=1.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
<!-- Language switcher -->
|
|
29
|
+
**English** | [Русский](README.ru.md)
|
|
30
|
+
|
|
31
|
+
> ⚠️ **Warning**: GWW is not heavily tested, use at your own risk!
|
|
32
|
+
|
|
33
|
+
# 🚀 GWW - Git Worktree Wrapper
|
|
34
|
+
|
|
35
|
+
[](https://github.com/vadimvolk/git-worktree-wrapper/actions/workflows/ci.yml)
|
|
36
|
+
|
|
37
|
+
A CLI tool that wraps git worktree functionality with configurable path templates, condition-based routing, and project-specific actions.
|
|
38
|
+
|
|
39
|
+
## ✨ Features
|
|
40
|
+
|
|
41
|
+
- **📝 Configurable path templates**: Dynamic path generation using templates with functions like `path(n)`, `branch()`, `norm_branch()`, `tag()`
|
|
42
|
+
- **🔄 Condition-based routing**: Route repositories to different locations based on URI conditions (host, path, protocol, tags)
|
|
43
|
+
- **🏷️ Tag support**: Pass custom tags via `--tag` option for conditional routing and path organization
|
|
44
|
+
- **⚙️ Project actions**: Execute custom actions (file copies, commands) after clone or worktree creation
|
|
45
|
+
- **🐚 Shell completion**: Bash, Zsh, and Fish completion support
|
|
46
|
+
|
|
47
|
+
## 📋 Requirements
|
|
48
|
+
|
|
49
|
+
- 🐍 Python 3.11+
|
|
50
|
+
- 🔧 Git
|
|
51
|
+
- 🖥️ Unix-like system (Linux, macOS)
|
|
52
|
+
|
|
53
|
+
## 📦 Installation
|
|
54
|
+
|
|
55
|
+
### Install from PyPI (recommended)
|
|
56
|
+
|
|
57
|
+
The package is published on PyPI as `git-worktree-wrapper`; the CLI
|
|
58
|
+
command it installs is `gww`.
|
|
59
|
+
|
|
60
|
+
#### Using uv
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv tool install git-worktree-wrapper
|
|
64
|
+
gww --help
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Using pipx
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pipx install git-worktree-wrapper
|
|
71
|
+
gww --help
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Using pip
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python -m pip install --user git-worktree-wrapper
|
|
78
|
+
gww --help
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### From Git (no PyPI)
|
|
82
|
+
|
|
83
|
+
Use this if you need a specific commit or branch that hasn't been
|
|
84
|
+
released yet.
|
|
85
|
+
|
|
86
|
+
#### Using uv
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uv tool install "git+https://github.com/vadimvolk/git-worktree-wrapper.git"
|
|
90
|
+
gww --help
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Using pipx
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pipx install "git+https://github.com/vadimvolk/git-worktree-wrapper.git"
|
|
97
|
+
gww --help
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### From source (development)
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Clone the repository
|
|
104
|
+
git clone git@github.com:vadimvolk/git-worktree-wrapper.git
|
|
105
|
+
cd git-worktree-wrapper
|
|
106
|
+
|
|
107
|
+
# Install with uv
|
|
108
|
+
uv sync
|
|
109
|
+
|
|
110
|
+
# Run gww
|
|
111
|
+
uv run gww --help
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### From source using pip
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# From a local checkout
|
|
118
|
+
cd git-worktree-wrapper
|
|
119
|
+
python -m pip install .
|
|
120
|
+
gww --help
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 🚀 Quick Start
|
|
124
|
+
|
|
125
|
+
### 1. ⚙️ Initialize Configuration
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
gww init config
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
This creates a default configuration file at `~/.config/gww/config.yml` (Linux), `~/Library/Application Support/gww/config.yml` (macOS), or `%APPDATA%\gww\config.yml` (Windows). On any platform, exporting `XDG_CONFIG_HOME` to an absolute path overrides the default and stores the config there instead. Edit these 2 values: `default_sources` and `default_worktrees`. Check the [tutorial section](#tutorial) for routing details.
|
|
132
|
+
|
|
133
|
+
### 2. 🐚 Initialize Shell Integration
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
gww init shell zsh # or bash, or fish
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This installs shell completion and aliases (`gwc`, `gwa`, `gwr`) for easier workflow. Follow the instructions printed by the command to enable them in your shell.
|
|
140
|
+
|
|
141
|
+
### 3. 📥 Clone a Repository
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
gwc https://github.com/user/repo.git
|
|
145
|
+
# Prompts: "Navigate to ~/Developer/sources/github/user/repo? [Y/n]"
|
|
146
|
+
# Navigates if you confirm (default: yes)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 4. ➕ Add a Worktree
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd ~/Developer/sources/github/user/repo
|
|
153
|
+
gwa feature-branch
|
|
154
|
+
# Prompts: "Navigate to ~/Developer/worktrees/github/user/repo/feature-branch? [Y/n]"
|
|
155
|
+
# Navigates if you confirm (default: yes)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 5. ➖ Remove a Worktree
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
gwr feature-branch
|
|
162
|
+
# If worktree has uncommitted changes or untracked files:
|
|
163
|
+
# Prompts: "Force removal? [y/N]"
|
|
164
|
+
# Removes with --force if you confirm
|
|
165
|
+
# Otherwise: Removes worktree immediately
|
|
166
|
+
# Output: Removed worktree: ~/Developer/worktrees/github/user/repo/feature-branch
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 6. 🔄 Update Source Repository
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
gww pull
|
|
173
|
+
# Output: Updated source repository: ~/Developer/sources/github/user/repo
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Note**: `gww pull` updates the source repository even from a worktree, as long as the source is clean and has `main` or `master` checked out. Useful for merge/rebase workflows.
|
|
177
|
+
```bash
|
|
178
|
+
gww pull # from any repository worktree
|
|
179
|
+
git rebase main # rebase your current changes to updated main branch
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 7. 🚚 Migrate Repositories
|
|
183
|
+
Create a backup first!
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
gww migrate ~/old-repos --dry-run
|
|
187
|
+
# Output:
|
|
188
|
+
# Would migrate 5 repositories:
|
|
189
|
+
# ~/old-repos/repo1 -> ~/Developer/sources/github/user/repo1
|
|
190
|
+
# ...
|
|
191
|
+
|
|
192
|
+
gww migrate ~/old-repos
|
|
193
|
+
# Copy (default): list, copy sources then worktrees, repair, summary
|
|
194
|
+
|
|
195
|
+
gww migrate ~/old-repos --inplace
|
|
196
|
+
# Move worktrees then sources, repair, clean empty folders
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The `migrate` command scans one or more directories for git repositories and migrates them to locations based on your current configuration. It's useful when:
|
|
200
|
+
- You've updated your configuration and want to reorganize existing repositories
|
|
201
|
+
- You're moving from manual repository management to GWW
|
|
202
|
+
- You need to consolidate repositories from different locations
|
|
203
|
+
|
|
204
|
+
**Options**:
|
|
205
|
+
- `--dry-run`, `-n`: Show what would be migrated without making changes
|
|
206
|
+
- `--copy` (default): Copy repositories to new locations; list, validate, copy sources then worktrees, run `git worktree repair`, then report summary. No folder cleanup.
|
|
207
|
+
- `--inplace`: Move repositories in place (worktrees first, then sources), run `git worktree repair`, then recursively clean empty source folders.
|
|
208
|
+
|
|
209
|
+
**Behavior**:
|
|
210
|
+
- Accepts one or more paths; scans each and merges repo lists (deduplicated)
|
|
211
|
+
- Classifies each repo as source or worktree; uses source path template for sources and worktree path template for worktrees
|
|
212
|
+
- **--inplace**: Two passes (worktrees then sources), move and repair, then remove vacated dirs and empty parents up to input roots
|
|
213
|
+
- **--copy**: List sources and worktrees, validate destinations, copy sources then worktrees, repair relations, report summary
|
|
214
|
+
- Skips repositories without remotes, detached HEAD worktrees, or already at target
|
|
215
|
+
|
|
216
|
+
## Tutorial
|
|
217
|
+
|
|
218
|
+
A minimal config file looks like:
|
|
219
|
+
```yaml
|
|
220
|
+
# Folder where all sources are checked out with gwc. path(-2)/path(-1) generates 2-level subfolders based on repository URI. Like https://github.com/user/repo.git -> ~/Developer/sources/user/repo
|
|
221
|
+
default_sources: ~/Developer/other/sources/path(-2)/path(-1)
|
|
222
|
+
# Folder where all worktrees are checked out with gwa. norm_branch() works better with remote branches, e.g. origin/remote-branch -> origin-remote-branch
|
|
223
|
+
default_worktrees: ~/Developer/other/worktrees/path(-2)/path(-1)/norm_branch()
|
|
224
|
+
```
|
|
225
|
+
The generated file will have more options commented out, including the functions reference.
|
|
226
|
+
|
|
227
|
+
### Checkout based on where repository is hosted
|
|
228
|
+
Useful to separate e.g. open source projects (where you learn or get inspired) from your work projects.
|
|
229
|
+
```yaml
|
|
230
|
+
# Still needed in case the config fails to find a section. You may prefer a non-nested sources structure, but make sure the result folder is unique
|
|
231
|
+
default_sources: ~/Developer/sources/host()-path(-2)-path(-1)
|
|
232
|
+
default_worktrees: ~/Developer/worktrees/host()-path(-2)-path(-1)-norm_branch()
|
|
233
|
+
sources:
|
|
234
|
+
# ... other rules
|
|
235
|
+
work:
|
|
236
|
+
when: "your.org.host" in host()
|
|
237
|
+
sources: ~/Developer/work/sources/path(-2)-path(-1)
|
|
238
|
+
worktrees: ~/Developer/work/sources/path(-2)-path(-1)-norm_branch()
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
That's enough to separate work sources from all others, but you can create more sections with various rules. The library uses [simpleeval](https://github.com/danthedeckie/simpleeval) to evaluate templates, so you can use its [operators](https://github.com/danthedeckie/simpleeval?tab=readme-ov-file#operators) and functions below to get necessary routing.
|
|
242
|
+
|
|
243
|
+
#### 🌐 URI Functions (available in templates and `when` conditions)
|
|
244
|
+
|
|
245
|
+
| Function | Description | Example |
|
|
246
|
+
|----------|-------------|---------|
|
|
247
|
+
| `uri()` | Get full URI string | `uri()` → `"https://loca-repo-manager.com:8081/user/repo.git"` |
|
|
248
|
+
| `host()` | Get URI hostname | `host()` → `"loca-repo-manager.com"` |
|
|
249
|
+
| `port()` | Get URI port (empty string if not specified) | `port()` → `"8081"` or `""` usually |
|
|
250
|
+
| `protocol()` | Get URI protocol/scheme | `protocol()` → `"https"` / `"ssh"` / `git` |
|
|
251
|
+
| `path(n)` | Get URI path segment by index (0-based, negative for reverse) | `path(-1)` → `"repo"`, `path(0)` → `"user"` |
|
|
252
|
+
|
|
253
|
+
#### 🌿 Branch Functions (available in templates)
|
|
254
|
+
|
|
255
|
+
| Function | Description | Example |
|
|
256
|
+
|----------|-------------|---------|
|
|
257
|
+
| `branch()` | Get current branch name | `branch()` → `"feature/new/ui"` |
|
|
258
|
+
| `norm_branch(replacement)` | Branch name with `/` replaced (default: `"-"`) | `norm_branch()` → `"feature-new-ui"`, `norm_branch("_")` → `"feature_new_ui"` |
|
|
259
|
+
|
|
260
|
+
Need to checkout temporary projects separately? Add this to your config:
|
|
261
|
+
```yml
|
|
262
|
+
sources:
|
|
263
|
+
# ... other rules
|
|
264
|
+
temp:
|
|
265
|
+
when: tag_exist("temp") # See [tags section](#-tags) for details about tags
|
|
266
|
+
sources: ~/Downloads/temp/sources/time_id()-host()-path(-2)-path(-1)
|
|
267
|
+
worktrees: ~/Downloads/temp/worktrees/time_id()-host()-path(-2)-path(-1)-norm-branch()
|
|
268
|
+
```
|
|
269
|
+
`time_id(fmt)` generates a datetime-based identifier (cached per template evaluation). Default format is `"20260120-2134.03"` (short, seconds accuracy unique). Use [format codes](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) for more detailed/nested results. Works properly if used multiple times.
|
|
270
|
+
```yml
|
|
271
|
+
worktrees: ~/Downloads/temp/worktrees/time_id("%Y")/time_id("%m")/time_id("%H-%M$.%S")/host()-path(-2)-path(-1)-norm-branch()
|
|
272
|
+
```
|
|
273
|
+
Generates nested structure: `YYYY/HH-MM.ss/host()-path(-2)-path(-1)-norm-branch()`
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
#### ⚙️ Actions (available in `actions` section)
|
|
277
|
+
Run actions after checking out a repository, after adding a worktree, or **before removing a worktree**. Common example: copying `local.properties` for Gradle projects.
|
|
278
|
+
```yml
|
|
279
|
+
actions:
|
|
280
|
+
- when: file_exists("settings.gradle") # Check if it's actually a Gradle project
|
|
281
|
+
after_clone:
|
|
282
|
+
- copy: ["~/sources/default-local.properties", "local.properties"] # Copies your default file right after cloning the repo
|
|
283
|
+
after_add:
|
|
284
|
+
- copy: ["source_path('local.properties')", "local.properties"] # Inherit existing repository file to worktree
|
|
285
|
+
```
|
|
286
|
+
You can have multiple `when` subsections in actions. After clone/add, the library goes top-to-bottom and executes all actions with matching `when` conditions.
|
|
287
|
+
Other functions available in the actions section:
|
|
288
|
+
| Action | Description | Example |
|
|
289
|
+
|--------|-------------|---------|
|
|
290
|
+
| `copy` | Copy a file or directory tree from a template-evaluated source to a template-evaluated destination (relative to `current_worktree()`) | `copy: ["source_path('local.properties')", "local.properties"]` or `copy: ["~/sources/default-local.properties", "local.properties"]` |
|
|
291
|
+
| `command` | Execute external command (runs in destination directory, template functions available) | `command: "npm install"` or `command: "claude init"` |
|
|
292
|
+
|
|
293
|
+
Each action rule also accepts an optional `critical:` flag (default `true`). When `true`, a failing action in that rule aborts the remaining actions in the rule and makes the command exit `1`. Set `critical: false` for non-critical rules — failures are reported in the action execution summary but the command still exits `0`. See [Failure handling](#-failure-handling) below for the full exit-code table.
|
|
294
|
+
```yml
|
|
295
|
+
actions:
|
|
296
|
+
- when: file_exists("package.json")
|
|
297
|
+
critical: false # non-critical: a missing node_modules is annoying, not fatal
|
|
298
|
+
after_clone:
|
|
299
|
+
- command: "npm install"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
##### 🪓 `before_remove` — cleanup actions before `gww remove`
|
|
303
|
+
A third action kind, `before_remove`, runs user-defined cleanup steps **before** `git worktree remove` deletes the worktree. Use it to archive the worktree, post a notification, run a hook, etc. The same `critical:` / `command:` / `copy:` machinery applies; a critical `before_remove` failure aborts the remove and exits `1`, a non-critical failure is reported but the remove still proceeds. `--force` is git-only and does *not* bypass `before_remove`.
|
|
304
|
+
|
|
305
|
+
In `before_remove` rules, `current_worktree()` is the worktree being removed, `source_path()` is its parent source repo, and `branch()` resolves to the worktree's currently checked-out branch (or `""` on detached HEAD). The `gww remove` command also accepts `--tag key=value`, which flows into `tag()` / `tag_exist()` predicates so per-call cleanup can be tag-driven.
|
|
306
|
+
|
|
307
|
+
```yml
|
|
308
|
+
actions:
|
|
309
|
+
# Critical: archive the worktree before letting `gww remove` delete it.
|
|
310
|
+
- when: 'tag_exist("archive")'
|
|
311
|
+
before_remove:
|
|
312
|
+
- command: "tar -czf ~/archives/norm_branch()-time_id('%Y%m%d').tar.gz current_worktree()"
|
|
313
|
+
|
|
314
|
+
# Non-critical: notify a Slack channel. A failed notification should not block the remove.
|
|
315
|
+
- when: tag("notify") == "slack"
|
|
316
|
+
critical: false
|
|
317
|
+
before_remove:
|
|
318
|
+
- command: "curl -sf -X POST https://hooks.slack.com/... -d branch=branch()"
|
|
319
|
+
|
|
320
|
+
# Path-based remove: when removing by absolute path, `branch()` still resolves.
|
|
321
|
+
- when: branch() == "main"
|
|
322
|
+
before_remove:
|
|
323
|
+
- command: "echo refusing to remove main branch"
|
|
324
|
+
```
|
|
325
|
+
> Note: `gww remove` accepts an absolute worktree path as well as a branch name. When invoked with a path, `branch()` reads the worktree's current branch via `git rev-parse --abbrev-ref HEAD` and falls back to `""` for detached HEAD, so predicates referencing `branch()` never raise.
|
|
326
|
+
|
|
327
|
+
#### ❗ Failure handling
|
|
328
|
+
Action failures are reported per-rule, grouped at the end of the action loop on stderr as the **action execution summary**. The summary lists every failing rule by its index in `actions:`, its criticality flag, and the failing action's error. Its non-emptiness also gates the success line: `say()` (the line scripts `cd $(gwc …)`) is suppressed whenever the summary has any entry, so `cd` only ever lands on a fully-configured worktree.
|
|
329
|
+
|
|
330
|
+
| Outcome | Exit code |
|
|
331
|
+
|---|---|
|
|
332
|
+
| Clean run (no failures) | `0` |
|
|
333
|
+
| Non-critical rule failure only | `0` |
|
|
334
|
+
| Critical rule failure | `1` |
|
|
335
|
+
| `when:` predicate or `command:` template failed to evaluate | `2` |
|
|
336
|
+
|
|
337
|
+
#### 📁 Actions Functions (available in `command` actions and `when` conditions)
|
|
338
|
+
|
|
339
|
+
| Function | Description | Example |
|
|
340
|
+
|----------|-------------|---------|
|
|
341
|
+
| `source_path(extra?)` | Get absolute path to source repository, optionally joined with `extra` | `source_path()` → `"/path/to/repo"`, `source_path("local.properties")` → `"/path/to/repo/local.properties"` |
|
|
342
|
+
| `current_worktree(extra?)` | Get absolute path to the current worktree, optionally joined with `extra` | `current_worktree()` → `"/path/to/worktree"`, `current_worktree("local.properties")` → `"/path/to/worktree/local.properties"` |
|
|
343
|
+
| `file_exists(path)` | Check if file exists relative to source repository | `file_exists("local.properties")` → `True` |
|
|
344
|
+
| `dir_exists(path)` | Check if directory exists relative to source repository | `dir_exists("config")` → `True` |
|
|
345
|
+
| `path_exists(path)` | Check if path exists (file or directory) relative to source repository | `path_exists("local.properties")` → `True` |
|
|
346
|
+
|
|
347
|
+
#### 🏷️ Tags
|
|
348
|
+
|
|
349
|
+
Still not flexible enough? Here comes tags. Tags specified using command line param `-t <tag-name>[=optional value]` (or `--tag`) for clone / add commands. Tags available in configuration with:
|
|
350
|
+
|
|
351
|
+
| Function | Description | Example |
|
|
352
|
+
|----------|-------------|---------|
|
|
353
|
+
| `tag(name)` | Get tag value by name (returns empty string if not set) | `tag("env")` → `"prod"` |
|
|
354
|
+
| `tag_exist(name)` | Check if tag exists (returns boolean) | `tag_exist("env")` → `True` |
|
|
355
|
+
|
|
356
|
+
**🏷️ Tag Usage Example**:
|
|
357
|
+
```yaml
|
|
358
|
+
sources:
|
|
359
|
+
# Temporary checkout: Clone repositories to ~/Downloads/temp for quick access
|
|
360
|
+
# Usage: gwc <uri> -t temp
|
|
361
|
+
temp:
|
|
362
|
+
when: 'tag_exist("temp")'
|
|
363
|
+
sources: ~/Downloads/temp/time_id()-host()-path(-1)
|
|
364
|
+
worktrees: ~/Downloads/temp/time_id()-host()-path(-1)/norm_branch()
|
|
365
|
+
|
|
366
|
+
# Code review worktrees: Add worktrees to ~/Developer/worktree/code-review for review tasks
|
|
367
|
+
# Usage: gwa <branch> --tag review
|
|
368
|
+
review:
|
|
369
|
+
when: 'tag_exist("review")'
|
|
370
|
+
worktrees: ~/Developer/review/worktree/path(-1)/norm_branch()
|
|
371
|
+
# If used during clone, default source path is used
|
|
372
|
+
```
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
# Clone to temporary location
|
|
377
|
+
gwc https://github.com/user/repo.git -t temp
|
|
378
|
+
# Output: ~/Downloads/temp/repo
|
|
379
|
+
|
|
380
|
+
# Add worktree for code review
|
|
381
|
+
cd ~/Developer/sources/github/user/repo
|
|
382
|
+
gwa feature-branch --tag review
|
|
383
|
+
# Output: ~/Developer/worktree/code-review/repo/feature-branch
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## 📖 Commands
|
|
387
|
+
|
|
388
|
+
| Command | Description |
|
|
389
|
+
|---------|-------------|
|
|
390
|
+
| `gwc <uri> [--tag key=value]...` | 📥 Clone repository to configured location (tags available in templates/conditions) |
|
|
391
|
+
| `gwa <branch> [-c] [--tag key=value]...` | ➕ Add worktree for branch (optionally create branch, tags available in templates/conditions) |
|
|
392
|
+
| `gwr <branch\|path> [-f] [--tag key=value]...` | ➖ Remove worktree (tags available in `before_remove` predicates) |
|
|
393
|
+
| `gww pull` | 🔄 Update source repository (works from worktrees if source is clean and on main/master) |
|
|
394
|
+
| `gww migrate <path>... [--dry-run] [--copy \| --inplace]` | 🚚 Migrate repositories to new locations |
|
|
395
|
+
| `gww init config` | ⚙️ Create default configuration file |
|
|
396
|
+
| `gww init shell <shell>` | 🐚 Install shell completion (bash/zsh/fish) |
|
|
397
|
+
|
|
398
|
+
**Note**: `gwc`, `gwa`, and `gwr` are convenient shell aliases for `gww clone`, `gww add`, and `gww remove` respectively. They provide the same functionality with automatic navigation prompts. Install them with `gww init shell <shell>`.
|
|
399
|
+
|
|
400
|
+
**Common Options**:
|
|
401
|
+
- `--tag`, `-t`: Tag in the format `key=value` or just `key` (can be specified multiple times).
|
|
402
|
+
|
|
403
|
+
## 🔄 Update
|
|
404
|
+
|
|
405
|
+
### Using uv
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Re-run the install command to update to the latest version
|
|
409
|
+
uv tool install "git+https://github.com/vadimvolk/git-worktree-wrapper.git"
|
|
410
|
+
|
|
411
|
+
# Or use the update command (if available)
|
|
412
|
+
uv tool update gww
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Using pipx
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
pipx upgrade gww
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Using pip
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
python -m pip install --upgrade gww
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## 🗑️ Uninstall
|
|
428
|
+
|
|
429
|
+
### Using uv
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
uv tool uninstall gww
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
### Using pipx
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
pipx uninstall gww
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Using pip
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
python -m pip uninstall gww
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## 🛠️ Development
|
|
448
|
+
|
|
449
|
+
### 🧪 Running Tests
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# Run all tests
|
|
453
|
+
uv run pytest
|
|
454
|
+
|
|
455
|
+
# Run with coverage
|
|
456
|
+
uv run pytest --cov
|
|
457
|
+
|
|
458
|
+
# Run only unit tests
|
|
459
|
+
uv run pytest tests/unit/
|
|
460
|
+
|
|
461
|
+
# Run only integration tests
|
|
462
|
+
uv run pytest tests/integration/
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 🔍 Type Checking
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
uv run mypy src/gww
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## 📄 License
|
|
472
|
+
|
|
473
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
gww/__init__.py,sha256=vfQCT0-gHDKkxjf6xFD8aww8hH0pP2ogENG_rciXSZI,66
|
|
2
|
+
gww/actions/__init__.py,sha256=sFJGWqdKRy0dOpe-SJ4-I45tIwxHbggb-tljwRjH89M,8041
|
|
3
|
+
gww/actions/types.py,sha256=nwjNvB6uxaXs0YRRo_QvmX50fpW0lxLnve9bjPK-kTM,7004
|
|
4
|
+
gww/cli/__init__.py,sha256=mSTfwVlkSHus8bxd0nY_AjHU0G-Hj5fzI0baSkc3qE8,35
|
|
5
|
+
gww/cli/context.py,sha256=UR105KabnJb06lGSq6rT1KQ-gdWJ0F5E0AIfRuQUL2E,13826
|
|
6
|
+
gww/cli/main.py,sha256=UunuMuvjKO2zD_qcUbBNnnCu0E999_8fGpQ8Hi0on9Q,8419
|
|
7
|
+
gww/cli/commands/__init__.py,sha256=GYdoNv80KS2p7bCThuoRbhiLmIZW6ddPqPEFEx567nQ,35
|
|
8
|
+
gww/cli/commands/add.py,sha256=Slr-Xkx7jxIAl4VJUHkcJhgbT7k7h7V-bSosd45ge1I,3841
|
|
9
|
+
gww/cli/commands/clone.py,sha256=DZYyW_VfiJ9lJ-cZARdhOmxLkHvTFtmHRWA-Zk-0qAY,2904
|
|
10
|
+
gww/cli/commands/init.py,sha256=NyT43OVH_TvoW3BLaKfzTwAEzwwKhxL5_V9pMAx9IYs,4283
|
|
11
|
+
gww/cli/commands/migrate.py,sha256=6-NQnKcOxBsPZZLuSGMVUt_RDYWvaP4fNaBXi-1ddRA,2248
|
|
12
|
+
gww/cli/commands/pull.py,sha256=LqDKAD-LIjTEGIEelhlDLxUpiHJXz8VJdpW-ipNyy0M,1547
|
|
13
|
+
gww/cli/commands/remove.py,sha256=OJpjhRdeSZ4-YPDaQJpmwBLD_HWX9ueI17ni8HqMnZM,4782
|
|
14
|
+
gww/config/__init__.py,sha256=d00FAAaL_BSHLLmcjKtoc--KdvjeNCsojX0rObEBY8c,44
|
|
15
|
+
gww/config/loader.py,sha256=V4NQJP8QvXvXyZc2wpq_dA0SfQgFqynXLnBYTgxtLqY,11884
|
|
16
|
+
gww/config/resolver.py,sha256=FJR6mz8OHoRC0u1r2RCKVt6a0csxagurxSfyUoKuIaA,5086
|
|
17
|
+
gww/config/validator.py,sha256=TXDnmtiNn1KBtFiF6I2XLs_gQ_YrEVJKlU7sRQa02DQ,11001
|
|
18
|
+
gww/git/__init__.py,sha256=A4LQuKLpS4ZLNQbayH2JI01CfJJmXRSdLuY8d2w7v5A,30
|
|
19
|
+
gww/git/branch.py,sha256=-u9myGQdCaj8_vwqMjY7jHOWX8jeQItz-24yTQC3jmc,6280
|
|
20
|
+
gww/git/repository.py,sha256=Wk4f54QlefVqjqRsrSP1MNP8hpuV_pPHZaxC1lUNn-4,10469
|
|
21
|
+
gww/git/worktree.py,sha256=v8ujYxhp4FXL0QVkf6mgOAMArkmBP4lu0dlvOBNuYSg,11097
|
|
22
|
+
gww/migration/__init__.py,sha256=68rfmkXUBS42UGXtlSKcLG-u97kQYtBNVJY8N1hKCBM,1092
|
|
23
|
+
gww/migration/executor.py,sha256=nnM3TwaHOs9dsuIZ3Osl-ElB4l_cX5cwlDwNLXWop-U,12642
|
|
24
|
+
gww/migration/planner.py,sha256=2hz89kRw8KZE3wvyZi5QhoF0dluArRTekCbB4_EhGKs,8625
|
|
25
|
+
gww/template/__init__.py,sha256=dVv5IfFynYyrrYDDEu63FTcHNrqAG04TErWTaRQIz6g,34
|
|
26
|
+
gww/template/evaluator.py,sha256=NVeqfvvq8tJGhVHqjsejLjQg9cHm2Kbpd-2qhvqEKlc,8874
|
|
27
|
+
gww/template/functions.py,sha256=BEZpYsbXdYHkIMp1LEKc0DsK5uB5yAf_L0j10Ugfh3Q,12801
|
|
28
|
+
gww/utils/__init__.py,sha256=qHkwUQnAipyD7orncfDbJCaQ47mc02h90FgWy9ad_2o,25
|
|
29
|
+
gww/utils/shell.py,sha256=4AbJdFdh4zP7jD3o7eai_Mikzqc0-BT1GgrvoLooz-8,28663
|
|
30
|
+
gww/utils/uri.py,sha256=BtXPhvBz4rDoeFMKnFS_mGCXBAAzNnzBZQjMtk7r_vQ,4252
|
|
31
|
+
gww/utils/xdg.py,sha256=sozg8K2Secvbr-SOY96bMBRIFWvjPRUJT3mabuBBVw8,1969
|
|
32
|
+
git_worktree_wrapper-0.1.0.dist-info/METADATA,sha256=DW7ixqOIiIYO7s7DNM3oZ7jYTXGOwsctAQqeWYZLUqA,19430
|
|
33
|
+
git_worktree_wrapper-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
34
|
+
git_worktree_wrapper-0.1.0.dist-info/entry_points.txt,sha256=-nEszK-ay0Q-pKK5kMum48Ov4qZo-pUfAfmYwXLUePk,42
|
|
35
|
+
git_worktree_wrapper-0.1.0.dist-info/RECORD,,
|
gww/__init__.py
ADDED