git-worm 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.
@@ -0,0 +1,44 @@
1
+ name: CI
2
+ on:
3
+ schedule:
4
+ - cron: "0 0 1 * *" # Run every month
5
+ # This way, we can make sure code doesn't break via external dependencies
6
+ push:
7
+ pull_request:
8
+ jobs:
9
+ test:
10
+ strategy:
11
+ matrix:
12
+ python-version:
13
+ - "3.12"
14
+ os: [ubuntu-latest, macos-latest, windows-latest]
15
+ runs-on: ${{ matrix.os }}
16
+ steps:
17
+ - name: Check out repository code
18
+ uses: actions/checkout@v4
19
+ - name: Set up uv
20
+ uses: astral-sh/setup-uv@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+ enable-cache: true
24
+ - name: Install just
25
+ uses: extractions/setup-just@v2
26
+ - name: Install dependencies
27
+ run: just install
28
+ - name: Run test suite
29
+ run: just test
30
+ - name: Upload to CodeCov
31
+ uses: codecov/codecov-action@v5
32
+ with:
33
+ token: ${{ secrets.CODECOV_TOKEN }}
34
+ build:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - name: Check out repository code
38
+ uses: actions/checkout@v4
39
+ - name: Set up uv
40
+ uses: astral-sh/setup-uv@v5
41
+ - name: Install just
42
+ uses: extractions/setup-just@v2
43
+ - name: Build project
44
+ run: just build
@@ -0,0 +1,24 @@
1
+ name: Release Asset Uploader
2
+ on:
3
+ release:
4
+ types:
5
+ - created
6
+ permissions:
7
+ contents: write
8
+ id-token: write
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Set up uv
15
+ uses: astral-sh/setup-uv@v5
16
+ - name: Build artifacts
17
+ run: uv build
18
+ - name: Upload artifacts
19
+ uses: shogo82148/actions-upload-release-asset@v1
20
+ with:
21
+ upload_url: ${{ github.event.release.upload_url }}
22
+ asset_path: dist/*
23
+ - name: Publish to PyPI
24
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,12 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ .DS_Store
12
+ docs/superpowers/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,29 @@
1
+ default: test
2
+
3
+ # Run the test suite
4
+ test *args:
5
+ uv run pytest {{ args }}
6
+
7
+ # Run tests with coverage report
8
+ cov:
9
+ uv run pytest --cov=src/xclif --cov-report=term-missing
10
+
11
+ # Build the HTML docs
12
+ docs:
13
+ uv run sphinx-build docs docs/_build/html
14
+
15
+ # Serve docs locally with live reload
16
+ docs-serve:
17
+ uv run sphinx-autobuild docs docs/_build/html
18
+
19
+ # Build the distribution (wheel + sdist)
20
+ build:
21
+ uv build
22
+
23
+ # Remove build artifacts
24
+ clean:
25
+ rm -rf dist docs/_build
26
+
27
+ # Install all dependency groups
28
+ install:
29
+ uv sync --all-groups
git_worm-0.1.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.4
2
+ Name: git-worm
3
+ Version: 0.1.0
4
+ Summary: A better git worktree manager
5
+ Project-URL: Homepage, https://github.com/ThatXliner/git-worm
6
+ Project-URL: Repository, https://github.com/ThatXliner/git-worm
7
+ Project-URL: Issues, https://github.com/ThatXliner/git-worm/issues
8
+ Author-email: Bryan Hu <thatxliner@gmail.com>
9
+ License-Expression: Unlicense
10
+ License-File: LICENSE
11
+ Keywords: cli,git,worktree
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: The Unlicense (Unlicense)
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.12
20
+ Requires-Dist: xclif>=0.4.0
21
+ Description-Content-Type: text/markdown
22
+
23
+ # git-worm
24
+
25
+ > Git WORktree Manager
26
+
27
+ [![CI](https://github.com/ThatXliner/git-worm/actions/workflows/ci.yml/badge.svg)](https://github.com/ThatXliner/git-worm/actions/workflows/ci.yml)
28
+
29
+ https://github.com/user-attachments/assets/916af237-e959-43fc-9046-067929ad874f
30
+
31
+ A better git worktree manager. Built with [Xclif](https://github.com/ThatXliner/xclif).
32
+
33
+ ## Why?
34
+
35
+ `git worktree` is powerful but raw. git-worm adds:
36
+
37
+ - **Automatic file management** — gitignored files (`.env`, `.venv`, `node_modules`, etc.) are copied into new worktrees so switching feels like `git switch`
38
+ - **Smart package manager detection** — pnpm/bun/Yarn PnP users don't get unnecessary `node_modules` copies
39
+ - **Nice UI** — Rich-formatted output, tree views, colored status. You can also create multiple worktrees in one command!
40
+
41
+ ## Install
42
+
43
+ Requires Python 3.12+
44
+
45
+ ```bash
46
+ pip install git-worm
47
+ ```
48
+
49
+ Or with [uv](https://github.com/astral-sh/uv):
50
+
51
+ ```bash
52
+ uv tool install git-worm
53
+ ```
54
+
55
+ ## Usage
56
+
57
+ Because of [Xclif](https://github.com/ThatXliner/xclif), git-worm automatically gets a very nice-looking help page
58
+
59
+ ![Help](./help.png)
60
+
61
+ ```bash
62
+ # Create a new worktree (copies .env, .venv, etc. automatically)
63
+ git worm new feat-login
64
+
65
+ # Create from a specific ref
66
+ git worm new feat-login --from-ref main
67
+
68
+ # List all worktrees
69
+ git worm list
70
+
71
+ # Print worktree path
72
+ git worm switch feat-login
73
+
74
+ # Remove a worktree
75
+ git worm rm feat-login
76
+
77
+ # Remove even if dirty
78
+ git worm rm feat-login --force
79
+
80
+ # Shell integration (add to .bashrc/.zshrc)
81
+ eval "$(git-worm shell-init)"
82
+ # Then: worm switch feat-login (auto-cds)
83
+ ```
84
+
85
+ ### Shell integration
86
+
87
+ `git-worm shell-init` outputs a `worm` shell function that wraps `git-worm`. This is needed because `worm switch` needs to `cd` into the worktree directory, which a plain subprocess can't do. All other commands are forwarded to `git-worm` as-is.
88
+
89
+ Add this to your `.bashrc` or `.zshrc`:
90
+
91
+ ```bash
92
+ eval "$(git-worm shell-init)"
93
+ ```
94
+
95
+ Then use `worm` instead of `git-worm`:
96
+
97
+ ```bash
98
+ worm new feat-login
99
+ worm switch feat-login # cds into the worktree
100
+ worm list
101
+ ```
102
+
103
+ ## Configuration
104
+
105
+ Optional `.git-worm.toml` in your repo root:
106
+
107
+ ```toml
108
+ [settings]
109
+ worktree_dir = ".worktrees" # default
110
+
111
+ [[share]]
112
+ path = ".env*"
113
+ strategy = "copy"
114
+
115
+ [[share]]
116
+ path = "node_modules"
117
+ strategy = "ignore"
118
+
119
+ [[share]]
120
+ path = "target"
121
+ strategy = "symlink"
122
+ ```
123
+
124
+ Strategies: `copy`, `reflink` (COW, falls back to copy), `symlink`, `ignore`.
125
+
126
+ When a config file is present, it replaces the default behavior entirely.
127
+
128
+ ## Default Behavior (no config)
129
+
130
+ 1. All gitignored files/dirs are detected
131
+ 2. `.git/` and `.worktrees/` are excluded
132
+ 3. Files are plain-copied, directories are reflinked (with copy fallback)
133
+ 4. `node_modules/` is skipped if pnpm, bun, or Yarn PnP is detected
134
+
135
+ ## License
136
+
137
+ Public Domain
@@ -0,0 +1,115 @@
1
+ # git-worm
2
+
3
+ > Git WORktree Manager
4
+
5
+ [![CI](https://github.com/ThatXliner/git-worm/actions/workflows/ci.yml/badge.svg)](https://github.com/ThatXliner/git-worm/actions/workflows/ci.yml)
6
+
7
+ https://github.com/user-attachments/assets/916af237-e959-43fc-9046-067929ad874f
8
+
9
+ A better git worktree manager. Built with [Xclif](https://github.com/ThatXliner/xclif).
10
+
11
+ ## Why?
12
+
13
+ `git worktree` is powerful but raw. git-worm adds:
14
+
15
+ - **Automatic file management** — gitignored files (`.env`, `.venv`, `node_modules`, etc.) are copied into new worktrees so switching feels like `git switch`
16
+ - **Smart package manager detection** — pnpm/bun/Yarn PnP users don't get unnecessary `node_modules` copies
17
+ - **Nice UI** — Rich-formatted output, tree views, colored status. You can also create multiple worktrees in one command!
18
+
19
+ ## Install
20
+
21
+ Requires Python 3.12+
22
+
23
+ ```bash
24
+ pip install git-worm
25
+ ```
26
+
27
+ Or with [uv](https://github.com/astral-sh/uv):
28
+
29
+ ```bash
30
+ uv tool install git-worm
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ Because of [Xclif](https://github.com/ThatXliner/xclif), git-worm automatically gets a very nice-looking help page
36
+
37
+ ![Help](./help.png)
38
+
39
+ ```bash
40
+ # Create a new worktree (copies .env, .venv, etc. automatically)
41
+ git worm new feat-login
42
+
43
+ # Create from a specific ref
44
+ git worm new feat-login --from-ref main
45
+
46
+ # List all worktrees
47
+ git worm list
48
+
49
+ # Print worktree path
50
+ git worm switch feat-login
51
+
52
+ # Remove a worktree
53
+ git worm rm feat-login
54
+
55
+ # Remove even if dirty
56
+ git worm rm feat-login --force
57
+
58
+ # Shell integration (add to .bashrc/.zshrc)
59
+ eval "$(git-worm shell-init)"
60
+ # Then: worm switch feat-login (auto-cds)
61
+ ```
62
+
63
+ ### Shell integration
64
+
65
+ `git-worm shell-init` outputs a `worm` shell function that wraps `git-worm`. This is needed because `worm switch` needs to `cd` into the worktree directory, which a plain subprocess can't do. All other commands are forwarded to `git-worm` as-is.
66
+
67
+ Add this to your `.bashrc` or `.zshrc`:
68
+
69
+ ```bash
70
+ eval "$(git-worm shell-init)"
71
+ ```
72
+
73
+ Then use `worm` instead of `git-worm`:
74
+
75
+ ```bash
76
+ worm new feat-login
77
+ worm switch feat-login # cds into the worktree
78
+ worm list
79
+ ```
80
+
81
+ ## Configuration
82
+
83
+ Optional `.git-worm.toml` in your repo root:
84
+
85
+ ```toml
86
+ [settings]
87
+ worktree_dir = ".worktrees" # default
88
+
89
+ [[share]]
90
+ path = ".env*"
91
+ strategy = "copy"
92
+
93
+ [[share]]
94
+ path = "node_modules"
95
+ strategy = "ignore"
96
+
97
+ [[share]]
98
+ path = "target"
99
+ strategy = "symlink"
100
+ ```
101
+
102
+ Strategies: `copy`, `reflink` (COW, falls back to copy), `symlink`, `ignore`.
103
+
104
+ When a config file is present, it replaces the default behavior entirely.
105
+
106
+ ## Default Behavior (no config)
107
+
108
+ 1. All gitignored files/dirs are detected
109
+ 2. `.git/` and `.worktrees/` are excluded
110
+ 3. Files are plain-copied, directories are reflinked (with copy fallback)
111
+ 4. `node_modules/` is skipped if pnpm, bun, or Yarn PnP is detected
112
+
113
+ ## License
114
+
115
+ Public Domain
Binary file
Binary file
@@ -0,0 +1,47 @@
1
+ [project]
2
+ name = "git-worm"
3
+ version = "0.1.0"
4
+ description = "A better git worktree manager"
5
+ readme = "README.md"
6
+ license = "Unlicense"
7
+ requires-python = ">=3.12"
8
+ authors = [
9
+ { name = "Bryan Hu", email = "thatxliner@gmail.com" },
10
+ ]
11
+ keywords = ["git", "worktree", "cli"]
12
+ classifiers = [
13
+ "Development Status :: 4 - Beta",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: The Unlicense (Unlicense)",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Software Development :: Version Control :: Git",
20
+ ]
21
+ dependencies = [
22
+ "xclif>=0.4.0",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/ThatXliner/git-worm"
27
+ Repository = "https://github.com/ThatXliner/git-worm"
28
+ Issues = "https://github.com/ThatXliner/git-worm/issues"
29
+
30
+ [project.scripts]
31
+ git-worm = "git_worm.__main__:cli"
32
+
33
+ [build-system]
34
+ requires = ["hatchling"]
35
+ build-backend = "hatchling.build"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/git_worm"]
39
+
40
+ [tool.pytest.ini_options]
41
+ testpaths = ["tests"]
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "pytest>=8.0",
46
+ "pytest-cov>=7.1.0",
47
+ ]
File without changes
@@ -0,0 +1,8 @@
1
+ from xclif import Cli
2
+
3
+ from . import routes
4
+
5
+ cli = Cli.from_routes(routes, local_config=".git-worm.toml")
6
+
7
+ if __name__ == "__main__":
8
+ cli()
@@ -0,0 +1,35 @@
1
+ """Parse .git-worm.toml configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import tomllib
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ShareRule:
12
+ path: str
13
+ strategy: str # "copy", "reflink", "symlink", "ignore"
14
+
15
+
16
+ @dataclass
17
+ class Config:
18
+ worktree_dir: str = ".worktrees"
19
+ share_rules: list[ShareRule] = field(default_factory=list)
20
+
21
+
22
+ def load_config(path: Path) -> Config | None:
23
+ """Load config from a TOML file. Returns None if file doesn't exist."""
24
+ if not path.exists():
25
+ return None
26
+ with open(path, "rb") as f:
27
+ data = tomllib.load(f)
28
+ share_rules = [
29
+ ShareRule(path=rule["path"], strategy=rule["strategy"])
30
+ for rule in data.get("share", [])
31
+ ]
32
+ return Config(
33
+ worktree_dir=data.get("worktree_dir", ".worktrees"),
34
+ share_rules=share_rules,
35
+ )