Skip to content

Instantly share code, notes, and snippets.

@ShawnMilo
Created October 9, 2017 15:35
Show Gist options
  • Save ShawnMilo/54138dd079b2037fd0bd1aa4929c445c to your computer and use it in GitHub Desktop.
Save ShawnMilo/54138dd079b2037fd0bd1aa4929c445c to your computer and use it in GitHub Desktop.
Listen for GitHub webhooks and keep local repos up to date.
package main
/*
Listen for calls from GitHub and update local repositories.
*/
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"time"
)
var gopath string
func init() {
// Set gopath to GOPATH or $HOME (the default)
gp := os.Getenv("GOPATH")
if gp == "" {
u, err := user.Current()
if err != nil {
log.Printf("Failed to check home dir: %s\n", err)
} else {
gp = u.HomeDir
}
}
gopath = filepath.Join(gp, "src")
}
// event represents a JSONfile from GitHub.
// It is a small subset of the actual data GitHub provides.
type event struct {
Repository repo `json:"repository"`
Action string
CommitID string `json:"after"`
Branch string `json:"ref"`
path string
}
type repo struct {
URL string `json:"html_url"`
Name string `json:"name"`
FullName string `json:"full_name"`
}
func (e event) String() string {
return fmt.Sprintf("%s %s", e.Repository.URL, e.Action)
}
func main() {
http.HandleFunc("/", catch)
http.ListenAndServe(":7000", nil)
}
func catch(w http.ResponseWriter, r *http.Request) {
defer w.Write([]byte("OK"))
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Failed to read body: %s\n")
return
}
r.Body.Close()
filename := fmt.Sprintf("TEMP_%s_%s.json", r.RemoteAddr, time.Now().Format("2006-01-02_150405"))
err = ioutil.WriteFile(filename, body, 0644)
if err != nil {
log.Printf("Failed to write body: %s\n")
return
}
e, err := newEvent(body)
if err != nil {
log.Printf("Failed to parse event from JSON: %s\n", err)
return
}
go logEvent(e, filename)
if e.CommitID != "" {
go updateLocal(e)
}
}
func newEvent(body []byte) (*event, error) {
e := &event{}
err := json.Unmarshal(body, e)
if err != nil {
return e, err
}
head := "refs/heads/"
if e.CommitID != "" && strings.HasPrefix(e.Branch, head) {
e.Branch = e.Branch[len(head):]
}
prefix := "https://"
if strings.HasPrefix(e.Repository.URL, prefix) {
e.path = filepath.Join(gopath, e.Repository.URL[len(prefix):])
}
return e, err
}
func logEvent(e *event, filename string) {
log.Printf("%s: %s\n", filename, e)
fmt.Printf("Event %#v\n", e)
if e.CommitID != "" {
fmt.Printf("Commit %s\n", e.CommitID)
}
}
func updateLocal(e *event) {
/*
Go to dir
check if dir clean
check out branch
fetch & pull
*/
cmd := exec.Command("git", "diff")
cmd.Dir = e.path
out, err := cmd.CombinedOutput()
if err != nil {
log.Printf("Error running combined output: %s\n", err)
return
}
if len(out) > 0 {
log.Printf("%s directory not clean.\n", e.path)
return
}
cmd = exec.Command("git", "checkout", e.Branch)
cmd.Dir = e.path
if err = cmd.Run(); err != nil {
log.Printf("Failed to check out branch: %s\n", err)
return
}
log.Printf("Checked out %s %s\n", e.Repository, e.Branch)
cmd = exec.Command("git", "fetch")
cmd.Dir = e.path
if err = cmd.Run(); err != nil {
log.Printf("Failed to fetch: %s\n", err)
return
}
log.Printf("Fetched %s %s\n", e.Repository, e.Branch)
cmd = exec.Command("git", "pull", "origin", e.Branch)
cmd.Dir = e.path
if err = cmd.Run(); err != nil {
log.Printf("Failed to pull branch %s: %s\n", e.Branch, err)
return
}
log.Printf("Pulled %s %s\n", e.Repository, e.Branch)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment