Skip to content

Commit 02af088

Browse files
authored
feat(git): push with async (#6)
1 parent 7b21e21 commit 02af088

File tree

5 files changed

+715
-7
lines changed

5 files changed

+715
-7
lines changed

GEMINI.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ This project is a CodeCompanion extension designed to generate AI-powered Git co
1111
- **File Filtering**: Supports glob patterns to exclude generated files from analysis.
1212
- **Natural Language Interface**: Enables controlling Git workflows through conversational commands.
1313

14+
## Development Principles
15+
16+
- **No Plenary**: The use of the `plenary.nvim` library is prohibited. Instead, leverage Neovim's built-in `vim.uv` library for I/O and other system-level operations.
17+
- **Asynchronous by Default**: For any operation that could be time-consuming (e.g., network requests, file system operations), always prefer an asynchronous implementation to avoid blocking the main thread.
18+
- **Comprehensive Feedback**: Regardless of the outcome, every asynchronous operation must report its result (both success and failure) back to the language model. This ensures the LLM is always aware of the state of the tool.
19+
1420
## Installation:
1521

1622
Add this extension to your CodeCompanion configuration in `init.lua` or similar:
@@ -70,4 +76,4 @@ Key configurable options include LLM adapter and model, supported languages, fil
7076

7177
## License:
7278

73-
MIT License
79+
MIT License

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ require("codecompanion").setup({
4242

4343
-- Feature toggles
4444
add_slash_command = true, -- Add /gitcommit slash command
45-
add_git_tool = true, -- Add @git_read and @git_edit tools
45+
add_git_tool = true, -- Add @git_read and @git_edit tools, and @git_bot tool group
4646
add_git_commands = true, -- Add :CodeCompanionGit commands
4747
}
4848
}

lua/codecompanion/_extensions/gitcommit/tools/git.lua

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,68 @@ function GitTool.push(remote, branch, force, tags, tag_name)
434434
return execute_git_command(cmd)
435435
end
436436

437+
---Push changes to a remote repository asynchronously
438+
---@param remote? string The name of the remote to push to (e.g., origin)
439+
---@param branch? string The name of the branch to push (defaults to current branch)
440+
---@param force? boolean Force push (DANGEROUS: overwrites remote history)
441+
---@param set_upstream? boolean Set the upstream branch
442+
---@param tags? boolean Push all tags
443+
---@param tag_name? string The name of a single tag to push
444+
---@param on_exit function The callback function to execute on completion
445+
function GitTool.push_async(remote, branch, force, set_upstream, tags, tag_name, on_exit)
446+
local cmd = { "git", "push" }
447+
if force then
448+
table.insert(cmd, "--force")
449+
end
450+
if set_upstream then
451+
table.insert(cmd, "--set-upstream")
452+
end
453+
if tags then
454+
table.insert(cmd, "--tags")
455+
end
456+
if tag_name then
457+
table.insert(cmd, "tag")
458+
table.insert(cmd, tag_name)
459+
end
460+
if remote then
461+
table.insert(cmd, remote)
462+
end
463+
if branch then
464+
table.insert(cmd, branch)
465+
end
466+
467+
local stdout_lines = {}
468+
local stderr_lines = {}
469+
470+
vim.fn.jobstart(cmd, {
471+
on_stdout = function(_, data)
472+
if data then
473+
for _, line in ipairs(data) do
474+
if line ~= "" then
475+
table.insert(stdout_lines, line)
476+
end
477+
end
478+
end
479+
end,
480+
on_stderr = function(_, data)
481+
if data then
482+
for _, line in ipairs(data) do
483+
if line ~= "" then
484+
table.insert(stderr_lines, line)
485+
end
486+
end
487+
end
488+
end,
489+
on_exit = function(_, code)
490+
if code == 0 then
491+
on_exit({ status = "success", data = table.concat(stdout_lines, "\n") })
492+
else
493+
on_exit({ status = "error", data = table.concat(stderr_lines, "\n") })
494+
end
495+
end,
496+
})
497+
end
498+
437499
---Perform a git rebase operation
438500

439501
---@param onto? string The branch to rebase onto

lua/codecompanion/_extensions/gitcommit/tools/git_edit.lua

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ GitEdit.schema = {
107107
type = "boolean",
108108
description = "Force push (DANGEROUS: overwrites remote history)",
109109
},
110+
set_upstream = {
111+
type = "boolean",
112+
description = "Set the upstream branch for the current local branch",
113+
},
110114
tags = {
111115
type = "boolean",
112116
description = "Push all tags",
@@ -162,13 +166,14 @@ Best practices:
162166
• Auto-generation analyzes staged changes and creates Conventional Commit compliant messages
163167
• Use format: type(scope): description with lowercase type and imperative verb description
164168
• Include body with bullet points for complex changes, keep description under 50 characters
169+
• Use the `set_upstream` option to create and track a remote branch if it doesn't exist
165170
• Avoid force push operations that rewrite history
166171
• Ensure file paths and branch names are valid
167172
168173
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]]
169174

170175
GitEdit.cmds = {
171-
function(self, args, input)
176+
function(self, args, input, output_handler)
172177
local operation = args.operation
173178
local op_args = args.args or {}
174179

@@ -193,6 +198,18 @@ Available write-access Git operations:
193198
return { status = "success", data = help_text }
194199
end
195200

201+
if operation == "push" then
202+
return GitTool.push_async(
203+
op_args.remote,
204+
op_args.branch,
205+
op_args.force,
206+
op_args.set_upstream,
207+
op_args.tags,
208+
op_args.single_tag_name,
209+
output_handler
210+
)
211+
end
212+
196213
local success, output
197214

198215
if operation == "stage" then
@@ -255,8 +272,6 @@ Available write-access Git operations:
255272
return { status = "error", data = "No rule(s) specified for .gitignore remove" }
256273
end
257274
success, output = GitTool.remove_gitignore_rule(rules)
258-
elseif operation == "push" then
259-
success, output = GitTool.push(op_args.remote, op_args.branch, op_args.force, op_args.tags, op_args.tag_name)
260275
elseif operation == "cherry_pick" then
261276
if not op_args.cherry_pick_commit_hash then
262277
return { status = "error", data = "Commit hash is required for cherry-pick" }
@@ -310,8 +325,9 @@ GitEdit.output = {
310325
end,
311326
error = function(self, agent, cmd, stderr, stdout)
312327
local chat = agent.chat
313-
local error_msg = stderr[1] or "Git edit operation failed"
314-
local user_msg = "Git edit operation failed"
328+
local operation = self.args.operation
329+
local error_msg = stderr and stderr[1] or ("Git edit operation [%s] failed"):format(operation)
330+
local user_msg = string.format("Git edit operation [%s] failed", operation)
315331
return chat:add_tool_output(self, error_msg, user_msg)
316332
end,
317333
}

0 commit comments

Comments
 (0)