-
-
Save technoweenie/4b6cf6d714e30e090ed0 to your computer and use it in GitHub Desktop.
prototype to build a go lib into a git packfile with a single commit, and serve it with a custom git smart http server in go
This file contains 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 ( | |
"fmt" | |
"github.com/libgit2/git2go" | |
"io/ioutil" | |
"os" | |
"path/filepath" | |
"strings" | |
"time" | |
) | |
func BuildGoPack(directory string) (err error) { | |
os.RemoveAll("/Users/rick/p/go-git-experiment/repo") | |
repo, err := git.InitRepository("/Users/rick/p/go-git-experiment/repo", true) | |
if err != nil { | |
return | |
} | |
odb, err := repo.Odb() | |
if err != nil { | |
return | |
} | |
oid, err := writeDirToOdb(repo, odb, directory) | |
if err != nil { | |
return | |
} | |
tree, err := repo.LookupTree(oid) | |
if err != nil { | |
return | |
} | |
author := &git.Signature{ | |
Name: "Rick", | |
Email: "technoweenie@gmail.com", | |
When: time.Now(), | |
} | |
oid, err = repo.CreateCommit("refs/heads/master", author, author, "BOOYA", tree) | |
if err != nil { | |
return | |
} | |
pb, err := repo.NewPackbuilder() | |
if err != nil { | |
return | |
} | |
err = pb.InsertCommit(oid) | |
fmt.Println(pb.ObjectCount()) | |
return pb.WriteToFile("/Users/rick/p/go-git-experiment/repo.pack") | |
} | |
func writeFileToOdb(odb *git.Odb, parentdir string, fileinfo os.FileInfo) (oid *git.Oid, err error) { | |
fullname := filepath.Join(parentdir, fileinfo.Name()) | |
contents, err := ioutil.ReadFile(fullname) | |
if err != nil { | |
return | |
} | |
return odb.Write(contents, git.OBJ_BLOB) | |
} | |
func writeDirToOdb(repo *git.Repository, odb *git.Odb, dir string) (oid *git.Oid, err error) { | |
if base := filepath.Base(dir); strings.HasPrefix(base, ".") { | |
return | |
} | |
tree, err := repo.TreeBuilder() | |
if err != nil { | |
return | |
} | |
files, err := ioutil.ReadDir(dir) | |
if err != nil { | |
return | |
} | |
var filemode int | |
for _, file := range files { | |
if file.IsDir() { | |
oid, err = writeDirToOdb(repo, odb, filepath.Join(dir, file.Name())) | |
if err != nil { | |
return nil, err | |
} | |
filemode = 0040000 | |
} else { | |
oid, err = writeFileToOdb(odb, dir, file) | |
if err != nil { | |
return nil, err | |
} | |
filemode = 0100644 | |
} | |
if oid != nil { | |
tree.Insert(file.Name(), oid, filemode) | |
} | |
} | |
return tree.Write() | |
} | |
func main() { | |
err := BuildGoPack("/Users/rick/p/multipartstreamer") | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println("DONE") | |
} |
This file contains 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 ( | |
"encoding/hex" | |
"fmt" | |
"github.com/libgit2/git2go" | |
"io" | |
"net/http" | |
"os" | |
"strings" | |
) | |
func main() { | |
repo, err := git.OpenRepository("/Users/rick/p/go-git-experiment/repo") | |
if err != nil { | |
panic(err) | |
} | |
ref, err := repo.LookupReference("refs/heads/master") | |
if err != nil { | |
panic(err) | |
} | |
oidobj := ref.Target() | |
oid := oidobj.String() | |
fmt.Println("serving", oid) | |
http.HandleFunc("/info/refs", func(w http.ResponseWriter, r *http.Request) { | |
if r.Method != "GET" { | |
w.WriteHeader(http.StatusMethodNotAllowed) | |
fmt.Fprint(w, "Not Found") | |
} | |
query := r.URL.Query() | |
if query.Get("service") != "git-upload-pack" { | |
w.WriteHeader(http.StatusNotFound) | |
fmt.Fprint(w, "Not Found") | |
return | |
} | |
header := w.Header() | |
header.Set("Content-Type", "application/x-git-upload-pack-advertisement") | |
header.Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT") | |
header.Set("Pragma", "no-cache") | |
header.Set("Cache-Control", "no-cache, max-age=0, must-revalidate") | |
writeLine(w, "# service=git-upload-pack") | |
writeEmptyLine(w) | |
writeLine(w, oid+" HEAD\u0000side-band-64k agent=go-lol/0.0.1") | |
writeLine(w, oid+" refs/heads/master") | |
writeEmptyLine(w) | |
}) | |
http.HandleFunc("/git-upload-pack", func(w http.ResponseWriter, r *http.Request) { | |
if r.Method != "POST" { | |
w.WriteHeader(http.StatusMethodNotAllowed) | |
fmt.Fprint(w, "Not Found") | |
} | |
if wantsOid(r.Body, oid) { | |
header := w.Header() | |
header.Set("Content-Type", "application/x-git-upload-pack-result") | |
header.Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT") | |
header.Set("Pragma", "no-cache") | |
header.Set("Cache-Control", "no-cache, max-age=0, must-revalidate") | |
writeLine(w, "ACK "+oid) | |
err := sendPackfile(w, "/Users/rick/p/go-git-experiment/repo.pack") | |
if err != nil { | |
w.WriteHeader(http.StatusInternalServerError) | |
fmt.Println(err) | |
fmt.Fprint(w, "Error") | |
} | |
return | |
} | |
w.WriteHeader(http.StatusNotFound) | |
fmt.Fprint(w, "Not Found") | |
}) | |
http.ListenAndServe(":8080", nil) | |
} | |
func writeEmptyLine(w http.ResponseWriter) (int, error) { | |
return fmt.Fprint(w, "0000") | |
} | |
func writeLine(w http.ResponseWriter, line string) (int, error) { | |
size := len(line) + 5 | |
full := lenToHex(uint16(size)) + line + "\n" | |
return fmt.Fprint(w, full) | |
} | |
func lenToHex(len uint16) string { | |
lenbytes := []byte{byte(len / 256), byte(len % 256)} | |
return hex.EncodeToString(lenbytes) | |
} | |
func wantsOid(reader io.Reader, oid string) (wants bool) { | |
wants = true | |
reading := true | |
prefix := "want " + oid | |
for wants == true && reading == true { | |
wants, reading = lineWantsOid(reader, prefix) | |
} | |
return | |
} | |
func lineWantsOid(reader io.Reader, prefix string) (wants, reading bool) { | |
sizebytes := make([]byte, 4) | |
n, err := reader.Read(sizebytes) | |
if err != nil { | |
return false, false | |
} | |
if n != 4 { | |
return false, false | |
} | |
sizestring := string(sizebytes) | |
if sizestring == "0000" { | |
return true, false | |
} | |
size, _ := hex.DecodeString(sizestring) | |
buffer := make([]byte, size[1]-4) | |
n, err = reader.Read(buffer) | |
if err != nil { | |
return false, false | |
} | |
if strings.HasPrefix(string(buffer), prefix) { | |
return true, true | |
} | |
return false, false | |
} | |
func sendPackfile(w http.ResponseWriter, packfile string) error { | |
file, err := os.Open(packfile) | |
if err != nil { | |
return err | |
} | |
defer func() { | |
if err := file.Close(); err != nil { | |
panic(err) | |
} | |
}() | |
buf := make([]byte, 65519) | |
for { | |
n, err := file.Read(buf) | |
if err != nil && err != io.EOF { | |
return err | |
} | |
if n == 0 { | |
break | |
} | |
hexsize := lenToHex(uint16(n + 5)) | |
w.Write([]byte(hexsize)) | |
w.Write([]byte{byte(1)}) | |
w.Write(buf[0:n]) | |
} | |
w.Write([]byte("0000")) | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment