Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc23b1f9

Browse files
authored
get_file_content Match Paths in Git Tree if Full Path Unknown (#650)
* add contingency to match path in git tree* resolveGitReference helper* fix: handling of directories* Test_filterPaths* filterPaths - trailing slashes* fix: close response body, improve error messages, docs* update tool result message about resolved git ref* unit test cases for filterPaths maxResults param* resolveGitReference - NewGitHubAPIErrorToCtx
1 parent42e5ce9 commitc23b1f9

File tree

2 files changed

+317
-45
lines changed

2 files changed

+317
-45
lines changed

‎pkg/github/repositories.go

Lines changed: 114 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"io"
99
"net/http"
1010
"net/url"
11-
"strconv"
1211
"strings"
1312

1413
ghErrors"github.com/github/github-mcp-server/pkg/errors"
@@ -495,33 +494,18 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
495494
returnmcp.NewToolResultError(err.Error()),nil
496495
}
497496

498-
rawOpts:=&raw.ContentOpts{}
499-
500-
ifstrings.HasPrefix(ref,"refs/pull/") {
501-
prNumber:=strings.TrimSuffix(strings.TrimPrefix(ref,"refs/pull/"),"/head")
502-
iflen(prNumber)>0 {
503-
// fetch the PR from the API to get the latest commit and use SHA
504-
githubClient,err:=getClient(ctx)
505-
iferr!=nil {
506-
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
507-
}
508-
prNum,err:=strconv.Atoi(prNumber)
509-
iferr!=nil {
510-
returnnil,fmt.Errorf("invalid pull request number: %w",err)
511-
}
512-
pr,_,err:=githubClient.PullRequests.Get(ctx,owner,repo,prNum)
513-
iferr!=nil {
514-
returnnil,fmt.Errorf("failed to get pull request: %w",err)
515-
}
516-
sha=pr.GetHead().GetSHA()
517-
ref=""
518-
}
497+
client,err:=getClient(ctx)
498+
iferr!=nil {
499+
returnmcp.NewToolResultError("failed to get GitHub client"),nil
519500
}
520501

521-
rawOpts.SHA=sha
522-
rawOpts.Ref=ref
502+
rawOpts,err:=resolveGitReference(ctx,client,owner,repo,ref,sha)
503+
iferr!=nil {
504+
returnmcp.NewToolResultError(fmt.Sprintf("failed to resolve git reference: %s",err)),nil
505+
}
523506

524-
// 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.
507+
// If the path is (most likely) not to be a directory, we will
508+
// first try to get the raw content from the GitHub raw content API.
525509
ifpath!=""&&!strings.HasSuffix(path,"/") {
526510

527511
rawClient,err:=getRawClient(ctx)
@@ -580,36 +564,51 @@ func GetFileContents(getClient GetClientFn, getRawClient raw.GetRawClientFn, t t
580564
}
581565
}
582566

583-
client,err:=getClient(ctx)
584-
iferr!=nil {
585-
returnmcp.NewToolResultError("failed to get GitHub client"),nil
586-
}
587-
588-
ifsha!="" {
589-
ref=sha
567+
ifrawOpts.SHA!="" {
568+
ref=rawOpts.SHA
590569
}
591570
ifstrings.HasSuffix(path,"/") {
592571
opts:=&github.RepositoryContentGetOptions{Ref:ref}
593572
_,dirContent,resp,err:=client.Repositories.GetContents(ctx,owner,repo,path,opts)
594-
iferr!=nil {
595-
returnmcp.NewToolResultError("failed to get file contents"),nil
596-
}
597-
deferfunc() {_=resp.Body.Close() }()
598-
599-
ifresp.StatusCode!=200 {
600-
body,err:=io.ReadAll(resp.Body)
573+
iferr==nil&&resp.StatusCode==http.StatusOK {
574+
deferfunc() {_=resp.Body.Close() }()
575+
r,err:=json.Marshal(dirContent)
601576
iferr!=nil {
602-
returnmcp.NewToolResultError("failed toread response body"),nil
577+
returnmcp.NewToolResultError("failed tomarshal response"),nil
603578
}
604-
returnmcp.NewToolResultError(fmt.Sprintf("failed to get file contents: %s",string(body))),nil
579+
returnmcp.NewToolResultText(string(r)),nil
605580
}
581+
}
582+
583+
// The path does not point to a file or directory.
584+
// Instead let's try to find it in the Git Tree by matching the end of the path.
585+
586+
// Step 1: Get Git Tree recursively
587+
tree,resp,err:=client.Git.GetTree(ctx,owner,repo,ref,true)
588+
iferr!=nil {
589+
returnghErrors.NewGitHubAPIErrorResponse(ctx,
590+
"failed to get git tree",
591+
resp,
592+
err,
593+
),nil
594+
}
595+
deferfunc() {_=resp.Body.Close() }()
606596

607-
r,err:=json.Marshal(dirContent)
597+
// Step 2: Filter tree for matching paths
598+
constmaxMatchingFiles=3
599+
matchingFiles:=filterPaths(tree.Entries,path,maxMatchingFiles)
600+
iflen(matchingFiles)>0 {
601+
matchingFilesJSON,err:=json.Marshal(matchingFiles)
602+
iferr!=nil {
603+
returnmcp.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s",err)),nil
604+
}
605+
resolvedRefs,err:=json.Marshal(rawOpts)
608606
iferr!=nil {
609-
returnmcp.NewToolResultError("failed to marshalresponse"),nil
607+
returnmcp.NewToolResultError(fmt.Sprintf("failed to marshalresolved refs: %s",err)),nil
610608
}
611-
returnmcp.NewToolResultText(string(r)),nil
609+
returnmcp.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
612610
}
611+
613612
returnmcp.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
614613
}
615614
}
@@ -1293,3 +1292,74 @@ func GetTag(getClient GetClientFn, t translations.TranslationHelperFunc) (tool m
12931292
returnmcp.NewToolResultText(string(r)),nil
12941293
}
12951294
}
1295+
1296+
// filterPaths filters the entries in a GitHub tree to find paths that
1297+
// match the given suffix.
1298+
// maxResults limits the number of results returned to first maxResults entries,
1299+
// a maxResults of -1 means no limit.
1300+
// It returns a slice of strings containing the matching paths.
1301+
// Directories are returned with a trailing slash.
1302+
funcfilterPaths(entries []*github.TreeEntry,pathstring,maxResultsint) []string {
1303+
// Remove trailing slash for matching purposes, but flag whether we
1304+
// only want directories.
1305+
dirOnly:=false
1306+
ifstrings.HasSuffix(path,"/") {
1307+
dirOnly=true
1308+
path=strings.TrimSuffix(path,"/")
1309+
}
1310+
1311+
matchedPaths:= []string{}
1312+
for_,entry:=rangeentries {
1313+
iflen(matchedPaths)==maxResults {
1314+
break// Limit the number of results to maxResults
1315+
}
1316+
ifdirOnly&&entry.GetType()!="tree" {
1317+
continue// Skip non-directory entries if dirOnly is true
1318+
}
1319+
entryPath:=entry.GetPath()
1320+
ifentryPath=="" {
1321+
continue// Skip empty paths
1322+
}
1323+
ifstrings.HasSuffix(entryPath,path) {
1324+
ifentry.GetType()=="tree" {
1325+
entryPath+="/"// Return directories with a trailing slash
1326+
}
1327+
matchedPaths=append(matchedPaths,entryPath)
1328+
}
1329+
}
1330+
returnmatchedPaths
1331+
}
1332+
1333+
// resolveGitReference resolves git references with the following logic:
1334+
// 1. If SHA is provided, it takes precedence
1335+
// 2. If neither is provided, use the default branch as ref
1336+
// 3. Get commit SHA from the ref
1337+
// Refs can look like `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`
1338+
// The function returns the resolved ref, commit SHA and any error.
1339+
funcresolveGitReference(ctx context.Context,githubClient*github.Client,owner,repo,ref,shastring) (*raw.ContentOpts,error) {
1340+
// 1. If SHA is provided, use it directly
1341+
ifsha!="" {
1342+
return&raw.ContentOpts{Ref:"",SHA:sha},nil
1343+
}
1344+
1345+
// 2. If neither provided, use the default branch as ref
1346+
ifref=="" {
1347+
repoInfo,resp,err:=githubClient.Repositories.Get(ctx,owner,repo)
1348+
iferr!=nil {
1349+
_,_=ghErrors.NewGitHubAPIErrorToCtx(ctx,"failed to get repository info",resp,err)
1350+
returnnil,fmt.Errorf("failed to get repository info: %w",err)
1351+
}
1352+
ref=fmt.Sprintf("refs/heads/%s",repoInfo.GetDefaultBranch())
1353+
}
1354+
1355+
// 3. Get the SHA from the ref
1356+
reference,resp,err:=githubClient.Git.GetRef(ctx,owner,repo,ref)
1357+
iferr!=nil {
1358+
_,_=ghErrors.NewGitHubAPIErrorToCtx(ctx,"failed to get reference",resp,err)
1359+
returnnil,fmt.Errorf("failed to get reference: %w",err)
1360+
}
1361+
sha=reference.GetObject().GetSHA()
1362+
1363+
// Use provided ref, or it will be empty which defaults to the default branch
1364+
return&raw.ContentOpts{Ref:ref,SHA:sha},nil
1365+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp