@@ -12,7 +12,6 @@ import (
1212 ghErrors "github.com/github/github-mcp-server/pkg/errors"
1313 "github.com/github/github-mcp-server/pkg/inventory"
1414 "github.com/github/github-mcp-server/pkg/octicons"
15- "github.com/github/github-mcp-server/pkg/raw"
1615 "github.com/github/github-mcp-server/pkg/translations"
1716 "github.com/github/github-mcp-server/pkg/utils"
1817 "github.com/google/go-github/v79/github"
@@ -1327,21 +1326,20 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool {
13271326 defer func () { _ = resp .Body .Close () }()
13281327 }
13291328 } else {
1329+ var base * github.Commit
13301330 // Repository is empty, need to initialize it first
1331- defaultRef , base , err : = initializeRepository (ctx , client , owner , repo )
1331+ ref , base , err = initializeRepository (ctx , client , owner , repo )
13321332 if err != nil {
13331333 return utils .NewToolResultError (fmt .Sprintf ("failed to initialize repository: %v" , err )), nil , nil
13341334 }
13351335
1336- defaultBranch := strings .TrimPrefix (* defaultRef .Ref , "refs/heads/" )
1336+ defaultBranch := strings .TrimPrefix (* ref .Ref , "refs/heads/" )
13371337 if branch != defaultBranch {
13381338 // Create the requested branch from the default branch
13391339 ref , err = createReferenceFromDefaultBranch (ctx , client , owner , repo , branch )
13401340 if err != nil {
13411341 return utils .NewToolResultError (fmt .Sprintf ("failed to create branch from default: %v" , err )), nil , nil
13421342 }
1343- } else {
1344- ref = defaultRef
13451343 }
13461344
13471345 baseCommit = base
@@ -1407,6 +1405,7 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool {
14071405 }
14081406
14091407 // Update the reference to point to the new commit
1408+ fmt .Printf ("=================== ref: %v" , ref )
14101409 ref .Object .SHA = newCommit .SHA
14111410 updatedRef , resp , err := client .Git .UpdateRef (ctx , owner , repo , * ref .Ref , github.UpdateRef {
14121411 SHA : * newCommit .SHA ,
@@ -1431,80 +1430,6 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool {
14311430 )
14321431}
14331432
1434- func initializeRepository (ctx context.Context , client * github.Client , owner , repo string ) (ref * github.Reference , baseCommit * github.Commit , err error ) {
1435- // First, we need to check what's the default branch in this empty repo should be:
1436- repository , resp , err := client .Repositories .Get (ctx , owner , repo )
1437- if err != nil {
1438- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get repository" , resp , err )
1439- return nil , nil , fmt .Errorf ("failed to get repository: %w" , err )
1440- }
1441- if resp != nil && resp .Body != nil {
1442- defer func () { _ = resp .Body .Close () }()
1443- }
1444-
1445- defaultBranch := repository .GetDefaultBranch ()
1446-
1447- fileOpts := & github.RepositoryContentFileOptions {
1448- Message : github .Ptr ("Initial commit" ),
1449- Content : []byte ("" ),
1450- Branch : github .Ptr (defaultBranch ),
1451- }
1452-
1453- // Create an initial empty commit to create the default branch
1454- createResp , resp , err := client .Repositories .CreateFile (ctx , owner , repo , "README.md" , fileOpts )
1455- if err != nil {
1456- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to create initial file" , resp , err )
1457- return nil , nil , fmt .Errorf ("failed to create initial file: %w" , err )
1458- }
1459- if resp != nil && resp .Body != nil {
1460- defer func () { _ = resp .Body .Close () }()
1461- }
1462-
1463- // Get the commit that was just created to use as base for remaining files
1464- baseCommit , resp , err = client .Git .GetCommit (ctx , owner , repo , * createResp .Commit .SHA )
1465- if err != nil {
1466- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get initial commit" , resp , err )
1467- return nil , nil , fmt .Errorf ("failed to get initial commit: %w" , err )
1468- }
1469- if resp != nil && resp .Body != nil {
1470- defer func () { _ = resp .Body .Close () }()
1471- }
1472-
1473- ref , resp , err = client .Git .GetRef (ctx , owner , repo , "refs/heads/" + defaultBranch )
1474- if err != nil {
1475- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get final reference" , resp , err )
1476- return nil , nil , fmt .Errorf ("failed to get branch reference after initial commit: %w" , err )
1477- }
1478- if resp != nil && resp .Body != nil {
1479- defer func () { _ = resp .Body .Close () }()
1480- }
1481-
1482- return ref , baseCommit , nil
1483- }
1484-
1485- func createReferenceFromDefaultBranch (ctx context.Context , client * github.Client , owner , repo , branch string ) (* github.Reference , error ) {
1486- defaultRef , err := resolveDefaultBranch (ctx , client , owner , repo )
1487- if err != nil {
1488- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to resolve default branch" , nil , err )
1489- return nil , fmt .Errorf ("failed to resolve default branch: %w" , err )
1490- }
1491-
1492- // Create the new branch reference
1493- createdRef , resp , err := client .Git .CreateRef (ctx , owner , repo , github.CreateRef {
1494- Ref : "refs/heads/" + branch ,
1495- SHA : * defaultRef .Object .SHA ,
1496- })
1497- if err != nil {
1498- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to create new branch reference" , resp , err )
1499- return nil , fmt .Errorf ("failed to create new branch reference: %w" , err )
1500- }
1501- if resp != nil && resp .Body != nil {
1502- defer func () { _ = resp .Body .Close () }()
1503- }
1504-
1505- return createdRef , nil
1506- }
1507-
15081433// ListTags creates a tool to list tags in a GitHub repository.
15091434func ListTags (t translations.TranslationHelperFunc ) inventory.ServerTool {
15101435 return NewTool (
@@ -1895,244 +1820,6 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool
18951820 )
18961821}
18971822
1898- // matchFiles searches for files in the Git tree that match the given path.
1899- // It's used when GetContents fails or returns unexpected results.
1900- func matchFiles (ctx context.Context , client * github.Client , owner , repo , ref , path string , rawOpts * raw.ContentOpts , rawAPIResponseCode int ) (* mcp.CallToolResult , any , error ) {
1901- // Step 1: Get Git Tree recursively
1902- tree , response , err := client .Git .GetTree (ctx , owner , repo , ref , true )
1903- if err != nil {
1904- return ghErrors .NewGitHubAPIErrorResponse (ctx ,
1905- "failed to get git tree" ,
1906- response ,
1907- err ,
1908- ), nil , nil
1909- }
1910- defer func () { _ = response .Body .Close () }()
1911-
1912- // Step 2: Filter tree for matching paths
1913- const maxMatchingFiles = 3
1914- matchingFiles := filterPaths (tree .Entries , path , maxMatchingFiles )
1915- if len (matchingFiles ) > 0 {
1916- matchingFilesJSON , err := json .Marshal (matchingFiles )
1917- if err != nil {
1918- return utils .NewToolResultError (fmt .Sprintf ("failed to marshal matching files: %s" , err )), nil , nil
1919- }
1920- resolvedRefs , err := json .Marshal (rawOpts )
1921- if err != nil {
1922- return utils .NewToolResultError (fmt .Sprintf ("failed to marshal resolved refs: %s" , err )), nil , nil
1923- }
1924- if rawAPIResponseCode > 0 {
1925- return utils .NewToolResultText (fmt .Sprintf ("Resolved potential matches in the repository tree (resolved refs: %s, matching files: %s), but the content API returned an unexpected status code %d." , string (resolvedRefs ), string (matchingFilesJSON ), rawAPIResponseCode )), nil , nil
1926- }
1927- return utils .NewToolResultText (fmt .Sprintf ("Resolved potential matches in the repository tree (resolved refs: %s, matching files: %s)." , string (resolvedRefs ), string (matchingFilesJSON ))), nil , nil
1928- }
1929- return utils .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 , nil
1930- }
1931-
1932- // filterPaths filters the entries in a GitHub tree to find paths that
1933- // match the given suffix.
1934- // maxResults limits the number of results returned to first maxResults entries,
1935- // a maxResults of -1 means no limit.
1936- // It returns a slice of strings containing the matching paths.
1937- // Directories are returned with a trailing slash.
1938- func filterPaths (entries []* github.TreeEntry , path string , maxResults int ) []string {
1939- // Remove trailing slash for matching purposes, but flag whether we
1940- // only want directories.
1941- dirOnly := false
1942- if strings .HasSuffix (path , "/" ) {
1943- dirOnly = true
1944- path = strings .TrimSuffix (path , "/" )
1945- }
1946-
1947- matchedPaths := []string {}
1948- for _ , entry := range entries {
1949- if len (matchedPaths ) == maxResults {
1950- break // Limit the number of results to maxResults
1951- }
1952- if dirOnly && entry .GetType () != "tree" {
1953- continue // Skip non-directory entries if dirOnly is true
1954- }
1955- entryPath := entry .GetPath ()
1956- if entryPath == "" {
1957- continue // Skip empty paths
1958- }
1959- if strings .HasSuffix (entryPath , path ) {
1960- if entry .GetType () == "tree" {
1961- entryPath += "/" // Return directories with a trailing slash
1962- }
1963- matchedPaths = append (matchedPaths , entryPath )
1964- }
1965- }
1966- return matchedPaths
1967- }
1968-
1969- // looksLikeSHA returns true if the string appears to be a Git commit SHA.
1970- // A SHA is a 40-character hexadecimal string.
1971- func looksLikeSHA (s string ) bool {
1972- if len (s ) != 40 {
1973- return false
1974- }
1975- for _ , c := range s {
1976- if (c < '0' || c > '9' ) && (c < 'a' || c > 'f' ) && (c < 'A' || c > 'F' ) {
1977- return false
1978- }
1979- }
1980- return true
1981- }
1982-
1983- // resolveGitReference takes a user-provided ref and sha and resolves them into a
1984- // definitive commit SHA and its corresponding fully-qualified reference.
1985- //
1986- // The resolution logic follows a clear priority:
1987- //
1988- // 1. If a specific commit `sha` is provided, it takes precedence and is used directly,
1989- // and all reference resolution is skipped.
1990- //
1991- // 1a. If `sha` is empty but `ref` looks like a commit SHA (40 hexadecimal characters),
1992- // it is returned as-is without any API calls or reference resolution.
1993- //
1994- // 2. If no `sha` is provided and `ref` does not look like a SHA, the function resolves
1995- // the `ref` string into a fully-qualified format (e.g., "refs/heads/main") by trying
1996- // the following steps in order:
1997- // a). **Empty Ref:** If `ref` is empty, the repository's default branch is used.
1998- // b). **Fully-Qualified:** If `ref` already starts with "refs/", it's considered fully
1999- // qualified and used as-is.
2000- // c). **Partially-Qualified:** If `ref` starts with "heads/" or "tags/", it is
2001- // prefixed with "refs/" to make it fully-qualified.
2002- // d). **Short Name:** Otherwise, the `ref` is treated as a short name. The function
2003- // first attempts to resolve it as a branch ("refs/heads/<ref>"). If that
2004- // returns a 404 Not Found error, it then attempts to resolve it as a tag
2005- // ("refs/tags/<ref>").
2006- //
2007- // 3. **Final Lookup:** Once a fully-qualified ref is determined, a final API call
2008- // is made to fetch that reference's definitive commit SHA.
2009- //
2010- // Any unexpected (non-404) errors during the resolution process are returned
2011- // immediately. All API errors are logged with rich context to aid diagnostics.
2012- func resolveGitReference (ctx context.Context , githubClient * github.Client , owner , repo , ref , sha string ) (* raw.ContentOpts , bool , error ) {
2013- // 1) If SHA explicitly provided, it's the highest priority.
2014- if sha != "" {
2015- return & raw.ContentOpts {Ref : "" , SHA : sha }, false , nil
2016- }
2017-
2018- // 1a) If sha is empty but ref looks like a SHA, return it without changes
2019- if looksLikeSHA (ref ) {
2020- return & raw.ContentOpts {Ref : "" , SHA : ref }, false , nil
2021- }
2022-
2023- originalRef := ref // Keep original ref for clearer error messages down the line.
2024-
2025- // 2) If no SHA is provided, we try to resolve the ref into a fully-qualified format.
2026- var reference * github.Reference
2027- var resp * github.Response
2028- var err error
2029- var fallbackUsed bool
2030-
2031- switch {
2032- case originalRef == "" :
2033- // 2a) If ref is empty, determine the default branch.
2034- reference , err = resolveDefaultBranch (ctx , githubClient , owner , repo )
2035- if err != nil {
2036- return nil , false , err // Error is already wrapped in resolveDefaultBranch.
2037- }
2038- ref = reference .GetRef ()
2039- case strings .HasPrefix (originalRef , "refs/" ):
2040- // 2b) Already fully qualified. The reference will be fetched at the end.
2041- case strings .HasPrefix (originalRef , "heads/" ) || strings .HasPrefix (originalRef , "tags/" ):
2042- // 2c) Partially qualified. Make it fully qualified.
2043- ref = "refs/" + originalRef
2044- default :
2045- // 2d) It's a short name, so we try to resolve it to either a branch or a tag.
2046- branchRef := "refs/heads/" + originalRef
2047- reference , resp , err = githubClient .Git .GetRef (ctx , owner , repo , branchRef )
2048-
2049- if err == nil {
2050- ref = branchRef // It's a branch.
2051- } else {
2052- // The branch lookup failed. Check if it was a 404 Not Found error.
2053- ghErr , isGhErr := err .(* github.ErrorResponse )
2054- if isGhErr && ghErr .Response .StatusCode == http .StatusNotFound {
2055- tagRef := "refs/tags/" + originalRef
2056- reference , resp , err = githubClient .Git .GetRef (ctx , owner , repo , tagRef )
2057- if err == nil {
2058- ref = tagRef // It's a tag.
2059- } else {
2060- // The tag lookup also failed. Check if it was a 404 Not Found error.
2061- ghErr2 , isGhErr2 := err .(* github.ErrorResponse )
2062- if isGhErr2 && ghErr2 .Response .StatusCode == http .StatusNotFound {
2063- if originalRef == "main" {
2064- reference , err = resolveDefaultBranch (ctx , githubClient , owner , repo )
2065- if err != nil {
2066- return nil , false , err // Error is already wrapped in resolveDefaultBranch.
2067- }
2068- // Update ref to the actual default branch ref so the note can be generated
2069- ref = reference .GetRef ()
2070- fallbackUsed = true
2071- break
2072- }
2073- return nil , false , fmt .Errorf ("could not resolve ref %q as a branch or a tag" , originalRef )
2074- }
2075-
2076- // The tag lookup failed for a different reason.
2077- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get reference (tag)" , resp , err )
2078- return nil , false , fmt .Errorf ("failed to get reference for tag '%s': %w" , originalRef , err )
2079- }
2080- } else {
2081- // The branch lookup failed for a different reason.
2082- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get reference (branch)" , resp , err )
2083- return nil , false , fmt .Errorf ("failed to get reference for branch '%s': %w" , originalRef , err )
2084- }
2085- }
2086- }
2087-
2088- if reference == nil {
2089- reference , resp , err = githubClient .Git .GetRef (ctx , owner , repo , ref )
2090- if err != nil {
2091- if ref == "refs/heads/main" {
2092- reference , err = resolveDefaultBranch (ctx , githubClient , owner , repo )
2093- if err != nil {
2094- return nil , false , err // Error is already wrapped in resolveDefaultBranch.
2095- }
2096- // Update ref to the actual default branch ref so the note can be generated
2097- ref = reference .GetRef ()
2098- fallbackUsed = true
2099- } else {
2100- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get final reference" , resp , err )
2101- return nil , false , fmt .Errorf ("failed to get final reference for %q: %w" , ref , err )
2102- }
2103- }
2104- }
2105-
2106- sha = reference .GetObject ().GetSHA ()
2107- return & raw.ContentOpts {Ref : ref , SHA : sha }, fallbackUsed , nil
2108- }
2109-
2110- func resolveDefaultBranch (ctx context.Context , githubClient * github.Client , owner , repo string ) (* github.Reference , error ) {
2111- repoInfo , resp , err := githubClient .Repositories .Get (ctx , owner , repo )
2112- if err != nil {
2113- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get repository info" , resp , err )
2114- return nil , fmt .Errorf ("failed to get repository info: %w" , err )
2115- }
2116-
2117- if resp != nil && resp .Body != nil {
2118- _ = resp .Body .Close ()
2119- }
2120-
2121- defaultBranch := repoInfo .GetDefaultBranch ()
2122-
2123- defaultRef , resp , err := githubClient .Git .GetRef (ctx , owner , repo , "heads/" + defaultBranch )
2124- if err != nil {
2125- _ , _ = ghErrors .NewGitHubAPIErrorToCtx (ctx , "failed to get default branch reference" , resp , err )
2126- return nil , fmt .Errorf ("failed to get default branch reference: %w" , err )
2127- }
2128-
2129- if resp != nil && resp .Body != nil {
2130- defer func () { _ = resp .Body .Close () }()
2131- }
2132-
2133- return defaultRef , nil
2134- }
2135-
21361823// ListStarredRepositories creates a tool to list starred repositories for the authenticated user or a specified user.
21371824func ListStarredRepositories (t translations.TranslationHelperFunc ) inventory.ServerTool {
21381825 return NewTool (
0 commit comments