-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgit-word
More file actions
executable file
·306 lines (249 loc) · 13.1 KB
/
Copy pathgit-word
File metadata and controls
executable file
·306 lines (249 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#!/usr/bin/env python3
"""git word -- a bidirectional commitword bridge for the git command line.
git word mint the commitword for HEAD
git word <commit-ish> mint the commitword for a commit (SHA, tag, HEAD~3, ...)
git word <commitword> resolve a commitword to its commit's FULL sha
git word <git-command> [args] run a git command, resolving commitword args first
Direction is auto-detected from the first argument: a commitword is resolved, a
git revision is minted, and anything else is treated as a git subcommand whose
commitword arguments are resolved before it runs -- so these are equivalent:
git show $(git word inner-19-sage)
git word show inner-19-sage
Resolving prints the bare full sha, so a commitword also drops into any command
by substitution. Force mint/resolve with --mint / --find. When minting or
resolving, other flags pass through to commitmint.py / commitfind.py, e.g.
`git word HEAD --list`, `git word HEAD --sep`, `git word inner-19-sage --head-only`.
Default mint preferences come from `git config commitword.*` (the command line
always wins):
commitword.sep - or _ decorate output (or install.sh --sep)
commitword.floor <N> margin-floor bits (-> --floor N)
commitword.select strongest | shortest | vibe:<criterion> | reach-floor |
three the one mint mode (unset = default pick;
three -> --three). Any mode flag on the
command line overrides it.
These are read repo-scoped (a global default applies everywhere, a repo overrides).
The commitword.llm.* connection below is the exception -- it's global-only.
`git word HEAD --vibe funny` asks an LLM to pick the most-<vibe> code. Configure
it once with `git config --global commitword.llm.<provider|endpoint|model>`
(provider: ollama (default) or openai-compatible). Only **global** config is
read -- never a repo's local .git/config, which a hostile clone could use to
redirect your LLM calls (and key). Set the API key in the environment, not git
config: `COMMITWORD_LLM_KEY` (a token doesn't belong in plaintext gitconfig). An
explicit COMMITWORD_LLM_* env var overrides the git-config value.
Installed as a git subcommand: put this file on PATH as `git-word` (the symlink
name sets the verb -- `git-w` gives `git w show inner-19-sage`), or wire a git
alias -- see the README "Git integration".
"""
import os
import re
import subprocess
import sys
HERE = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, HERE)
import commitword as sw # noqa: E402 (after sys.path)
# Long options that always consume a following value, so the scan for the
# subject (the first bareword) skips their values. --sep/--list take an optional
# value and are intentionally NOT listed; put the subject first to avoid ambiguity.
VALUE_OPTS = {"-C", "--repo", "--growth", "--pmax", "--floor", "--choose", "--vibe"}
USAGE = ("usage: git word [--mint|--find] [<commit-ish>|<commitword>] [flags...]\n"
" or: git word <git-command> [args... including commitwords]")
def find_subject(args):
"""First bareword in `args` that isn't an option or an option's value."""
i = 0
while i < len(args):
a = args[i]
if a == "--":
return args[i + 1] if i + 1 < len(args) else None
if a in VALUE_OPTS:
i += 2 # skip the option and its value
continue
if a.startswith("-") and a != "-":
i += 1 # a flag (or --opt=val); not the subject
continue
return a # a bareword: the commit / commitword
return None
def _exec(argv):
"""Hand off to `argv`, replacing this process. POSIX os.exec* replaces the
process image, so the child's output stays in front of the shell prompt. On
Windows os.exec* instead spawns a *detached* child and this process returns,
so the shell prints its next prompt before the child's output appears -- so
there, spawn-and-wait and propagate the child's exit code."""
if os.name == "nt":
sys.exit(subprocess.run(argv).returncode)
os.execvp(argv[0], argv)
def exec_tool(name, args):
"""Replace this process with `name` (commitmint.py / commitfind.py)."""
_exec([sys.executable, os.path.join(HERE, name), *args])
def repo_from(args):
"""The -C/--repo value in `args` (so the rev check and commitword resolution
use the repo the user pointed at), else the current directory."""
for i, a in enumerate(args):
if a in ("-C", "--repo"):
return args[i + 1] if i + 1 < len(args) else "."
if a.startswith("--repo="):
return a.split("=", 1)[1]
if a.startswith("-C") and len(a) > 2:
return a[2:]
return "."
# Mint preferences from `git config commitword.<key>` (NOT commitword.llm.*, the
# global-only LLM connection). `git word` injects these as flags on the mint path
# -- a personal default for how to mint -- and an explicit flag on the command
# line always wins. Read repo-scoped (merged global + local), so a global default
# applies everywhere and a repo can override, exactly as commitword.sep always has.
def mint_config(repo):
"""{key: value} for every commitword.<key> mint pref, excluding the
commitword.llm.* connection subsection."""
r = subprocess.run(["git", "-C", repo, "config", "--get-regexp", r"^commitword\."],
capture_output=True, text=True)
cfg = {}
for line in r.stdout.splitlines():
name, _, val = line.partition(" ")
key = name[len("commitword."):]
if "." not in key: # skip commitword.llm.* etc.
cfg[key.lower()] = val.strip()
return cfg
def _has(args, *flags):
"""True if any of `flags` (or its `=value` form) is already in `args`."""
return any(a == f or a.startswith(f + "=") for f in flags for a in args)
# Flags that choose the mint *mode*. commitword.select is one mutually-exclusive
# mode, so ANY of these on the command line overrides the configured default whole.
_MODE_FLAGS = ("--strongest", "--shortest", "--vibe", "--choose", "--list",
"-i", "--interactive", "--three", "--reach-floor")
def _select_flags(select):
"""commitmint flags for a `commitword.select` value, or [] if unrecognized."""
table = {"strongest": ["--strongest"], "shortest": ["--shortest"],
"reach-floor": ["--reach-floor"], "three": ["--three"]}
if select in table:
return table[select]
if select.startswith("vibe:"):
crit = select[len("vibe:"):].strip()
return ["--vibe", crit] if crit else []
return []
def with_mint_defaults(args, repo):
"""Append configured commitword.* mint defaults to a mint arg list, each only
when the command line didn't already specify it (the command line wins). The
`select` mode is one unit -- suppressed entirely if any mode flag is present."""
cfg = mint_config(repo)
extra = []
sep = cfg.get("sep")
if sep in ("-", "_") and not _has(args, "--sep"):
extra.append(f"--sep={sep}")
if "floor" in cfg and not _has(args, "--floor"):
extra += ["--floor", cfg["floor"]]
select = cfg.get("select")
if select and not _has(args, *_MODE_FLAGS):
extra += _select_flags(select)
return [*args, *extra]
# `git config commitword.llm.<provider|endpoint|model>` -> COMMITWORD_LLM_* env,
# so `git word ... --vibe` reads its LLM settings from git config like
# commitword.sep. The API key is intentionally NOT sourced from git config (a
# secret doesn't belong in plaintext ~/.gitconfig) -- set COMMITWORD_LLM_KEY in
# your environment instead.
_LLM_CONFIG = {"provider": "COMMITWORD_LLM_PROVIDER",
"endpoint": "COMMITWORD_LLM_ENDPOINT",
"model": "COMMITWORD_LLM_MODEL",
"temperature": "COMMITWORD_LLM_TEMPERATURE",
"options": "COMMITWORD_LLM_OPTIONS",
"prompt": "COMMITWORD_LLM_PROMPT"}
def apply_llm_config():
"""Set each COMMITWORD_LLM_* var from **global** git config (commitword.llm.*),
only when it isn't already in the environment (so an explicit env var still
wins).
Global-only by design: reading these from repo-local config would let a
cloned repo's .git/config redirect your --vibe LLM calls -- and, on the
openai path, exfiltrate your COMMITWORD_LLM_KEY -- to an attacker's endpoint.
Per-repo LLM settings, if ever wanted, go through the (explicit) env vars."""
r = subprocess.run(["git", "config", "--global", "--get-regexp",
r"^commitword\.llm\."], capture_output=True, text=True)
for line in r.stdout.splitlines():
name, _, val = line.partition(" ")
env = _LLM_CONFIG.get(name.rsplit(".", 1)[-1])
if env and env not in os.environ and val.strip():
os.environ[env] = val.strip()
def exec_mint(base, repo):
"""Mint via commitmint.py, applying the git-config defaults (separator, floor,
select mode) and LLM settings first."""
apply_llm_config()
exec_tool("commitmint.py", with_mint_defaults(base, repo))
def is_git_rev(token, repo):
"""True if `token` names a commit in `repo` (so it should be minted)."""
r = subprocess.run(["git", "-C", repo, "rev-parse", "--verify", "--quiet",
token + "^{commit}"], capture_output=True, text=True)
return r.returncode == 0
def resolve_sha(token, repo):
"""Full SHA a commitword resolves to (first match), or None."""
r = subprocess.run([sys.executable, os.path.join(HERE, "commitfind.py"),
token, "-C", repo, "--sha"], capture_output=True, text=True)
out = r.stdout.split()
return out[0] if out else None
# A run of commitword-legal characters (letters, digits, and the `-`/`_`
# separators). `.` is not a commitword character, so it naturally delimits --
# which is what lets a range like `<cw>..<cw>` resolve as two tokens.
_CWORD_RUN = re.compile(r"[A-Za-z0-9_-]+")
def translate_arg(arg, repo):
"""Replace each commitword-shaped run in `arg` that actually resolves to a
commit with its full SHA -- so rev-expressions work: `<cw>..<cw>`, `<cw>~3`,
`<cw>^`, `<cw>:path`. A run that git already understands as a revision (a
ref, SHA, or HEAD) is left untouched, so real refs always win."""
def repl(m):
tok = m.group(0)
if sw.decode_to_bits(tok) is None: # not commitword-shaped
return tok
if is_git_rev(tok, repo): # a real ref/SHA -> git's
return tok
return resolve_sha(tok, repo) or tok # resolve, else leave for git
return _CWORD_RUN.sub(repl, arg)
def run_git(args, repo):
"""Pass-through: run `git <args>`, resolving commitword tokens to SHAs first
(including inside rev-expressions). Put `-C <path>` before the subcommand,
as git itself requires."""
_exec(["git", *[translate_arg(a, repo) for a in args]])
def options_block(name, title):
"""The `options:` section of a sub-tool's --help, minus the redundant
-h/--help line, so `git word -h` can show every pass-through flag (read
live, so it never drifts out of sync with the tools)."""
out = subprocess.run([sys.executable, os.path.join(HERE, name), "--help"],
capture_output=True, text=True).stdout.splitlines()
try:
start = next(i for i, ln in enumerate(out) if ln.strip() == "options:")
except StopIteration:
return ""
kept = [ln for ln in out[start + 1:] if not ln.strip().startswith("-h, --help")]
return title + "\n" + "\n".join(kept)
def main():
argv = sys.argv[1:]
if "-h" in argv or "--help" in argv:
print(USAGE + "\n\n" + __doc__.strip() + "\n")
print(options_block("commitmint.py",
"mint flags (git word <commit-ish> ...):"))
print()
print(options_block("commitfind.py",
"resolve flags (git word <commitword> ...):"))
return
force = None
passthrough = []
for a in argv:
if a == "--mint":
force = "mint"
elif a == "--find":
force = "find"
else:
passthrough.append(a)
subject = find_subject(passthrough)
repo = repo_from(passthrough)
if force == "find":
if subject is None:
sys.exit("git word: --find needs a commitword to resolve\n" + USAGE)
exec_tool("commitfind.py", [*passthrough, "--sha"])
if force == "mint":
exec_mint(passthrough if subject else ["HEAD", *passthrough], repo)
# Auto-detect direction from the first argument.
if subject is None: # bare `git word` -> mint HEAD
exec_mint(["HEAD", *passthrough], repo)
if sw.decode_to_bits(subject) is not None: # a commitword -> resolve
exec_tool("commitfind.py", [*passthrough, "--sha"])
if is_git_rev(subject, repo): # a git revision -> mint
exec_mint(passthrough, repo)
run_git(passthrough, repo) # a git subcommand -> pass through
if __name__ == "__main__":
main()