git-omit 0.0.1__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_omit/__init__.py +6 -0
- git_omit/__main__.py +3 -0
- git_omit/_version.py +1 -0
- git_omit/_version.pyi +1 -0
- git_omit/cli.py +188 -0
- git_omit-0.0.1.dist-info/METADATA +46 -0
- git_omit-0.0.1.dist-info/RECORD +9 -0
- git_omit-0.0.1.dist-info/WHEEL +4 -0
- git_omit-0.0.1.dist-info/entry_points.txt +2 -0
git_omit/__init__.py
ADDED
git_omit/__main__.py
ADDED
git_omit/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1"
|
git_omit/_version.pyi
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__: str
|
git_omit/cli.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from git_omit import __version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GitError(RuntimeError):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _git(*args: str) -> subprocess.CompletedProcess[str]:
|
|
14
|
+
return subprocess.run(
|
|
15
|
+
["git", *args],
|
|
16
|
+
check=False,
|
|
17
|
+
stdout=subprocess.PIPE,
|
|
18
|
+
stderr=subprocess.PIPE,
|
|
19
|
+
text=True,
|
|
20
|
+
encoding="utf-8",
|
|
21
|
+
errors="surrogateescape",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _git_stdout(*args: str) -> str:
|
|
26
|
+
result = _git(*args)
|
|
27
|
+
if result.returncode != 0:
|
|
28
|
+
message = result.stderr.strip() or "git command failed"
|
|
29
|
+
raise GitError(message)
|
|
30
|
+
return result.stdout.strip()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _git_path(path: str) -> Path:
|
|
34
|
+
value = _git_stdout("rev-parse", "--git-path", path)
|
|
35
|
+
return Path(value).resolve()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _exclude_path() -> Path:
|
|
39
|
+
return _git_path("info/exclude")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _read_lines(path: Path) -> list[str]:
|
|
43
|
+
if not path.exists():
|
|
44
|
+
return []
|
|
45
|
+
return path.read_text(encoding="utf-8").splitlines()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _write_lines(path: Path, lines: list[str]) -> None:
|
|
49
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
content = "\n".join(lines)
|
|
51
|
+
if content:
|
|
52
|
+
content += "\n"
|
|
53
|
+
path.write_text(content, encoding="utf-8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def hidden_patterns() -> list[str]:
|
|
57
|
+
return [
|
|
58
|
+
line
|
|
59
|
+
for line in _read_lines(_exclude_path())
|
|
60
|
+
if line.strip() and not line.lstrip().startswith("#")
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def hide(patterns: list[str]) -> None:
|
|
65
|
+
path = _exclude_path()
|
|
66
|
+
lines = _read_lines(path)
|
|
67
|
+
existing = set(lines)
|
|
68
|
+
changed = False
|
|
69
|
+
|
|
70
|
+
for pattern in patterns:
|
|
71
|
+
value = pattern.strip()
|
|
72
|
+
if value and value not in existing:
|
|
73
|
+
lines.append(value)
|
|
74
|
+
existing.add(value)
|
|
75
|
+
changed = True
|
|
76
|
+
|
|
77
|
+
if changed:
|
|
78
|
+
_write_lines(path, lines)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def unhide(patterns: list[str]) -> None:
|
|
82
|
+
targets = {pattern.strip() for pattern in patterns if pattern.strip()}
|
|
83
|
+
if not targets:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
path = _exclude_path()
|
|
87
|
+
lines = _read_lines(path)
|
|
88
|
+
next_lines = [line for line in lines if line not in targets]
|
|
89
|
+
if len(next_lines) != len(lines):
|
|
90
|
+
_write_lines(path, next_lines)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def freeze(paths: list[str]) -> None:
|
|
94
|
+
_run_update_index("--skip-worktree", paths)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def unfreeze(paths: list[str]) -> None:
|
|
98
|
+
_run_update_index("--no-skip-worktree", paths)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _run_update_index(flag: str, paths: list[str]) -> None:
|
|
102
|
+
result = _git("update-index", flag, "--", *paths)
|
|
103
|
+
if result.returncode != 0:
|
|
104
|
+
message = result.stderr.strip() or "git update-index failed"
|
|
105
|
+
raise GitError(message)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def frozen_paths() -> list[str]:
|
|
109
|
+
result = _git("ls-files", "-v", "-z")
|
|
110
|
+
if result.returncode != 0:
|
|
111
|
+
message = result.stderr.strip() or "git ls-files failed"
|
|
112
|
+
raise GitError(message)
|
|
113
|
+
|
|
114
|
+
paths: list[str] = []
|
|
115
|
+
for record in result.stdout.split("\0"):
|
|
116
|
+
if record.startswith("S "):
|
|
117
|
+
paths.append(record[2:])
|
|
118
|
+
return paths
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def list_items() -> None:
|
|
122
|
+
for pattern in hidden_patterns():
|
|
123
|
+
print(f"hide\t{pattern}")
|
|
124
|
+
for path in frozen_paths():
|
|
125
|
+
print(f"freeze\t{path}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
129
|
+
parser = argparse.ArgumentParser(
|
|
130
|
+
prog="git-omit",
|
|
131
|
+
description="Keep local Git noise local.",
|
|
132
|
+
)
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"--version",
|
|
135
|
+
action="version",
|
|
136
|
+
version=f"%(prog)s {__version__}",
|
|
137
|
+
)
|
|
138
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
139
|
+
|
|
140
|
+
hide_parser = subparsers.add_parser(
|
|
141
|
+
"hide", help="Add patterns to .git/info/exclude"
|
|
142
|
+
)
|
|
143
|
+
hide_parser.add_argument("patterns", nargs="+")
|
|
144
|
+
|
|
145
|
+
unhide_parser = subparsers.add_parser(
|
|
146
|
+
"unhide", help="Remove patterns from .git/info/exclude"
|
|
147
|
+
)
|
|
148
|
+
unhide_parser.add_argument("patterns", nargs="+")
|
|
149
|
+
|
|
150
|
+
freeze_parser = subparsers.add_parser(
|
|
151
|
+
"freeze", help="Mark tracked files as skip-worktree"
|
|
152
|
+
)
|
|
153
|
+
freeze_parser.add_argument("paths", nargs="+")
|
|
154
|
+
|
|
155
|
+
unfreeze_parser = subparsers.add_parser(
|
|
156
|
+
"unfreeze", help="Clear skip-worktree from tracked files"
|
|
157
|
+
)
|
|
158
|
+
unfreeze_parser.add_argument("paths", nargs="+")
|
|
159
|
+
|
|
160
|
+
subparsers.add_parser("list", help="List hidden patterns and frozen paths")
|
|
161
|
+
return parser
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def cli(argv: list[str] | None = None) -> int:
|
|
165
|
+
parser = build_parser()
|
|
166
|
+
args = parser.parse_args(argv)
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
match args.command:
|
|
170
|
+
case "hide":
|
|
171
|
+
hide(args.patterns)
|
|
172
|
+
case "unhide":
|
|
173
|
+
unhide(args.patterns)
|
|
174
|
+
case "freeze":
|
|
175
|
+
freeze(args.paths)
|
|
176
|
+
case "unfreeze":
|
|
177
|
+
unfreeze(args.paths)
|
|
178
|
+
case "list":
|
|
179
|
+
list_items()
|
|
180
|
+
except GitError as exc:
|
|
181
|
+
print(f"git-omit: {exc}", file=sys.stderr)
|
|
182
|
+
return 1
|
|
183
|
+
|
|
184
|
+
return 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
raise SystemExit(cli())
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: git-omit
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Small Git helpers for local-only ignores and tracked files you want Git to leave alone
|
|
5
|
+
Author-email: Noai-oss <jiuwoxiao@outlook.com>
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# git-omit
|
|
10
|
+
|
|
11
|
+
Small Git helpers for local-only ignores and tracked files you want Git to leave alone.
|
|
12
|
+
|
|
13
|
+
## Local ignores
|
|
14
|
+
|
|
15
|
+
| Kind | Git mechanism | Use when |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Untracked | `.git/info/exclude` | The file should stay local and untracked |
|
|
18
|
+
| Tracked | `skip-worktree` | The file is tracked, but local changes should be left alone |
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
uv tool install git-omit
|
|
24
|
+
# or from GitHub
|
|
25
|
+
uv tool install git+https://github.com/Noai-oss/git-omit
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Commands
|
|
29
|
+
|
|
30
|
+
| Command | Example | Effect |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| `git-omit hide <pattern>...` | `git-omit hide '*.log'` | Add patterns to `.git/info/exclude` |
|
|
33
|
+
| `git-omit unhide <pattern>...` | `git-omit unhide '*.log'` | Remove patterns from `.git/info/exclude` |
|
|
34
|
+
| `git-omit freeze <path>...` | `git-omit freeze config.local.json` | Mark tracked files as `skip-worktree` |
|
|
35
|
+
| `git-omit unfreeze <path>...` | `git-omit unfreeze config.local.json` | Clear `skip-worktree` |
|
|
36
|
+
| `git-omit list` | `git-omit list` | Print hidden patterns and frozen paths |
|
|
37
|
+
| `git-omit --version` | `git-omit --version` | Print the version |
|
|
38
|
+
|
|
39
|
+
We recommend quoting glob patterns, like `'*.log'`, so your shell passes them unchanged.
|
|
40
|
+
|
|
41
|
+
`git-omit list` prints tab-separated lines:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
hide *.log
|
|
45
|
+
freeze config.local.json
|
|
46
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
git_omit/__init__.py,sha256=0VRY0I6G4MGwHEY4kTlK2iqXyv2PB7ueHrYt6WMm74k,116
|
|
2
|
+
git_omit/__main__.py,sha256=TOJrcKFbI6FZfkCu5urVrBqu8GnpP45SUDpJFE4cCDE,46
|
|
3
|
+
git_omit/_version.py,sha256=ry5O5SW74ugwOUMKSV2_LbWjYa8B0IEvUm7S-lHPA2c,23
|
|
4
|
+
git_omit/_version.pyi,sha256=Y25n44pyE3vp92MiABKrcK3IWRyQ1JG1rZ4Ufqy2nC0,17
|
|
5
|
+
git_omit/cli.py,sha256=6EyaJyyrXpu8WLW6KXm8AGxkHMSGQxFji6bNRaBhMG4,4844
|
|
6
|
+
git_omit-0.0.1.dist-info/METADATA,sha256=daWGXhX2vlJ3IxhXLTQ6J_XWZBBoKwQCIKGKUFTqhaw,1544
|
|
7
|
+
git_omit-0.0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
8
|
+
git_omit-0.0.1.dist-info/entry_points.txt,sha256=RmtMe53O3dY7j0Uq2sJi-PcuJlvJSlULm2mYn5w5Zuw,46
|
|
9
|
+
git_omit-0.0.1.dist-info/RECORD,,
|