Skip to content

Commit 125bfd1

Browse files
committed
actually commit things
The MCP server will now actually commit work. Just trying to see how to actually run GitButler stuff via this Rust binary. Currently it will just choose the first stack it sees and blindly commit everything with the unmodified prompt and summary.
1 parent 730af3d commit 125bfd1

File tree

8 files changed

+155
-105
lines changed

8 files changed

+155
-105
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1 @@
1-
## General information
2-
3-
This is a monorepo with multiple projects.
4-
The main applications are found in the `apps` directory.
5-
They are:
6-
7-
- `desktop` containing the Tauri application's frontend code
8-
- `web` containing the web application's frontend code
9-
10-
The backend of the Tauri application is found in the `crates` directory.
11-
It contains different rust packages, all used for the Tauri application.
12-
13-
The `packages` directory contains different self-contained npm packages.
14-
These are shared between the `desktop` and `web` applications.
15-
The packages are:
16-
17-
- `ui` containing the shared UI components
18-
- `shared` containing the shared types and utils
19-
- `mcp` containing the Model Context Protocol packages
20-
- `no-relative-imports` containing the no-relative-imports ESLINT package
21-
22-
## Version control
23-
24-
- Use GitButler tools
25-
- The MCP tools require the absolute path to the repository
26-
- Don't use any other git commands
27-
28-
### Absorb
29-
30-
When told to 'absorb' follow these steps:
31-
32-
1. If there were any instructions given, take them into account.
33-
2. List the file changes
34-
3. Get the hunk dependencies
35-
4. For all files that depend on a **single** commit, amend the file onto that commit. Every time a file is amended, list the hunk dependencies again.
36-
5. If there are no dependencies, list the stacks. Based on the stack branch names and descriptions, determine the best branch to commit the changes to.
37-
6. List the commits in the branch determined in the previous step, and then determine the best commit to amend the changes to based on the description. Update the description if needed.
38-
39-
### Figure out the commits
40-
41-
When told to 'figure out the commits' follow these steps:
42-
43-
1. List the file changes
44-
2. List the stacks
45-
3. Figure out, based on the changes and whether there are any applied stacks/branches:
46-
4. Create a plan for the commits. For that, take a look at the `Create a commit plan` section below.
47-
5. Always execute the plan, and commit the changes as previously determined unless otherwise directed.
48-
49-
### Creating a commit plan
50-
51-
Follow this instructions when creating a commit plan:
52-
53-
1. Group the file changes into logical groups
54-
- Take a look at their diffs and determine if they are related.
55-
- Groups are good, but prefer to have smaller commits than larger ones.
56-
- Granularity is good.
57-
2. Determine if any branches should be created
58-
- If there are any stacks with branches, take a look at the branch names and descriptions and match them with the file changes.
59-
- Create multiple branches if needed. You can tell if multiple branches are needed if the file changes span multiple projects in the monorepo.
60-
3. Determine the commits
61-
- For each group of file changes, determine the commit message. Be descriptive and explain what the changes are.
62-
- If branches need to be created, use a descriptive name for the branch.
63-
- Determine the order of the commits. If there are any dependencies between the commits, make sure to commit them in the correct order.
64-
- Define which commits should go into which branches.
65-
- If there were any other instructions given, take them into account.
66-
67-
In the end, the plan should contain a list of the branches to be created (if any) and the commits to be made
1+
After every session that adds, deletes or modifies code, call the gitbutler update_branch MCP call to update the current branch and then inform the user what happened based on the output of that call.

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/gitbutler-mcp/Cargo.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ description = "Model Context Protocol server for GitButler"
66
authors = ["GitButler <gitbutler@gitbutler.com>"]
77
publish = false
88

9+
[[bin]]
10+
name = "gitbutler-mcp"
11+
path = "src/main.rs"
12+
913
[dependencies]
1014
tokio = { version = "1", features = [
1115
"macros",
@@ -27,6 +31,17 @@ tracing-subscriber = { version = "0.3", features = [
2731
"std",
2832
"fmt",
2933
] }
34+
gitbutler-oplog.workspace = true
35+
gitbutler-project.workspace = true
36+
gitbutler-reference.workspace = true
37+
gitbutler-branch-actions.workspace = true
38+
gitbutler-command-context.workspace = true
39+
gitbutler-branch.workspace = true
40+
gitbutler-diff.workspace = true
41+
but-settings.workspace = true
42+
gitbutler-stack.workspace = true
43+
gitbutler-oxidize.workspace = true
44+
gix = { workspace = true, features = ["max-performance", "tracing"] }
3045

3146
[dev-dependencies]
3247
temp-dir = "0.1"

crates/gitbutler-mcp/README.md

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,23 @@
1-
# GitButler MCP
1+
# Butler MCP
22

3-
A Model Context Protocol server implementation for GitButler that provides AI assistants with branch management capabilities.
3+
This crate implments a single binary Rust MCP server that can be pointed to by an Agent to do some basic branch management work with the GitButler tooling.
44

5-
## Overview
5+
It implements a single tool called 'update_branch' that will look at uncommitted changes in the working directory and either commit them or amend an existing commit.
66

7-
`gitbutler-mcp` is a Rust crate that implements a Model Context Protocol (MCP) server for GitButler. It enables AI assistants to perform branch-related operations in GitButler repositories through standardized MCP interactions.
7+
If there is no existing branch, it will create a new one.
88

9-
## Features
9+
If AI capabilities are enabled, it will also use the AI to generate a commit message for the changes based on the prompt.
1010

11-
- **Branch Management**: Update branches with summaries and prompts
12-
- **MCP Compliance**: Fully implements the Model Context Protocol specification
13-
- **Tooling Integration**: Provides tools that can be used by AI assistants
11+
## The Idea
1412

15-
## Usage
13+
The concept is not to give an Agent an endpoint to every API that we have, which mainly results in being able to use the agent as a slow command line. The idea is to have a few very powerful tools that can be used to do a lot of work automatically.
1614

17-
The MCP server can be integrated with AI assistants that support the Model Context Protocol. It exposes tools that allow these assistants to:
15+
Most of the work should be done in GitButler for more specific tasks, but updating a branch with new work generated via agentic work can be simple and powerful.
1816

19-
- Update branches with contextual information
20-
- Process prompts and convert them into branch-specific actions
17+
## TODO
2118

22-
## Tool Reference
23-
24-
### `update_branch`
25-
26-
Updates a GitButler branch with a given prompt and summary.
27-
28-
**Parameters:**
29-
- `working_directory`: Path to the Git repository
30-
- `full_prompt`: Complete prompt that was used for the branch update
31-
- `summary`: Short description of the changes
32-
33-
## Development
34-
35-
To run the MCP server:
36-
37-
```bash
38-
cargo run -p gitbutler-mcp
39-
```
40-
41-
## Integration
42-
43-
This MCP server can be integrated with any AI assistant that supports the Model Context Protocol (MCP) specification.
44-
45-
## License
46-
47-
Same as the GitButler project.
19+
- [ ] create a new branch if there is no existing one
20+
- [ ] determine the actual branch name to use of everything existing
21+
- [ ] determine if a new virtual branch should be created
22+
- [ ] determine if work should be committed or amended
23+
- [ ] use the AI to generate a commit message

crates/gitbutler-mcp/src/common/butler.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use serde_json::json;
66
use std::path::PathBuf;
77
use tracing;
88

9+
use crate::common::commit::commit;
10+
use crate::common::prepare::project_from_path;
11+
912
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
1013
pub struct UpdateBranchRequest {
1114
pub working_directory: String,
@@ -47,6 +50,12 @@ impl Butler {
4750
));
4851
}
4952

53+
let project = project_from_path(project_path).unwrap();
54+
dbg!(&project);
55+
56+
let _commit = commit(project, full_prompt.clone(), summary.clone());
57+
dbg!(&_commit);
58+
5059
// In a real implementation, we would use GitButler's branch management APIs
5160
// But for now, we'll simulate a successful branch update
5261
tracing::info!(
@@ -63,6 +72,25 @@ impl Butler {
6372
}
6473
}
6574

75+
// simple test with my working directory
76+
#[cfg(test)]
77+
mod tests {
78+
use super::*;
79+
80+
#[test]
81+
fn test_update_branch() {
82+
let butler = Butler::new();
83+
let request = UpdateBranchRequest {
84+
working_directory: "/Users/schacon/projects/gitbutler".to_string(),
85+
full_prompt: "Update branch with new changes".to_string(),
86+
summary: "Updated branch with new changes".to_string(),
87+
};
88+
89+
let result = butler.update_branch(request);
90+
dbg!(result);
91+
}
92+
}
93+
6694
const_string!(UpdateBranch = "updateBranch");
6795

6896
#[tool(tool_box)]
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
use anyhow::{bail, Result};
2+
use but_settings::AppSettings;
3+
use gitbutler_branch::{BranchCreateRequest, BranchIdentity, BranchUpdateRequest};
4+
use gitbutler_branch_actions::{get_branch_listing_details, list_branches, BranchManagerExt};
5+
use gitbutler_command_context::CommandContext;
6+
use gitbutler_project::Project;
7+
use gitbutler_reference::{LocalRefname, Refname};
8+
use gitbutler_stack::{Stack, VirtualBranchesHandle};
9+
10+
pub fn commit(project: Project, full_prompt: String, summary: String) -> Result<()> {
11+
let ctx = CommandContext::open(&project, AppSettings::default())?;
12+
let list_result = gitbutler_branch_actions::list_virtual_branches(&ctx)?;
13+
14+
// just get the first stack for now
15+
let stack = VirtualBranchesHandle::new(project.gb_dir())
16+
.list_stacks_in_workspace()?
17+
.into_iter()
18+
.next()
19+
.ok_or(anyhow::anyhow!("No stacks found in the project directory"))?;
20+
21+
dbg!(&stack);
22+
23+
if !list_result.skipped_files.is_empty() {
24+
eprintln!(
25+
"{} files could not be processed (binary or large size)",
26+
list_result.skipped_files.len()
27+
)
28+
}
29+
30+
dbg!(&list_result);
31+
32+
let target_branch = list_result
33+
.branches
34+
.iter()
35+
.next()
36+
.expect("A populated branch exists for a branch we can list");
37+
if target_branch.ownership.claims.is_empty() {
38+
bail!(
39+
"Branch has no change to commit{hint}",
40+
hint = {
41+
let candidate_names = list_result
42+
.branches
43+
.iter()
44+
.filter_map(|b| (!b.ownership.claims.is_empty()).then_some(b.name.as_str()))
45+
.collect::<Vec<_>>();
46+
let mut candidates = candidate_names.join(", ");
47+
if !candidate_names.is_empty() {
48+
candidates = format!(
49+
". {candidates} {have} changes.",
50+
have = if candidate_names.len() == 1 {
51+
"has"
52+
} else {
53+
"have"
54+
}
55+
)
56+
};
57+
candidates
58+
}
59+
)
60+
}
61+
62+
let message = full_prompt + "\n\n" + &summary;
63+
dbg!(&message);
64+
65+
let _oid = gitbutler_branch_actions::create_commit(
66+
&ctx,
67+
stack.id,
68+
&message,
69+
Some(&target_branch.ownership),
70+
)?;
71+
72+
dbg!("Commit created successfully");
73+
dbg!(_oid);
74+
75+
Ok(())
76+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
pub mod butler;
2+
pub mod commit;
3+
pub mod prepare;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use std::path::PathBuf;
2+
3+
use anyhow::{bail, Context};
4+
use gitbutler_project::Project;
5+
6+
pub fn project_from_path(path: PathBuf) -> anyhow::Result<Project> {
7+
Project::from_path(&path)
8+
}

0 commit comments

Comments
 (0)