review: handle hosting Gerrit in a sub-path
The git-review module is useful for other gerrit sites. Some
host gerrit under a sub-path. Add tests for both googlesource.com
repos and non-googlesource.com repos.
Change-Id: If2848128d6957db61ac50cbab7e6927da431002b
Reviewed-on: https://go-review.googlesource.com/20553
Reviewed-by: Andrew Gerrand <adg@golang.org>
Run-TryBot: Andrew Gerrand <adg@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/git-codereview/api.go b/git-codereview/api.go
index 6fbdd48..786ac60 100644
--- a/git-codereview/api.go
+++ b/git-codereview/api.go
@@ -11,6 +11,7 @@
"io"
"io/ioutil"
"net/http"
+ "net/url"
"os"
"sort"
"strings"
@@ -43,38 +44,80 @@
// Gerrit must be set, either explicitly via the code review config or
// implicitly as Git's origin remote.
origin := config()["gerrit"]
- if origin == "" {
- origin = trim(cmdOutput("git", "config", "remote.origin.url"))
- }
+ originUrl := trim(cmdOutput("git", "config", "remote.origin.url"))
+ err := loadGerritOriginInternal(origin, originUrl)
+ if err != nil {
+ dief("failed to load Gerrit origin: %v", err)
+ }
+}
+
+// loadGerritOriginInternal does the work of loadGerritOrigin, just extracted out
+// for easier testing.
+func loadGerritOriginInternal(origin, remoteOrigin string) error {
+ originUrl, err := url.Parse(remoteOrigin)
+ if err != nil {
+ return fmt.Errorf("failed to parse git's remote.origin.url %q as a URL: %v", remoteOrigin, err)
+ } else {
+ originUrl.User = nil
+ remoteOrigin = originUrl.String()
+ }
+ hasGerritConfig := true
+ if origin == "" {
+ hasGerritConfig = false
+ origin = remoteOrigin
+ }
if strings.Contains(origin, "github.com") {
- dief("git origin must be a Gerrit host, not GitHub: %s", origin)
+ return fmt.Errorf("git origin must be a Gerrit host, not GitHub: %s", origin)
}
if !strings.HasPrefix(origin, "https://") {
- dief("git origin must be an https:// URL: %s", origin)
+ return fmt.Errorf("git origin must be an https:// URL: %s", origin)
}
- // https:// prefix and then one slash between host and top-level name
- if strings.Count(origin, "/") != 3 {
- dief("git origin is malformed: %s", origin)
- }
- host := origin[len("https://"):strings.LastIndex(origin, "/")]
- // In the case of Google's Gerrit, host is go.googlesource.com
- // and apiURL uses go-review.googlesource.com, but the Gerrit
- // setup instructions do not write down a cookie explicitly for
- // go-review.googlesource.com, so we look for the non-review
- // host name instead.
- url := origin
- if i := strings.Index(url, ".googlesource.com"); i >= 0 {
+ if googlesourceIndex := strings.Index(origin, ".googlesource.com"); googlesourceIndex >= 0 {
+ // https:// prefix and then one slash between host and top-level name
+ if strings.Count(origin, "/") != 3 {
+ return fmt.Errorf("git origin is malformed: %s", origin)
+ }
+ host := origin[len("https://"):strings.LastIndex(origin, "/")]
+
+ // In the case of Google's Gerrit, host is go.googlesource.com
+ // and apiURL uses go-review.googlesource.com, but the Gerrit
+ // setup instructions do not write down a cookie explicitly for
+ // go-review.googlesource.com, so we look for the non-review
+ // host name instead.
+ url := origin
+ i := googlesourceIndex
url = url[:i] + "-review" + url[i:]
- }
- i := strings.LastIndex(url, "/")
- url, project := url[:i], url[i+1:]
- auth.host = host
- auth.url = url
- auth.project = project
+ i = strings.LastIndex(url, "/")
+ url, project := url[:i], url[i+1:]
+
+ auth.host = host
+ auth.url = url
+ auth.project = project
+ return nil
+ }
+
+ // Origin is not *.googlesource.com.
+ //
+ // If the Gerrit origin is set from the codereview.cfg file than we handle it
+ // differently to allow for sub-path hosted Gerrit.
+ auth.host = originUrl.Host
+ if hasGerritConfig {
+ if !strings.HasPrefix(remoteOrigin, origin) {
+ return fmt.Errorf("Gerrit origin %q from %q different then git origin url %q", origin, configRef, originUrl)
+ }
+
+ auth.project = strings.Trim(strings.TrimPrefix(remoteOrigin, origin), "/")
+ auth.url = origin
+ } else {
+ auth.project = strings.Trim(originUrl.Path, "/")
+ auth.url = strings.TrimSuffix(remoteOrigin, originUrl.Path)
+ }
+
+ return nil
}
// loadAuth loads the authentication tokens for making API calls to
diff --git a/git-codereview/api_test.go b/git-codereview/api_test.go
index 410be5a..279a76f 100644
--- a/git-codereview/api_test.go
+++ b/git-codereview/api_test.go
@@ -114,3 +114,71 @@
}
}
}
+
+func TestLoadGerritOrigin(t *testing.T) {
+ list := []struct {
+ origin, originUrl string
+
+ fail bool
+ host, url, project string
+ }{
+ {
+ // *.googlesource.com
+ origin: "",
+ originUrl: "https://go.googlesource.com/crypto",
+ host: "go.googlesource.com",
+ url: "https://go-review.googlesource.com",
+ project: "crypto",
+ },
+ {
+ // Gerrit origin is set.
+ // Gerrit is hosted on a sub-domain.
+ origin: "https://gerrit.mysite.com",
+ originUrl: "https://gerrit.mysite.com/projectA",
+ host: "gerrit.mysite.com",
+ url: "https://gerrit.mysite.com",
+ project: "projectA",
+ },
+ {
+ // Gerrit origin is set.
+ // Gerrit is hosted under sub-path under "/gerrit".
+ origin: "https://mysite.com/gerrit",
+ originUrl: "https://mysite.com/gerrit/projectA",
+ host: "mysite.com",
+ url: "https://mysite.com/gerrit",
+ project: "projectA",
+ },
+ {
+ // Gerrit origin is set.
+ // Gerrit is hosted under sub-path under "/gerrit" and repo is under
+ // sub-folder.
+ origin: "https://mysite.com/gerrit",
+ originUrl: "https://mysite.com/gerrit/sub/projectA",
+ host: "mysite.com",
+ url: "https://mysite.com/gerrit",
+ project: "sub/projectA",
+ },
+ }
+
+ for _, item := range list {
+ auth.host = ""
+ auth.url = ""
+ auth.project = ""
+ err := loadGerritOriginInternal(item.origin, item.originUrl)
+ if err != nil && !item.fail {
+ t.Errorf("unexpected error from item %q %q: %v", item.origin, item.originUrl, err)
+ continue
+ }
+ if auth.host != item.host || auth.url != item.url || auth.project != item.project {
+ t.Errorf("want %q %q %q, got %q %q %q", item.host, item.url, item.project, auth.host, auth.url, auth.project)
+ continue
+ }
+ if item.fail {
+ continue
+ }
+ have := haveGerritInternal(item.origin, item.originUrl)
+ if !have {
+ t.Errorf("for %q %q expect haveGerrit() == true, but got false", item.origin, item.originUrl)
+ }
+ }
+}
diff --git a/git-codereview/config.go b/git-codereview/config.go
index 739c4f5..9843fcf 100644
--- a/git-codereview/config.go
+++ b/git-codereview/config.go
@@ -36,15 +36,25 @@
return cachedConfig
}
+// haveGerrit returns true if gerrit should be used.
+// To enable gerrit, codereview.cfg must be present with "gerrit" property set to
+// the gerrit https URL or the git origin must be to
+// "https://<project>.googlesource.com/<repo>".
func haveGerrit() bool {
gerrit := config()["gerrit"]
+ origin := trim(cmdOutput("git", "config", "remote.origin.url"))
+ return haveGerritInternal(gerrit, origin)
+}
+
+// haveGerritInternal is the same as haveGerrit but factored out
+// for testing.
+func haveGerritInternal(gerrit, origin string) bool {
if gerrit == "off" {
return false
}
if gerrit != "" {
return true
}
- origin := trim(cmdOutput("git", "config", "remote.origin.url"))
if strings.Contains(origin, "github.com") {
return false
}