Skip to content

Instantly share code, notes, and snippets.

@m1cm1c
Last active April 10, 2024 11:20
Show Gist options
  • Save m1cm1c/64203ebd36ef7f39dddd053067ef3c03 to your computer and use it in GitHub Desktop.
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
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