git-patch 0.1.0 → 0.1.1
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.
- package/README.md +2 -0
- package/bin/git-patch.js +3 -2
- package/lib/commands/list.js +54 -6
- package/package.json +1 -1
- package/test/integration.test.js +53 -0
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ npx git-patch list
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
git-patch list # Human-readable hunk list
|
|
25
|
+
git-patch list --summary # One line per hunk (id + file/range + counts)
|
|
25
26
|
git-patch list --json # Structured JSON output
|
|
27
|
+
git-patch list --json --summary # Flat hunk summaries for scripts/LLMs
|
|
26
28
|
git-patch list --staged # Show staged hunks
|
|
27
29
|
git-patch list -- src/main.rs # Filter to specific files
|
|
28
30
|
```
|
package/bin/git-patch.js
CHANGED
|
@@ -10,7 +10,7 @@ function usage() {
|
|
|
10
10
|
console.log(`git-patch — Non-interactive hunk staging for LLMs
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
13
|
-
git-patch list [--json] [--staged] [-- files...]
|
|
13
|
+
git-patch list [--json] [--summary] [--staged] [-- files...]
|
|
14
14
|
git-patch stage <selector> [--all] [--matching <regex>] [-- files...]
|
|
15
15
|
git-patch unstage <selector> [--all] [--matching <regex>]
|
|
16
16
|
git-patch discard <selector> [--all] [--matching <regex>] [--yes] [--dry-run] [-- files...]
|
|
@@ -39,12 +39,13 @@ try {
|
|
|
39
39
|
args,
|
|
40
40
|
options: {
|
|
41
41
|
json: { type: "boolean", default: false },
|
|
42
|
+
summary: { type: "boolean", default: false },
|
|
42
43
|
staged: { type: "boolean", default: false },
|
|
43
44
|
},
|
|
44
45
|
strict: true,
|
|
45
46
|
});
|
|
46
47
|
let { run } = await import("../lib/commands/list.js");
|
|
47
|
-
run({ json: values.json, staged: values.staged, files });
|
|
48
|
+
run({ json: values.json, summary: values.summary, staged: values.staged, files });
|
|
48
49
|
break;
|
|
49
50
|
}
|
|
50
51
|
|
package/lib/commands/list.js
CHANGED
|
@@ -1,15 +1,59 @@
|
|
|
1
1
|
import { parseDiff } from "../diff-parser.js";
|
|
2
2
|
import { getDiff } from "../git.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
function formatDisplayRange(file, hunk) {
|
|
5
|
+
let span = Math.max(hunk.oldCount, hunk.newCount, 1);
|
|
6
|
+
let start = hunk.oldStart > 0 ? hunk.oldStart : hunk.newStart;
|
|
7
|
+
let end = start + span - 1;
|
|
8
|
+
return `${file.file}:${start}-${end}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function summarizeHunks(fileDiffs) {
|
|
12
|
+
let hunks = [];
|
|
13
|
+
|
|
14
|
+
for (let file of fileDiffs) {
|
|
15
|
+
for (let hunk of file.hunks) {
|
|
16
|
+
let oldEnd = hunk.oldCount === 0 ? hunk.oldStart : hunk.oldStart + hunk.oldCount - 1;
|
|
17
|
+
let newEnd = hunk.newCount === 0 ? hunk.newStart : hunk.newStart + hunk.newCount - 1;
|
|
18
|
+
|
|
19
|
+
hunks.push({
|
|
20
|
+
id: hunk.id,
|
|
21
|
+
file: file.file,
|
|
22
|
+
range: formatDisplayRange(file, hunk),
|
|
23
|
+
oldRange: {
|
|
24
|
+
start: hunk.oldStart,
|
|
25
|
+
end: oldEnd,
|
|
26
|
+
count: hunk.oldCount,
|
|
27
|
+
},
|
|
28
|
+
newRange: {
|
|
29
|
+
start: hunk.newStart,
|
|
30
|
+
end: newEnd,
|
|
31
|
+
count: hunk.newCount,
|
|
32
|
+
},
|
|
33
|
+
addedCount: hunk.addedCount,
|
|
34
|
+
removedCount: hunk.removedCount,
|
|
35
|
+
context: hunk.context,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return hunks;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function run({ json = false, staged = false, files = [], summary = false } = {}) {
|
|
5
44
|
let raw = getDiff({ staged, files });
|
|
6
45
|
let fileDiffs = parseDiff(raw);
|
|
7
46
|
|
|
8
47
|
if (json) {
|
|
9
|
-
let output =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
48
|
+
let output = summary
|
|
49
|
+
? {
|
|
50
|
+
type: staged ? "staged" : "unstaged",
|
|
51
|
+
hunks: summarizeHunks(fileDiffs),
|
|
52
|
+
}
|
|
53
|
+
: {
|
|
54
|
+
type: staged ? "staged" : "unstaged",
|
|
55
|
+
files: fileDiffs,
|
|
56
|
+
};
|
|
13
57
|
console.log(JSON.stringify(output, null, 2));
|
|
14
58
|
return;
|
|
15
59
|
}
|
|
@@ -23,11 +67,15 @@ export function run({ json = false, staged = false, files = [] } = {}) {
|
|
|
23
67
|
|
|
24
68
|
for (let file of fileDiffs) {
|
|
25
69
|
for (let hunk of file.hunks) {
|
|
26
|
-
let range =
|
|
70
|
+
let range = formatDisplayRange(file, hunk);
|
|
27
71
|
let counts = `(+${hunk.addedCount} -${hunk.removedCount})`;
|
|
28
72
|
let ctx = hunk.context ? ` ${hunk.context}` : "";
|
|
29
73
|
console.log(` ${hunk.id} ${range} ${counts}${ctx}`);
|
|
30
74
|
|
|
75
|
+
if (summary) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
31
79
|
// Show only change lines with their line indices
|
|
32
80
|
let changeIndex = 0;
|
|
33
81
|
for (let line of hunk.lines) {
|
package/package.json
CHANGED
package/test/integration.test.js
CHANGED
|
@@ -119,6 +119,30 @@ describe("git-patch integration", () => {
|
|
|
119
119
|
assert.match(out, /-.*Hello/);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
it("lists only hunk headers with --summary", () => {
|
|
123
|
+
writeFile(
|
|
124
|
+
"src/app.js",
|
|
125
|
+
[
|
|
126
|
+
"function greet(name) {",
|
|
127
|
+
' return "Hi, " + name;',
|
|
128
|
+
"}",
|
|
129
|
+
"",
|
|
130
|
+
"function farewell(name) {",
|
|
131
|
+
' return "Goodbye, " + name;',
|
|
132
|
+
"}",
|
|
133
|
+
"",
|
|
134
|
+
"module.exports = { greet, farewell };",
|
|
135
|
+
"",
|
|
136
|
+
].join("\n"),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
let out = gp("list --summary");
|
|
140
|
+
assert.match(out, /Unstaged changes/);
|
|
141
|
+
assert.match(out, /src\/app\.js/);
|
|
142
|
+
assert.doesNotMatch(out, /return "Hi, "/);
|
|
143
|
+
assert.doesNotMatch(out, /return "Hello, "/);
|
|
144
|
+
});
|
|
145
|
+
|
|
122
146
|
it("outputs valid JSON with --json", () => {
|
|
123
147
|
writeFile(
|
|
124
148
|
"src/utils.js",
|
|
@@ -144,6 +168,35 @@ describe("git-patch integration", () => {
|
|
|
144
168
|
assert.equal(out.files[0].hunks[0].id, 1);
|
|
145
169
|
});
|
|
146
170
|
|
|
171
|
+
it("outputs hunk summaries with --json --summary", () => {
|
|
172
|
+
writeFile(
|
|
173
|
+
"src/app.js",
|
|
174
|
+
[
|
|
175
|
+
"function greet(name) {",
|
|
176
|
+
' return "Hi, " + name;',
|
|
177
|
+
"}",
|
|
178
|
+
"",
|
|
179
|
+
"function farewell(name) {",
|
|
180
|
+
' return "Goodbye, " + name;',
|
|
181
|
+
"}",
|
|
182
|
+
"",
|
|
183
|
+
"module.exports = { greet, farewell };",
|
|
184
|
+
"",
|
|
185
|
+
].join("\n"),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
let out = JSON.parse(gp("list --json --summary"));
|
|
189
|
+
assert.equal(out.type, "unstaged");
|
|
190
|
+
assert.equal(out.hunks.length, 1);
|
|
191
|
+
assert.equal(out.hunks[0].id, 1);
|
|
192
|
+
assert.equal(out.hunks[0].file, "src/app.js");
|
|
193
|
+
assert.equal(out.hunks[0].addedCount, 1);
|
|
194
|
+
assert.equal(out.hunks[0].removedCount, 1);
|
|
195
|
+
assert.ok(out.hunks[0].range.startsWith("src/app.js:"));
|
|
196
|
+
assert.equal(out.hunks[0].oldRange.start, 1);
|
|
197
|
+
assert.equal(out.hunks[0].newRange.start, 1);
|
|
198
|
+
});
|
|
199
|
+
|
|
147
200
|
it("lists staged hunks with --staged", () => {
|
|
148
201
|
writeFile(
|
|
149
202
|
"src/app.js",
|