Last active
April 27, 2025 09:26
-
-
Save m1cm1c/64203ebd36ef7f39dddd053067ef3c03 to your computer and use it in GitHub Desktop.
Go code for pulling all contents of a GitHub repository to the local file system. This is not a git clone, fetch, merge nor pull. Adapted from: https://gist.github.com/jaredhoward/f231391529efcd638bb7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"crypto/sha1" | |
"encoding/hex" | |
"fmt" | |
"io" | |
"os" | |
"path/filepath" | |
"strconv" | |
"strings" | |
"github.com/google/go-github/github" | |
) | |
func GetContents(client *github.Client, owner string, repo string, | |
destinationBasePath string, pathInRepo string) error { | |
ctx := context.Background() | |
_, directoryContent, _, err := | |
client.Repositories.GetContents(ctx, owner, repo, pathInRepo, nil) | |
if err != nil { | |
return err | |
} | |
for _, content := range directoryContent { | |
local := filepath.Join(destinationBasePath, *content.Path) | |
switch *content.Type { | |
case "file": | |
_, err := os.Stat(local) | |
if err == nil { | |
b, err1 := os.ReadFile(local) | |
if err1 == nil { | |
sha := calculateGitSHA1(b) | |
if *content.SHA == hex.EncodeToString(sha) { | |
// No need to update this file because the SHA is the same | |
continue | |
} | |
} | |
} | |
// Ignore submodules. | |
// A nicer way to test for submodules would be good. Please let me know if you know of any. | |
if strings.Contains(*content.GitURL, "/trees/") { | |
continue | |
} | |
err = downloadContents(client, owner, repo, content, local) | |
if err != nil { | |
return err | |
} | |
case "dir": | |
err := GetContents(client, owner, repo, | |
destinationBasePath, *content.Path) | |
if err != nil { | |
return err | |
} | |
} | |
} | |
return nil | |
} | |
func downloadContents(client *github.Client, owner string, repo string, | |
content *github.RepositoryContent, localPath string) error { | |
ctx := context.Background() | |
rc, err := client.Repositories.DownloadContents(ctx, owner, repo, *content.Path, nil) | |
if err != nil { | |
return err | |
} | |
defer rc.Close() | |
fileContentBytes, err := io.ReadAll(rc) | |
if err != nil { | |
return err | |
} | |
const directoryPermissions = 0700 | |
err = os.MkdirAll(filepath.Dir(localPath), directoryPermissions) | |
if err != nil { | |
return err | |
} | |
f, err := os.Create(localPath) | |
if err != nil { | |
return err | |
} | |
defer f.Close() | |
n, err := f.Write(fileContentBytes) | |
if err != nil { | |
return err | |
} | |
if n != *content.Size { | |
return fmt.Errorf("number of bytes differ, %d vs %d\n", n, *content.Size) | |
} | |
return err | |
} | |
// calculateGitSHA1 computes the github sha1 from a slice of bytes. | |
// The bytes are prepended with: "blob " + filesize + "\0" before running through sha1. | |
func calculateGitSHA1(contents []byte) []byte { | |
contentLen := len(contents) | |
blobSlice := []byte("blob " + strconv.Itoa(contentLen)) | |
blobSlice = append(blobSlice, '\x00') | |
blobSlice = append(blobSlice, contents...) | |
h := sha1.New() | |
h.Write(blobSlice) | |
bs := h.Sum(nil) | |
return bs | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment