mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-08-30 03:57:45 +00:00
fix: honor new_branch_name on the file update path (#228)
The update path of `create_or_update_file` built `UpdateFileOptions` without `NewBranchName`, so a call asking for a new branch committed straight to the base branch and still reported success. The success text now names the branch that got the commit. Fixes: https://gitea.com/gitea/gitea-mcp/issues/217 Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/228 Reviewed-by: bircni <bircni@icloud.com> Co-authored-by: silverwind <2021+silverwind@noreply.gitea.com>
This commit is contained in:
+17
-17
@@ -3,6 +3,7 @@ package repo
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"cmp"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -61,7 +62,7 @@ var (
|
|||||||
tool.String("message", tool.Required(), tool.Description("commit message")),
|
tool.String("message", tool.Required(), tool.Description("commit message")),
|
||||||
tool.String("branch_name", tool.Required()),
|
tool.String("branch_name", tool.Required()),
|
||||||
tool.String("sha", tool.Description("existing file SHA (omit to create)")),
|
tool.String("sha", tool.Description("existing file SHA (omit to create)")),
|
||||||
tool.String("new_branch_name", tool.Description("new branch (create only)")),
|
tool.String("new_branch_name", tool.Description("branch to create from branch_name and commit to")),
|
||||||
)
|
)
|
||||||
|
|
||||||
DeleteFileTool = tool.NewDefinition(
|
DeleteFileTool = tool.NewDefinition(
|
||||||
@@ -204,6 +205,7 @@ func CreateOrUpdateFileFn(ctx context.Context, args map[string]any) (*mcp.CallTo
|
|||||||
content, _ := args["content"].(string)
|
content, _ := args["content"].(string)
|
||||||
message, _ := args["message"].(string)
|
message, _ := args["message"].(string)
|
||||||
branchName, _ := args["branch_name"].(string)
|
branchName, _ := args["branch_name"].(string)
|
||||||
|
newBranchName, _ := args["new_branch_name"].(string)
|
||||||
sha, _ := args["sha"].(string)
|
sha, _ := args["sha"].(string)
|
||||||
|
|
||||||
client, err := gitea.ClientFromContext(ctx)
|
client, err := gitea.ClientFromContext(ctx)
|
||||||
@@ -211,39 +213,37 @@ func CreateOrUpdateFileFn(ctx context.Context, args map[string]any) (*mcp.CallTo
|
|||||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileOpt := gitea_sdk.FileOptions{
|
||||||
|
Message: message,
|
||||||
|
BranchName: branchName,
|
||||||
|
NewBranchName: newBranchName,
|
||||||
|
}
|
||||||
|
targetBranch := cmp.Or(newBranchName, branchName)
|
||||||
|
|
||||||
if sha != "" {
|
if sha != "" {
|
||||||
// Update existing file
|
// Update existing file
|
||||||
opt := gitea_sdk.UpdateFileOptions{
|
opt := gitea_sdk.UpdateFileOptions{
|
||||||
SHA: sha,
|
SHA: sha,
|
||||||
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||||
FileOptions: gitea_sdk.FileOptions{
|
FileOptions: fileOpt,
|
||||||
Message: message,
|
|
||||||
BranchName: branchName,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
_, _, err = client.Repositories.UpdateFile(ctx, owner, repo, filePath, opt)
|
_, _, err = client.Repositories.UpdateFile(ctx, owner, repo, filePath, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return to.ErrorResult(fmt.Errorf("update file err: %v", err))
|
return to.ErrorResult(fmt.Errorf("update file err: %v", err))
|
||||||
}
|
}
|
||||||
return to.TextResult("Update file success")
|
return to.TextResult("Update file success on branch " + targetBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new file
|
// Create new file
|
||||||
opt := gitea_sdk.CreateFileOptions{
|
opt := gitea_sdk.CreateFileOptions{
|
||||||
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
Content: base64.StdEncoding.EncodeToString([]byte(content)),
|
||||||
FileOptions: gitea_sdk.FileOptions{
|
FileOptions: fileOpt,
|
||||||
Message: message,
|
|
||||||
BranchName: branchName,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if newBranch, ok := args["new_branch_name"].(string); ok && newBranch != "" {
|
|
||||||
opt.NewBranchName = newBranch
|
|
||||||
}
|
}
|
||||||
_, _, err = client.Repositories.CreateFile(ctx, owner, repo, filePath, opt)
|
_, _, err = client.Repositories.CreateFile(ctx, owner, repo, filePath, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return to.ErrorResult(fmt.Errorf("create file err: %v", err))
|
return to.ErrorResult(fmt.Errorf("create file err: %v", err))
|
||||||
}
|
}
|
||||||
return to.TextResult("Create file success")
|
return to.TextResult("Create file success on branch " + targetBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteFileFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
func DeleteFileFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.com/gitea/gitea-mcp/pkg/flag"
|
||||||
|
|
||||||
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateOrUpdateFileFnNewBranch(t *testing.T) {
|
||||||
|
const (
|
||||||
|
owner = "octo"
|
||||||
|
repo = "demo"
|
||||||
|
filePath = "README.md"
|
||||||
|
baseBranch = "main"
|
||||||
|
newBranch = "feature-x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
gotBody map[string]any
|
||||||
|
)
|
||||||
|
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var body map[string]any
|
||||||
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
||||||
|
mu.Lock()
|
||||||
|
gotBody = body
|
||||||
|
mu.Unlock()
|
||||||
|
_, _ = w.Write([]byte(`{}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
origHost, origToken := flag.Host, flag.Token
|
||||||
|
flag.Host, flag.Token = server.URL, ""
|
||||||
|
defer func() { flag.Host, flag.Token = origHost, origToken }()
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
sha string
|
||||||
|
wantResult string
|
||||||
|
}{
|
||||||
|
{"create", "", "Create file success on branch " + newBranch},
|
||||||
|
{"update", "blobsha", "Update file success on branch " + newBranch},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result, err := CreateOrUpdateFileFn(context.Background(), map[string]any{
|
||||||
|
"owner": owner,
|
||||||
|
"repo": repo,
|
||||||
|
"path": filePath,
|
||||||
|
"content": "hello",
|
||||||
|
"message": "update readme",
|
||||||
|
"branch_name": baseBranch,
|
||||||
|
"new_branch_name": newBranch,
|
||||||
|
"sha": tc.sha,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateOrUpdateFileFn() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if gotBody["new_branch"] != newBranch {
|
||||||
|
t.Fatalf("new_branch = %v, want %s", gotBody["new_branch"], newBranch)
|
||||||
|
}
|
||||||
|
if gotBody["branch"] != baseBranch {
|
||||||
|
t.Fatalf("branch = %v, want %s", gotBody["branch"], baseBranch)
|
||||||
|
}
|
||||||
|
if text := result.Content[0].(*mcp.TextContent).Text; !strings.Contains(text, tc.wantResult) {
|
||||||
|
t.Fatalf("result = %s, want it to contain %q", text, tc.wantResult)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user