Skip to content

Instantly share code, notes, and snippets.

@technoweenie
Created May 19, 2013 06:19
Show Gist options
  • Save technoweenie/4b6cf6d714e30e090ed0 to your computer and use it in GitHub Desktop.
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
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")
}
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