Skip to content

Instantly share code, notes, and snippets.

@defp
Forked from shanzi/gittp.go
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save defp/0c6fd6fe69840b2d600b to your computer and use it in GitHub Desktop.
Save defp/0c6fd6fe69840b2d600b to your computer and use it in GitHub Desktop.
/*
gittip: a basic git http server.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright 2014 Chase Zhang <yun.er.run@gmail.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
*/
/*
Usage:
Make sure you have git installed and can be accessed by this program.
Put this file anywhere you like (typically under `$GOPATH/src/`)
and then install the only third-party dependency by:
go get github.com/zenazn/goji
After this, you start the git server by runing `go run gittp.go`.
You should change the variable `realm` which is used by Basic HTTP Auth
to identify your server and replace the user/password combinations
defined in `users`.
To create or delete repos:
curl -u [username]:[password] -X PUT http://localhost:8000/[repo name]
curl -u [username]:[password] -X DELETE http://localhost:8000/[repo name]
Please enjoy it!
*/
package main
import (
"encoding/base64"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path"
"regexp"
"strings"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
const realm = "gittp"
// A map filled with allowed users and their passwords.
// Replace the contents with your own.
var users = map[string]string{
"admin": "adminpassword",
}
var gitRoot = path.Join(os.TempDir(), "git_repo")
func createRepo(c web.C, w http.ResponseWriter, r *http.Request) {
reponame := c.URLParams["reponame"]
repopath := path.Join(gitRoot, reponame)
if _, err := os.Stat(repopath); err == nil {
w.WriteHeader(400)
fmt.Fprintf(w, "Repo `%s` already exists!\n", reponame)
} else {
gitInitCmd := exec.Command("git", "init", "--bare", repopath)
_, err := gitInitCmd.CombinedOutput()
if err != nil {
w.WriteHeader(500)
fmt.Fprintf(w, "Initialize git repo `%s` failed!\n", reponame)
} else {
fmt.Fprintf(w, "Empty git repo `%s` initialized!\n", reponame)
}
}
}
func deleteRepo(c web.C, w http.ResponseWriter, r *http.Request) {
reponame := c.URLParams["reponame"]
repopath := path.Join(gitRoot, reponame)
if _, err := os.Stat(repopath); os.IsNotExist(err) {
w.WriteHeader(400)
fmt.Fprintf(w, "Repo `%s` does not exist!\n", reponame)
} else {
err := os.RemoveAll(repopath)
if err != nil {
w.WriteHeader(500)
fmt.Fprintf(w, "Delete repo `%s` failed!\n", reponame)
} else {
fmt.Fprintf(w, "Repo `%s` deleted!\n", reponame)
}
}
}
func inforefs(c web.C, w http.ResponseWriter, r *http.Request) {
reponame := c.URLParams["reponame"]
repopath := path.Join(gitRoot, reponame)
service := r.FormValue("service")
if len(service) > 0 {
w.Header().Add("Content-type", fmt.Sprintf("application/x-%s-advertisement", service))
gitLocalCmd := exec.Command(
"git",
string(service[4:]),
"--stateless-rpc",
"--advertise-refs",
repopath)
out, err := gitLocalCmd.CombinedOutput()
if err != nil {
w.WriteHeader(500)
fmt.Fprintln(w, "Internal Server Error")
w.Write(out)
} else {
serverAdvert := fmt.Sprintf("# service=%s", service)
length := len(serverAdvert) + 4
fmt.Fprintf(w, "%04x%s0000", length, serverAdvert)
w.Write(out)
}
} else {
fmt.Fprintln(w, "Invalid request")
w.WriteHeader(400)
}
}
func rpc(c web.C, w http.ResponseWriter, r *http.Request) {
reponame := c.URLParams["reponame"]
repopath := path.Join(gitRoot, reponame)
command := c.URLParams["command"]
if len(command) > 0 {
w.Header().Add("Content-type", fmt.Sprintf("application/x-git-%s-result", command))
w.WriteHeader(200)
gitCmd := exec.Command("git", command, "--stateless-rpc", repopath)
cmdIn, _ := gitCmd.StdinPipe()
cmdOut, _ := gitCmd.StdoutPipe()
body := r.Body
gitCmd.Start()
io.Copy(cmdIn, body)
io.Copy(w, cmdOut)
if command == "receive-pack" {
updateCmd := exec.Command("git", "--git-dir", repopath, "update-server-info")
updateCmd.Start()
}
} else {
w.WriteHeader(400)
fmt.Fprintln(w, "Invalid Request")
}
}
func generic(c web.C, w http.ResponseWriter, r *http.Request) {
reponame := c.URLParams["reponame"]
repopath := path.Join(gitRoot, reponame)
filepath := path.Join(gitRoot, r.URL.String())
if strings.HasPrefix(filepath, repopath) {
http.ServeFile(w, r, filepath)
} else {
w.WriteHeader(404)
}
}
func basicAuthMiddleware(c *web.C, h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
authField := r.Header["Authorization"]
if len(authField) > 0 {
auth := authField[0]
parts := strings.Split(auth, " ")
if len(parts) == 2 {
authType := parts[0]
combination := parts[1]
if authType == "Basic" {
s, e := base64.StdEncoding.DecodeString(combination)
str := string(s)
if e == nil {
parts := strings.SplitN(str, ":", 2)
if len(parts) == 2 && users[parts[0]] == parts[1] {
h.ServeHTTP(w, r)
return
}
}
}
}
}
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", realm))
w.WriteHeader(401)
fmt.Fprintln(w, "Unauthorized")
}
return http.HandlerFunc(fn)
}
func main() {
// create and delete repo
goji.Put("/:reponame", createRepo)
goji.Delete("/:reponame", deleteRepo)
// get repo info/refs
goji.Get("/:reponame/info/refs", inforefs)
goji.Head("/:reponame/info/refs", inforefs)
// RPC request on repo
goji.Post(regexp.MustCompile("^/(?P<reponame>[^/]+)/git-(?P<command>[^/]+)$"), rpc)
// access file contents
goji.Get("/:reponame/*", generic)
goji.Head("/:reponame/*", generic)
// start serving
goji.Use(basicAuthMiddleware)
goji.Serve()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment