From 3269af42718e7c5c5d9ec7af679223f34d350576 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Mon, 7 Jul 2025 16:56:24 +0100 Subject: [PATCH 1/9] add contingency to match path in git tree --- pkg/github/repositories.go | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index cf71a5839..2100c7f78 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -610,6 +610,31 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t } return mcp.NewToolResultText(string(r)), nil } + + // The path does not point to a file or directory. + // Instead let's try to find it in the Git Tree by matching the end of the path. + + // Step 1: Get Git Tree recursively + tree, resp, err := client.Git.GetTree(ctx, owner, repo, ref, true) + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to get git tree", + resp, + err, + ), nil + } + + // Step 2: Filter tree for matching paths + const maxMatchingFiles = 3 + matchingFiles := filterPaths(tree.Entries, path, maxMatchingFiles) + if len(matchingFiles) > 0 { + matchingFilesJSON, err := json.Marshal(matchingFiles) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil + } + return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but possible matches are: %s", matchingFilesJSON)), nil + } + return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil } } @@ -1293,3 +1318,22 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m return mcp.NewToolResultText(string(r)), nil } } + +// filterPaths filters the entries in a GitHub tree to find paths that +// match the given suffix. +func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string { + matchedPaths := []string{} + for _, entry := range entries { + if len(matchedPaths) == maxResults { + break // Limit the number of results to maxResults + } + entryPath := entry.GetPath() + if entryPath == "" { + continue // Skip empty paths + } + if strings.HasSuffix(entryPath, path) { + matchedPaths = append(matchedPaths, entryPath) + } + } + return matchedPaths +} From be2f36f79e0c135808368f40b05d2275f6560f7e Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Tue, 8 Jul 2025 00:59:37 +0100 Subject: [PATCH 2/9] resolveGitReference helper --- pkg/github/repositories.go | 77 ++++++++++++---------- pkg/github/repositories_test.go | 110 +++++++++++++++++++++++++++++++- 2 files changed, 153 insertions(+), 34 deletions(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 2100c7f78..1dc63dc12 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "net/url" - "strconv" "strings" ghErrors "github.com/github/github-mcp-server/pkg/errors" @@ -495,33 +494,18 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t return mcp.NewToolResultError(err.Error()), nil } - rawOpts := &raw.ContentOpts{} - - if strings.HasPrefix(ref, "refs/pull/") { - prNumber := strings.TrimSuffix(strings.TrimPrefix(ref, "refs/pull/"), "/head") - if len(prNumber) > 0 { - // fetch the PR from the API to get the latest commit and use SHA - githubClient, err := getClient(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get GitHub client: %w", err) - } - prNum, err := strconv.Atoi(prNumber) - if err != nil { - return nil, fmt.Errorf("invalid pull request number: %w", err) - } - pr, _, err := githubClient.PullRequests.Get(ctx, owner, repo, prNum) - if err != nil { - return nil, fmt.Errorf("failed to get pull request: %w", err) - } - sha = pr.GetHead().GetSHA() - ref = "" - } + client, err := getClient(ctx) + if err != nil { + return mcp.NewToolResultError("failed to get GitHub client"), nil } - rawOpts.SHA = sha - rawOpts.Ref = ref + rawOpts, err := resolveGitReference(ctx, client, owner, repo, ref, sha) + if err != nil { + return mcp.NewToolResultError(err.Error()), nil + } - // If the path is (most likely) not to be a directory, we will first try to get the raw content from the GitHub raw content API. + // If the path is (most likely) not to be a directory, we will + // first try to get the raw content from the GitHub raw content API. if path != "" && !strings.HasSuffix(path, "/") { rawClient, err := getRawClient(ctx) @@ -580,13 +564,8 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t } } - client, err := getClient(ctx) - if err != nil { - return mcp.NewToolResultError("failed to get GitHub client"), nil - } - - if sha != "" { - ref = sha + if rawOpts.SHA != "" { + ref = rawOpts.SHA } if strings.HasSuffix(path, "/") { opts := &github.RepositoryContentGetOptions{Ref: ref} @@ -632,7 +611,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil } - return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but possible matches are: %s", matchingFilesJSON)), nil + return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil } return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil @@ -1337,3 +1316,35 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str } return matchedPaths } + +// resolveGitReference resolves git references with the following logic: +// 1. If SHA is provided, it takes precedence +// 2. If neither is provided, use the default branch as ref +// 3. Get SHA from the ref +// Refs can look like `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head` +// The function returns the resolved ref, SHA and any error. +func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, error) { + // 1. If SHA is provided, use it directly + if sha != "" { + return &raw.ContentOpts{Ref: "", SHA: sha}, nil + } + + // 2. If neither provided, use the default branch as ref + if ref == "" { + repoInfo, _, err := githubClient.Repositories.Get(ctx, owner, repo) + if err != nil { + return nil, fmt.Errorf("failed to get repository info: %w", err) + } + ref = fmt.Sprintf("refs/heads/%s", repoInfo.GetDefaultBranch()) + } + + // 3. Get the SHA from the ref + reference, _, err := githubClient.Git.GetRef(ctx, owner, repo, ref) + if err != nil { + return nil, fmt.Errorf("failed to get reference for default branch: %w", err) + } + sha = reference.GetObject().GetSHA() + + // Use provided ref, or it will be empty which defaults to the default branch + return &raw.ContentOpts{Ref: ref, SHA: sha}, nil +} diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index b621cec43..c81796899 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -69,6 +69,13 @@ func Test_GetFileContents(t *testing.T) { { name: "successful text content fetch", mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + ), mock.WithRequestMatchHandler( raw.GetRawReposContentsByOwnerByRepoByBranchByPath, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -93,6 +100,13 @@ func Test_GetFileContents(t *testing.T) { { name: "successful file blob content fetch", mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + ), mock.WithRequestMatchHandler( raw.GetRawReposContentsByOwnerByRepoByBranchByPath, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -117,6 +131,20 @@ func Test_GetFileContents(t *testing.T) { { name: "successful directory content fetch", mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) + }), + ), + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + ), mock.WithRequestMatchHandler( mock.GetReposContentsByOwnerByRepoByPath, expectQueryParams(t, map[string]string{}).andThen( @@ -143,6 +171,13 @@ func Test_GetFileContents(t *testing.T) { { name: "content fetch fails", mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) + }), + ), mock.WithRequestMatchHandler( mock.GetReposContentsByOwnerByRepoByPath, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -203,7 +238,7 @@ func Test_GetFileContents(t *testing.T) { textContent := getTextResult(t, result) var returnedContents []*github.RepositoryContent err = json.Unmarshal([]byte(textContent.Text), &returnedContents) - require.NoError(t, err) + require.NoError(t, err, "Failed to unmarshal directory content result: %v", textContent.Text) assert.Len(t, returnedContents, len(expected)) for i, content := range returnedContents { assert.Equal(t, *expected[i].Name, *content.Name) @@ -2049,3 +2084,76 @@ func Test_GetTag(t *testing.T) { }) } } + +func Test_ResolveGitReference(t *testing.T) { + + ctx := context.Background() + owner := "owner" + repo := "repo" + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) + }), + ), + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": "123sha456"}}`)) + }), + ), + ) + + tests := []struct { + name string + ref string + sha string + expectedOutput *raw.ContentOpts + }{ + { + name: "sha takes precedence over ref", + ref: "refs/heads/main", + sha: "123sha456", + expectedOutput: &raw.ContentOpts{ + SHA: "123sha456", + }, + }, + { + name: "use default branch if ref and sha both empty", + ref: "", + sha: "", + expectedOutput: &raw.ContentOpts{ + Ref: "refs/heads/main", + SHA: "123sha456", + }, + }, + { + name: "get SHA from ref", + ref: "refs/heads/main", + sha: "", + expectedOutput: &raw.ContentOpts{ + Ref: "refs/heads/main", + SHA: "123sha456", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + // Setup client with mock + client := github.NewClient(mockedClient) + opts, err := resolveGitReference(ctx, client, owner, repo, tc.ref, tc.sha) + require.NoError(t, err) + + if tc.expectedOutput.SHA != "" { + assert.Equal(t, tc.expectedOutput.SHA, opts.SHA) + } + if tc.expectedOutput.Ref != "" { + assert.Equal(t, tc.expectedOutput.Ref, opts.Ref) + } + }) + } +} From 37a6088f0bd8702580726e70d5ea1b60c05a4e22 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Tue, 8 Jul 2025 01:34:39 +0100 Subject: [PATCH 3/9] fix: handling of directories --- pkg/github/repositories.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 1dc63dc12..44cbd2cf4 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -570,24 +570,14 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t if strings.HasSuffix(path, "/") { opts := &github.RepositoryContentGetOptions{Ref: ref} _, dirContent, resp, err := client.Repositories.GetContents(ctx, owner, repo, path, opts) - if err != nil { - return mcp.NewToolResultError("failed to get file contents"), nil - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != 200 { - body, err := io.ReadAll(resp.Body) + if err == nil && resp.StatusCode == http.StatusOK { + defer func() { _ = resp.Body.Close() }() + r, err := json.Marshal(dirContent) if err != nil { - return mcp.NewToolResultError("failed to read response body"), nil + return mcp.NewToolResultError("failed to marshal response"), nil } - return mcp.NewToolResultError(fmt.Sprintf("failed to get file contents: %s", string(body))), nil + return mcp.NewToolResultText(string(r)), nil } - - r, err := json.Marshal(dirContent) - if err != nil { - return mcp.NewToolResultError("failed to marshal response"), nil - } - return mcp.NewToolResultText(string(r)), nil } // The path does not point to a file or directory. @@ -1301,6 +1291,8 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m // filterPaths filters the entries in a GitHub tree to find paths that // match the given suffix. func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string { + path = strings.TrimSuffix(path, "/") // Normalize path to avoid double slashes + matchedPaths := []string{} for _, entry := range entries { if len(matchedPaths) == maxResults { @@ -1311,6 +1303,9 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str continue // Skip empty paths } if strings.HasSuffix(entryPath, path) { + if entry.GetType() == "tree" { + entryPath += "/" // show directories with a trailing slash + } matchedPaths = append(matchedPaths, entryPath) } } From 588866f031be4395b7404d9c734649e1ee9e3807 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Tue, 8 Jul 2025 09:28:32 +0100 Subject: [PATCH 4/9] Test_filterPaths --- pkg/github/repositories.go | 4 +++ pkg/github/repositories_test.go | 43 ++++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 44cbd2cf4..869d3c162 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -1290,6 +1290,10 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m // filterPaths filters the entries in a GitHub tree to find paths that // match the given suffix. +// maxResults limits the number of results returned to first maxResults entries, +// a maxResults of -1 means no limit. +// It returns a slice of strings containing the matching paths. +// Directories are returned with a trailing slash. func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string { path = strings.TrimSuffix(path, "/") // Normalize path to avoid double slashes diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index c81796899..55c3aa088 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -2085,8 +2085,49 @@ func Test_GetTag(t *testing.T) { } } -func Test_ResolveGitReference(t *testing.T) { +func Test_filterPaths(t *testing.T) { + tests := []struct { + name string + tree []*github.TreeEntry + path string + maxResults int + expected []string + }{ + { + name: "file name", + tree: []*github.TreeEntry{ + {Path: github.Ptr("folder/foo.txt"), Type: github.Ptr("blob")}, + {Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")}, + {Path: github.Ptr("nested/folder/foo.txt"), Type: github.Ptr("blob")}, + {Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")}, + }, + path: "foo.txt", + maxResults: -1, + expected: []string{"folder/foo.txt", "nested/folder/foo.txt"}, + }, + { + name: "dir name", + tree: []*github.TreeEntry{ + {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("bar.txt"), Type: github.Ptr("blob")}, + {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("nested/folder/baz.txt"), Type: github.Ptr("blob")}, + }, + path: "folder/", + maxResults: -1, + expected: []string{"folder/", "nested/folder/"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + result := filterPaths(tc.tree, tc.path, tc.maxResults) + assert.Equal(t, tc.expected, result) + }) + } +} +func Test_resolveGitReference(t *testing.T) { ctx := context.Background() owner := "owner" repo := "repo" From 1cc6f7c5b9f62aebb715c5426d72b5688e6ec1ef Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Tue, 8 Jul 2025 09:36:32 +0100 Subject: [PATCH 5/9] filterPaths - trailing slashes --- pkg/github/repositories.go | 13 +++++++++++-- pkg/github/repositories_test.go | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 869d3c162..ffcd1c7c7 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -1295,20 +1295,29 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m // It returns a slice of strings containing the matching paths. // Directories are returned with a trailing slash. func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string { - path = strings.TrimSuffix(path, "/") // Normalize path to avoid double slashes + // Remove trailing slash for matching purposes, but flag whether we + // only want directories. + dirOnly := false + if strings.HasSuffix(path, "/") { + dirOnly = true + path = strings.TrimSuffix(path, "/") + } matchedPaths := []string{} for _, entry := range entries { if len(matchedPaths) == maxResults { break // Limit the number of results to maxResults } + if dirOnly && entry.GetType() != "tree" { + continue // Skip non-directory entries if dirOnly is true + } entryPath := entry.GetPath() if entryPath == "" { continue // Skip empty paths } if strings.HasSuffix(entryPath, path) { if entry.GetType() == "tree" { - entryPath += "/" // show directories with a trailing slash + entryPath += "/" // Return directories with a trailing slash } matchedPaths = append(matchedPaths, entryPath) } diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 55c3aa088..81d80a2e5 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -2117,6 +2117,26 @@ func Test_filterPaths(t *testing.T) { maxResults: -1, expected: []string{"folder/", "nested/folder/"}, }, + { + name: "dir and file match", + tree: []*github.TreeEntry{ + {Path: github.Ptr("name"), Type: github.Ptr("tree")}, + {Path: github.Ptr("name"), Type: github.Ptr("blob")}, + }, + path: "name", // No trailing slash can match both files and directories + maxResults: -1, + expected: []string{"name/", "name"}, + }, + { + name: "dir only match", + tree: []*github.TreeEntry{ + {Path: github.Ptr("name"), Type: github.Ptr("tree")}, + {Path: github.Ptr("name"), Type: github.Ptr("blob")}, + }, + path: "name/", // Trialing slash ensures only directories are matched + maxResults: -1, + expected: []string{"name/"}, + }, } for _, tc := range tests { From 12c2cfe59ec8401759cc1d1e7b83a2279456c705 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Tue, 8 Jul 2025 12:50:56 +0100 Subject: [PATCH 6/9] fix: close response body, improve error messages, docs --- pkg/github/repositories.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index ffcd1c7c7..d33b51dae 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -501,7 +501,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t rawOpts, err := resolveGitReference(ctx, client, owner, repo, ref, sha) if err != nil { - return mcp.NewToolResultError(err.Error()), nil + return mcp.NewToolResultError(fmt.Sprintf("failed to resolve git reference: %s", err)), nil } // If the path is (most likely) not to be a directory, we will @@ -592,6 +592,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t err, ), nil } + defer func() { _ = resp.Body.Close() }() // Step 2: Filter tree for matching paths const maxMatchingFiles = 3 @@ -601,7 +602,7 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil } - return mcp.NewToolResultText(fmt.Sprintf("Provided path did not match a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil + return mcp.NewToolResultText(fmt.Sprintf("Path did not point to a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil } return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil @@ -1328,9 +1329,9 @@ func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []str // resolveGitReference resolves git references with the following logic: // 1. If SHA is provided, it takes precedence // 2. If neither is provided, use the default branch as ref -// 3. Get SHA from the ref +// 3. Get commit SHA from the ref // Refs can look like `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head` -// The function returns the resolved ref, SHA and any error. +// The function returns the resolved ref, commit SHA and any error. func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, error) { // 1. If SHA is provided, use it directly if sha != "" { From 8c40155f5ce07826587fc70f2101ea7e37d2ca76 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Tue, 8 Jul 2025 13:07:16 +0100 Subject: [PATCH 7/9] update tool result message about resolved git ref --- pkg/github/repositories.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index d33b51dae..f32d40fe5 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -602,7 +602,11 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t if err != nil { return mcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil } - return mcp.NewToolResultText(fmt.Sprintf("Path did not point to a file or directory, but resolved ref to %s with possible path matches: %s", ref, matchingFilesJSON)), nil + resolvedRefs, err := json.Marshal(rawOpts) + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("failed to marshal resolved refs: %s", err)), nil + } + return mcp.NewToolResultText(fmt.Sprintf("Path did not point to a file or directory, but resolved git ref to %s with possible path matches: %s", resolvedRefs, matchingFilesJSON)), nil } return mcp.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil From bf673f8d07a108c4b3276b9a2beec1f93fe4d8ae Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Tue, 8 Jul 2025 13:37:58 +0100 Subject: [PATCH 8/9] unit test cases for filterPaths maxResults param --- pkg/github/repositories_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 81d80a2e5..0b9c5d9f9 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -2137,6 +2137,39 @@ func Test_filterPaths(t *testing.T) { maxResults: -1, expected: []string{"name/"}, }, + { + name: "max results limit 2", + tree: []*github.TreeEntry{ + {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")}, + }, + path: "folder/", + maxResults: 2, + expected: []string{"folder/", "nested/folder/"}, + }, + { + name: "max results limit 1", + tree: []*github.TreeEntry{ + {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")}, + }, + path: "folder/", + maxResults: 1, + expected: []string{"folder/"}, + }, + { + name: "max results limit 0", + tree: []*github.TreeEntry{ + {Path: github.Ptr("folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("nested/folder"), Type: github.Ptr("tree")}, + {Path: github.Ptr("nested/nested/folder"), Type: github.Ptr("tree")}, + }, + path: "folder/", + maxResults: 0, + expected: []string{}, + }, } for _, tc := range tests { From 3aea320515696610aabdb207ce4c687023cf1a06 Mon Sep 17 00:00:00 2001 From: LuluBeatson Date: Fri, 11 Jul 2025 16:04:21 +0100 Subject: [PATCH 9/9] resolveGitReference - NewGitHubAPIErrorToCtx --- pkg/github/repositories.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index b1fce6beb..732f20ab1 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -1344,17 +1344,19 @@ func resolveGitReference(ctx context.Context, githubClient *github.Client, owner // 2. If neither provided, use the default branch as ref if ref == "" { - repoInfo, _, err := githubClient.Repositories.Get(ctx, owner, repo) + repoInfo, resp, err := githubClient.Repositories.Get(ctx, owner, repo) if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get repository info", resp, err) return nil, fmt.Errorf("failed to get repository info: %w", err) } ref = fmt.Sprintf("refs/heads/%s", repoInfo.GetDefaultBranch()) } // 3. Get the SHA from the ref - reference, _, err := githubClient.Git.GetRef(ctx, owner, repo, ref) + reference, resp, err := githubClient.Git.GetRef(ctx, owner, repo, ref) if err != nil { - return nil, fmt.Errorf("failed to get reference for default branch: %w", err) + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference", resp, err) + return nil, fmt.Errorf("failed to get reference: %w", err) } sha = reference.GetObject().GetSHA() pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy