This repository was archived by the owner on Jan 14, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgit_edit.lua
More file actions
341 lines (323 loc) · 12.6 KB
/
Copy pathgit_edit.lua
File metadata and controls
341 lines (323 loc) · 12.6 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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
local GitTool = require("codecompanion._extensions.gitcommit.tools.git").GitTool
---@class CodeCompanion.GitCommit.Tools.GitEdit
local GitEdit = {}
GitEdit.name = "git_edit"
GitEdit.description = "Tool for write-access Git operations like stage, unstage, branch creation, etc."
GitEdit.schema = {
type = "function",
["function"] = {
name = "git_edit",
description = "Execute various write-access Git operations.",
parameters = {
type = "object",
properties = {
operation = {
type = "string",
enum = {
"stage",
"unstage",
"commit",
"create_branch",
"checkout",
"stash",
"apply_stash",
"reset",
"gitignore_add",
"gitignore_remove",
"push",
"cherry_pick",
"revert",
"create_tag",
"delete_tag",
"merge",
"help",
},
description = "The write-access Git operation to perform.",
},
args = {
type = "object",
properties = {
files = {
type = "array",
items = { type = "string" },
description = "Required: List of files to stage/unstage (can use '.' for all files)",
},
branch_name = {
type = "string",
description = "Name of the branch",
},
checkout = {
type = "boolean",
description = "Whether to checkout new branch",
},
target = {
type = "string",
description = "Target branch or commit for checkout",
},
message = {
type = "string",
description = "Message for stash or commit",
},
commit_message = {
type = "string",
description = "Optional commit message for the commit operation. If not provided, will automatically analyze staged diff and generate Conventional Commit compliant message using format: type(scope): description with types: feat,fix,docs,style,refactor,perf,test,chore.",
},
amend = {
type = "boolean",
description = "Amend the last commit instead of creating a new one",
},
include_untracked = {
type = "boolean",
description = "Include untracked files in stash",
},
stash_ref = {
type = "string",
description = "Stash reference (e.g., stash@{0})",
},
commit_hash = {
type = "string",
description = "Commit hash or reference for reset",
},
mode = {
type = "string",
enum = { "soft", "mixed", "hard" },
description = "Reset mode",
},
gitignore_rule = {
type = "string",
description = "Rule to add or remove from .gitignore",
},
gitignore_rules = {
type = "array",
items = { type = "string" },
description = "Multiple rules to add or remove from .gitignore",
},
remote = {
type = "string",
description = "The name of the remote to push to (e.g., origin)",
},
branch = {
type = "string",
description = "The name of the branch to push or merge",
},
force = {
type = "boolean",
description = "Force push (DANGEROUS: overwrites remote history)",
},
set_upstream = {
type = "boolean",
description = "Set the upstream branch for the current local branch",
},
tags = {
type = "boolean",
description = "Push all tags",
},
single_tag_name = {
type = "string",
description = "The name of a single tag to push",
},
cherry_pick_commit_hash = {
type = "string",
description = "The commit hash to cherry-pick",
},
revert_commit_hash = {
type = "string",
description = "The commit hash to revert",
},
tag_name = {
type = "string",
description = "The name of the tag",
},
tag_message = {
type = "string",
description = "An optional message for an annotated tag",
},
tag_commit_hash = {
type = "string",
description = "An optional commit hash to tag",
},
},
additionalProperties = false,
},
},
required = { "operation" },
additionalProperties = false,
},
strict = true,
},
}
GitEdit.system_prompt = [[Execute write-access Git repository operations
When to use:
• When staging or unstaging file changes
• When creating or switching between branches
• When managing stashes and repository state
• When performing safe repository modifications
Best practices:
• Must verify Git repository before operations
• Always specify files parameter for stage/unstage operations
• Use '.' to stage all modified files or specific file paths
• For commit operations, if no commit_message provided, automatically generate AI message
• Auto-generation analyzes staged changes and creates Conventional Commit compliant messages
• Use format: type(scope): description with lowercase type and imperative verb description
• Include body with bullet points for complex changes, keep description under 50 characters
• Use the `set_upstream` option to create and track a remote branch if it doesn't exist
• Avoid force push operations that rewrite history
• Ensure file paths and branch names are valid
Available operations: stage, unstage, commit, create_branch, checkout, stash, apply_stash, reset, gitignore_add, gitignore_remove, push, cherry_pick, revert, create_tag, delete_tag, merge, help]]
GitEdit.cmds = {
function(self, args, input, output_handler)
local operation = args.operation
local op_args = args.args or {}
if operation == "help" then
local help_text = [[
Available write-access Git operations:
• stage/unstage: Stage/unstage files (requires files parameter)
• commit: Commit staged changes (automatically generates AI message from staged diff if no message provided)
• create_branch: Create new branch
• checkout: Switch branch/commit
• stash/apply_stash: Stash operations
• reset: Reset to specific commit
• gitignore_add: Add rule to .gitignore
• gitignore_remove: Remove rule from .gitignore
• push: Push changes to a remote repository (WARNING: force push is dangerous)
• cherry_pick: Apply changes from existing commits
• revert: Revert a commit
• create_tag: Create a new tag
• delete_tag: Delete a tag
• merge: Merge a branch into the current branch (requires branch parameter)
]]
return { status = "success", data = help_text }
end
if operation == "push" then
return GitTool.push_async(
op_args.remote,
op_args.branch,
op_args.force,
op_args.set_upstream,
op_args.tags,
op_args.single_tag_name,
output_handler
)
end
local success, output
if operation == "stage" then
if not op_args.files or #op_args.files == 0 then
return { status = "error", data = "No files specified for staging" }
end
success, output = GitTool.stage_files(op_args.files)
elseif operation == "unstage" then
if not op_args.files or #op_args.files == 0 then
return { status = "error", data = "No files specified for unstaging" }
end
success, output = GitTool.unstage_files(op_args.files)
elseif operation == "commit" then
local message = op_args.commit_message or op_args.message
if not message then
-- Check if there are staged changes
local diff_success, diff_output = GitTool.get_diff(true) -- staged changes
if not diff_success or not diff_output or vim.trim(diff_output) == "" then
return {
status = "error",
data = "No staged changes found for commit. Please stage your changes first using the stage operation.",
}
end
-- Return success with instruction for AI to use the diff tool
return {
status = "success",
data = "No commit message provided. I need to generate a Conventional Commit compliant message. Please use the `@git_read diff --staged` tool to see the changes and then create an appropriate commit message.",
}
end
success, output = GitTool.commit(message, op_args.amend)
elseif operation == "create_branch" then
if not op_args.branch_name then
return { status = "error", data = "Branch name is required" }
end
success, output = GitTool.create_branch(op_args.branch_name, op_args.checkout)
elseif operation == "checkout" then
if not op_args.target then
return { status = "error", data = "Target branch or commit is required" }
end
success, output = GitTool.checkout(op_args.target)
elseif operation == "stash" then
success, output = GitTool.stash(op_args.message, op_args.include_untracked)
elseif operation == "apply_stash" then
success, output = GitTool.apply_stash(op_args.stash_ref)
elseif operation == "reset" then
if not op_args.commit_hash then
return { status = "error", data = "Commit hash is required for reset" }
end
success, output = GitTool.reset(op_args.commit_hash, op_args.mode)
elseif operation == "gitignore_add" then
local rules = op_args.gitignore_rules or op_args.gitignore_rule
if not rules then
return { status = "error", data = "No rule(s) specified for .gitignore add" }
end
success, output = GitTool.add_gitignore_rule(rules)
elseif operation == "gitignore_remove" then
local rules = op_args.gitignore_rules or op_args.gitignore_rule
if not rules then
return { status = "error", data = "No rule(s) specified for .gitignore remove" }
end
success, output = GitTool.remove_gitignore_rule(rules)
elseif operation == "cherry_pick" then
if not op_args.cherry_pick_commit_hash then
return { status = "error", data = "Commit hash is required for cherry-pick" }
end
success, output = GitTool.cherry_pick(op_args.cherry_pick_commit_hash)
elseif operation == "revert" then
if not op_args.revert_commit_hash then
return { status = "error", data = "Commit hash is required for revert" }
end
success, output = GitTool.revert(op_args.revert_commit_hash)
elseif operation == "create_tag" then
if not op_args.tag_name then
return { status = "error", data = "Tag name is required" }
end
success, output = GitTool.create_tag(op_args.tag_name, op_args.tag_message, op_args.tag_commit_hash)
elseif operation == "delete_tag" then
if not op_args.tag_name then
return { status = "error", data = "Tag name is required for deletion" }
end
success, output = GitTool.delete_tag(op_args.tag_name, op_args.remote)
elseif operation == "merge" then
if not op_args.branch then
return { status = "error", data = "Branch to merge is required" }
end
success, output = GitTool.merge(op_args.branch)
else
return { status = "error", data = "Unknown Git edit operation: " .. operation }
end
if success then
return { status = "success", data = output }
else
return { status = "error", data = output }
end
end,
}
GitEdit.handlers = {
setup = function(self, agent)
return true
end,
on_exit = function(self, agent) end,
}
GitEdit.output = {
success = function(self, agent, cmd, stdout)
local chat = agent.chat
local operation = self.args.operation
local user_msg = string.format("Git edit operation [%s] executed successfully", operation)
return chat:add_tool_output(self, stdout[1], user_msg)
end,
error = function(self, agent, cmd, stderr, stdout)
local chat = agent.chat
local operation = self.args.operation
local error_msg = stderr and stderr[1] or ("Git edit operation [%s] failed"):format(operation)
local user_msg = string.format("Git edit operation [%s] failed", operation)
return chat:add_tool_output(self, error_msg, user_msg)
end,
}
GitEdit.opts = {
requires_approval = function(self, agent)
return true
end,
}
return GitEdit