Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Vosmaer (GitLab) <jacob@gitlab.com>2017-01-12 23:24:50 +0300
committerJacob Vosmaer (GitLab) <jacob@gitlab.com>2017-01-12 23:24:50 +0300
commit634bbbe031593e979786a1a4dcd42a449adc3bc6 (patch)
tree9850f51bf2a59c16835e9627bee11acf9082537e
parent7b80a75e0c266af6ced2bc43d827fface0078d27 (diff)
parent7b82af677652fc5b586eb80e2ad87eeed74932aa (diff)
Merge branch 'feature/upload-receive-pack' into 'master'
Implement git-{upload,receive}-pack handler Closes #43 See merge request !38
-rw-r--r--helper/helper.go40
-rw-r--r--router/home.go (renamed from handler/home.go)2
-rw-r--r--router/home_test.go (renamed from handler/home_test.go)4
-rw-r--r--router/info_refs.go103
-rw-r--r--router/info_refs_test.go126
-rw-r--r--router/router.go5
6 files changed, 274 insertions, 6 deletions
diff --git a/helper/helper.go b/helper/helper.go
new file mode 100644
index 000000000..9886afd69
--- /dev/null
+++ b/helper/helper.go
@@ -0,0 +1,40 @@
+package helper
+
+import (
+ "log"
+ "net/http"
+ "os/exec"
+ "syscall"
+)
+
+func Fail500(w http.ResponseWriter, r *http.Request, err error) {
+ http.Error(w, "Internal server error", 500)
+ printError(r, err)
+}
+
+func LogError(r *http.Request, err error) {
+ printError(r, err)
+}
+
+func printError(r *http.Request, err error) {
+ if r != nil {
+ log.Printf("error: %s %q: %v", r.Method, r.RequestURI, err)
+ } else {
+ log.Printf("error: %v", err)
+ }
+}
+
+func CleanUpProcessGroup(cmd *exec.Cmd) {
+ if cmd == nil {
+ return
+ }
+
+ process := cmd.Process
+ if process != nil && process.Pid > 0 {
+ // Send SIGTERM to the process group of cmd
+ syscall.Kill(-process.Pid, syscall.SIGTERM)
+ }
+
+ // reap our child process
+ cmd.Wait()
+}
diff --git a/handler/home.go b/router/home.go
index a57a55da5..54d09d3c0 100644
--- a/handler/home.go
+++ b/router/home.go
@@ -1,4 +1,4 @@
-package handler
+package router
import (
"net/http"
diff --git a/handler/home_test.go b/router/home_test.go
index ba1c0e689..d947d60dc 100644
--- a/handler/home_test.go
+++ b/router/home_test.go
@@ -1,4 +1,4 @@
-package handler
+package router
import (
"net/http"
@@ -14,7 +14,7 @@ func TestGetHome(t *testing.T) {
t.Fatal("Creating 'GET /' request failed!")
}
- http.HandlerFunc(Home).ServeHTTP(recorder, req)
+ NewRouter().ServeHTTP(recorder, req)
if recorder.Code != http.StatusOK {
t.Fatal("Server error: Returned ", recorder.Code, " instead of ", http.StatusOK)
diff --git a/router/info_refs.go b/router/info_refs.go
new file mode 100644
index 000000000..2f5db24d3
--- /dev/null
+++ b/router/info_refs.go
@@ -0,0 +1,103 @@
+package router
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "syscall"
+
+ "gitlab.com/gitlab-org/gitaly/helper"
+
+ "github.com/gorilla/mux"
+)
+
+const (
+ gitalyRepoPathHeader = "Gitaly-Repo-Path"
+ gitlabIdHeader = "Gitaly-GL-Id"
+)
+
+func GetInfoRefs(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ rpc := vars["service"]
+
+ glId := r.Header.Get(gitlabIdHeader)
+ if glId == "" {
+ helper.Fail500(w, r, fmt.Errorf("GetInfoRefs: %s header was not found", gitlabIdHeader))
+ return
+ }
+ repoPath := r.Header.Get(gitalyRepoPathHeader)
+ if repoPath == "" {
+ helper.Fail500(w, r, fmt.Errorf("GetInfoRefs: %s header was not found", gitalyRepoPathHeader))
+ return
+ }
+
+ // Prepare our Git subprocess
+ cmd := gitCommand(glId, "git", rpc, "--stateless-rpc", "--advertise-refs", repoPath)
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ helper.Fail500(w, r, fmt.Errorf("GetInfoRefs: stdout: %v", err))
+ return
+ }
+ defer stdout.Close()
+
+ if err := cmd.Start(); err != nil {
+ helper.Fail500(w, r, fmt.Errorf("GetInfoRefs: start %v: %v", cmd.Args, err))
+ return
+ }
+ defer helper.CleanUpProcessGroup(cmd) // Ensure brute force subprocess clean-up
+
+ // Start writing the response
+ w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", rpc))
+ w.Header().Set("Cache-Control", "no-cache")
+ w.WriteHeader(200) // Don't bother with HTTP 500 from this point on, just return
+
+ if err := pktLine(w, fmt.Sprintf("# service=git-%s\n", rpc)); err != nil {
+ helper.LogError(r, fmt.Errorf("GetInfoRefs: pktLine: %v", err))
+ return
+ }
+
+ if err := pktFlush(w); err != nil {
+ helper.LogError(r, fmt.Errorf("GetInfoRefs: pktFlush: %v", err))
+ return
+ }
+
+ if _, err := io.Copy(w, stdout); err != nil {
+ helper.LogError(r, fmt.Errorf("GetInfoRefs: copy output of %v: %v", cmd.Args, err))
+ return
+ }
+
+ if err := cmd.Wait(); err != nil {
+ helper.LogError(r, fmt.Errorf("GetInfoRefs: wait for %v: %v", cmd.Args, err))
+ return
+ }
+}
+
+func gitCommand(glId string, name string, args ...string) *exec.Cmd {
+ cmd := exec.Command(name, args...)
+ // Start the command in its own process group (nice for signalling)
+ cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
+ // Explicitly set the environment for the Git command
+ cmd.Env = []string{
+ fmt.Sprintf("HOME=%s", os.Getenv("HOME")),
+ fmt.Sprintf("PATH=%s", os.Getenv("PATH")),
+ fmt.Sprintf("LD_LIBRARY_PATH=%s", os.Getenv("LD_LIBRARY_PATH")),
+ fmt.Sprintf("GL_ID=%s", glId),
+ fmt.Sprintf("GL_PROTOCOL=http"),
+ }
+ // If we don't do something with cmd.Stderr, Git errors will be lost
+ cmd.Stderr = os.Stderr
+ return cmd
+}
+
+func pktLine(w io.Writer, s string) error {
+ _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
+ return err
+}
+
+func pktFlush(w io.Writer) error {
+ _, err := fmt.Fprint(w, "0000")
+ return err
+}
diff --git a/router/info_refs_test.go b/router/info_refs_test.go
new file mode 100644
index 000000000..fc280bbe3
--- /dev/null
+++ b/router/info_refs_test.go
@@ -0,0 +1,126 @@
+package router
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path"
+ "strings"
+ "testing"
+)
+
+const testRepoRoot = "testdata/data"
+const testRepo = "group/test.git"
+
+func TestMain(m *testing.M) {
+ source := "https://gitlab.com/gitlab-org/gitlab-test.git"
+ clonePath := path.Join(testRepoRoot, testRepo)
+ if _, err := os.Stat(clonePath); err != nil {
+ testCmd := exec.Command("git", "clone", "--bare", source, clonePath)
+ testCmd.Stdout = os.Stdout
+ testCmd.Stderr = os.Stderr
+
+ if err := testCmd.Run(); err != nil {
+ log.Printf("Test setup: failed to run %v", testCmd)
+ os.Exit(-1)
+ }
+ }
+
+ os.Exit(func() int {
+ return m.Run()
+ }())
+}
+
+func TestSuccessfulUploadPackRequest(t *testing.T) {
+ recorder := httptest.NewRecorder()
+
+ resource := "/projects/1/git-http/info-refs/upload-pack"
+ req, err := http.NewRequest("GET", resource, &bytes.Buffer{})
+ if err != nil {
+ t.Fatal("Failed creating a request to %s", resource)
+ }
+
+ req.Header.Add("Gitaly-Repo-Path", path.Join(testRepoRoot, testRepo))
+ req.Header.Add("Gitaly-GL-Id", "user-123")
+
+ NewRouter().ServeHTTP(recorder, req)
+
+ if recorder.Code != 200 {
+ t.Errorf("GET %q: expected 200, got %d", resource, recorder.Code)
+ }
+
+ response := recorder.Body.String()
+ assertGitRefAdvertisement(t, resource, response, "001e# service=git-upload-pack", "0000", []string{
+ "003ef4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0",
+ "00416f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 refs/tags/v1.0.0^{}",
+ })
+}
+
+func TestSuccessfulReceivePackRequest(t *testing.T) {
+ recorder := httptest.NewRecorder()
+
+ resource := "/projects/1/git-http/info-refs/receive-pack"
+ req, err := http.NewRequest("GET", resource, &bytes.Buffer{})
+ if err != nil {
+ t.Fatal("Failed creating a request to %s", resource)
+ }
+
+ req.Header.Add("Gitaly-Repo-Path", path.Join(testRepoRoot, testRepo))
+ req.Header.Add("Gitaly-GL-Id", "user-123")
+
+ NewRouter().ServeHTTP(recorder, req)
+
+ if recorder.Code != 200 {
+ t.Errorf("GET %q: expected 200, got %d", resource, recorder.Code)
+ }
+
+ response := recorder.Body.String()
+ assertGitRefAdvertisement(t, resource, response, "001f# service=git-receive-pack", "0000", []string{
+ "003ef4e6814c3e4e7a0de82a9e7cd20c626cc963a2f8 refs/tags/v1.0.0",
+ "003e8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b refs/tags/v1.1.0",
+ })
+}
+
+func TestFailedUploadPackRequestDueToMissingHeaders(t *testing.T) {
+ recorder := httptest.NewRecorder()
+
+ resource := "/projects/1/git-http/info-refs/upload-pack"
+ req, err := http.NewRequest("GET", resource, &bytes.Buffer{})
+ if err != nil {
+ t.Fatal("Failed creating a request to %s", resource)
+ }
+
+ for _, headerName := range []string{"Gitaly-Repo-Path", "Gitaly-GL-Id"} {
+ req.Header.Set(headerName, "Dummy Value")
+
+ NewRouter().ServeHTTP(recorder, req)
+
+ if recorder.Code != 500 {
+ t.Errorf("GET %q: expected 200, got %d", resource, recorder.Code)
+ }
+
+ req.Header.Del(headerName)
+ }
+}
+
+func assertGitRefAdvertisement(t *testing.T, requestPath, responseBody string, firstLine, lastLine string, middleLines []string) {
+ responseLines := strings.Split(responseBody, "\n")
+
+ if responseLines[0] != firstLine {
+ t.Errorf("GET %q: expected response first line to be %q, found %q", requestPath, firstLine, responseLines[0])
+ }
+
+ lastIndex := len(responseLines) - 1
+ if responseLines[lastIndex] != lastLine {
+ t.Errorf("GET %q: expected response last line to be %q, found %q", requestPath, lastLine, responseLines[lastIndex])
+ }
+
+ for _, ref := range middleLines {
+ if !strings.Contains(responseBody, ref) {
+ t.Errorf("GET %q: expected response to contain %q, found none", requestPath, ref)
+ }
+ }
+}
diff --git a/router/router.go b/router/router.go
index 0c929cb21..29e214f81 100644
--- a/router/router.go
+++ b/router/router.go
@@ -6,14 +6,13 @@ import (
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
-
- "gitlab.com/gitlab-org/gitaly/handler"
)
func NewRouter() http.Handler {
r := mux.NewRouter()
- r.HandleFunc("/", handler.Home)
+ r.HandleFunc("/", Home)
+ r.HandleFunc("/projects/{id:[0-9]+}/git-http/info-refs/{service:(upload|receive)-pack}", GetInfoRefs)
return handlers.LoggingHandler(os.Stdout, r)
}