Skip to content

Commit

Permalink
Add additional Provider support
Browse files Browse the repository at this point in the history
By default, only 3 providers are available. This PR adds the ability for
a user to set additional Git providers with the global Git config.

The Git config structure includes a base URL as an argument, and commit
prefix and path prefix keys and values.

  [open "https://git.mydomain.dev"]
    commitprefix = commit
    pathprefix = tree

It can be set by editing the above into a .gitconfig or with the Git
CLI.

  git config --global open.https://git.mydomain.dev.commitprefix commit
  git config --global open.https://git.mydomain.dev.pathprefix tree

The resulting additional providers will be merged with the default
providers and work with `git open` calls.
  • Loading branch information
arbourd committed Apr 3, 2024
1 parent 59d953a commit 831c604
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 13 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,35 @@ Open a different repository than `cwd`.
$ git -C ~/src/my-repo open
```

### Providers

By default, three providers [github.com](https://github.com), [gitlab.com](https://gitlab.com) and [bitbucket.org](https://bitbucket.org) are supported.

To add custom Git providers and their URLs, set their values within the global `git config`.

```ini
[open "https://git.mydomain.dev"]
commitprefix = commit
pathprefix = tree
```

This can also be set using the `git` CLI.

```console
$ git config --global open.https://git.mydomain.dev.commitprefix commit
$ git config --global open.https://git.mydomain.dev.pathprefix tree
```

`commitprefix` and `pathprefix` are used to template the URI for your provider.

```go
fmt.Println(host + "/" + repository + "/" + commitprefix )
// https://git.mydomain.dev/<repository>/commit

fmt.Println(host + "/" + repository + "/" + pathprefix )
// https://git.mydomain.dev/<repository>/tree
```

## Installation

Install with `brew`.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go 1.22

require (
github.com/arbourd/git-get v0.6.1
github.com/google/go-cmp v0.6.0
github.com/ldez/go-git-cmd-wrapper/v2 v2.6.0
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
github.com/arbourd/git-get v0.6.1 h1:IYMN6gbtJcADrUpwflVR/5ERwovQ1F4nENSh7CaJnQ4=
github.com/arbourd/git-get v0.6.1/go.mod h1:haRy2V/Odc+rDhrJhy098yNLZt0qNYu8Q752SGCHz9M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/ldez/go-git-cmd-wrapper/v2 v2.6.0 h1:o5QIusOiH9phm1gY2UGO6JQjYSPFYbgFCcntOigBvMg=
github.com/ldez/go-git-cmd-wrapper/v2 v2.6.0/go.mod h1:whnaSah+AmezZS8vwp8FyFzEBHZCLKywWILUj5D8Jq0=
6 changes: 3 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ import (
func main() {
arg, err := processArgs(os.Args)
if err != nil {
fmt.Printf("Error: \"%s\"\n", err)
fmt.Printf("error: \"%s\"\n", err)
os.Exit(1)
}

url, err := open.GetURL(arg)
if err != nil {
fmt.Printf("Error: \"%s\"\n", err)
fmt.Printf("error: \"%s\"\n", err)
os.Exit(1)
}

fmt.Printf("Opening %s in your browser.\n", url)
err = open.InBrowser(url)
if err != nil {
fmt.Printf("Error: unable to open in browser: \"%s\"\n", err)
fmt.Printf("error: unable to open in browser: \"%s\"\n", err)
os.Exit(1)
}
}
Expand Down
10 changes: 5 additions & 5 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ func TestProcessArgs(t *testing.T) {
cases := map[string]struct {
args []string
expectedArg string
err string
wantErr bool
}{
"no argument": {
args: []string{"git-open"},
Expand All @@ -21,18 +21,18 @@ func TestProcessArgs(t *testing.T) {
"two arguments": {
args: []string{"git-open", "LICENSE", "README.md"},
expectedArg: "",
err: "recieved 2 args, accepts 1",
wantErr: true,
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
arg, err := processArgs(c.args)

if err != nil && c.err == "" {
if err != nil && !c.wantErr {
t.Fatalf("unexpected error:\n\t(GOT): %#v\n\t(WNT): nil", err)
} else if err == nil && len(c.err) > 0 {
t.Fatalf("expected error:\n\t(GOT): nil\n\t(WNT): %s", c.err)
} else if err == nil && c.wantErr {
t.Fatalf("expected error:\n\t(GOT): nil\n")
} else if arg != c.expectedArg {
t.Fatalf("unexpected arg:\n\t(GOT): %#v\n\t(WNT): %#v", arg, c.expectedArg)
}
Expand Down
4 changes: 3 additions & 1 deletion open/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,11 @@ func GetURL(arg string) (string, error) {
return "", err
}

providers := append(DefaultProviders, LoadProviders()...)

// Find the provider by comparing hosts
var p Provider
for _, provider := range DefaultProviders {
for _, provider := range providers {
if strings.Contains(provider.BaseURL, host) {
p = provider
break
Expand Down
8 changes: 4 additions & 4 deletions open/open_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestGetURL(t *testing.T) {
gitdir string
arg string
expectedURL string
err string
wantErr bool
}{
"no argument": {
arg: "",
Expand Down Expand Up @@ -73,10 +73,10 @@ func TestGetURL(t *testing.T) {
}

url, err := GetURL(c.arg)
if err != nil && c.err == "" {
if err != nil && !c.wantErr {
t.Fatalf("unexpected error:\n\t(GOT): %#v\n\t(WNT): nil", err)
} else if err == nil && len(c.err) > 0 {
t.Fatalf("expected error:\n\t(GOT): nil\n\t(WNT): %s", c.err)
} else if err == nil && c.wantErr {
t.Fatalf("expected error:\n\t(GOT): nil\n")
} else if url != expectedURL {
t.Fatalf("unexpected url:\n\t(GOT): %#v\n\t(WNT): %#v", url, expectedURL)
}
Expand Down
61 changes: 61 additions & 0 deletions open/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package open
import (
"net/url"
"strings"

"github.com/ldez/go-git-cmd-wrapper/v2/config"
"github.com/ldez/go-git-cmd-wrapper/v2/git"
)

// DefaultProviders are a list of supported Providers
Expand Down Expand Up @@ -50,3 +53,61 @@ func (p Provider) RootURL(repo string) string {
func escapePath(u string) string {
return strings.TrimSuffix(strings.ReplaceAll(url.PathEscape(u), "%2F", "/"), "/")
}

const getRegex = `^open\..*prefix$`

// LoadProviders returns a slice of [Provider] from the global Git config.
//
// The Git config structure includes a base URL as an argument, and commit prefix and path prefix keys and values.
//
// [open "https://git.mydomain.dev"]
// commitprefix = commit
// pathprefix = tree
func LoadProviders() []Provider {
p := []Provider{}
out, _ := git.Config(config.Global, config.GetRegexp(getRegex, ""))
out = strings.TrimSpace(out)
if len(out) == 0 {
return p
}

urls := make(map[string]*struct {
commitPrefix string
pathPrefix string
})
for _, v := range strings.Split(out, "\n") {
s := strings.Split(strings.TrimPrefix(v, "open."), " ")

var value string
if len(s) == 2 {
value = s[1]
}

s = strings.Split(s[0], ".")
key := s[len(s)-1]
url := strings.Join(s[0:len(s)-1], ".")

if urls[url] == nil {
urls[url] = &struct {
commitPrefix string
pathPrefix string
}{}
}

switch key {
case "commitprefix":
urls[url].commitPrefix = value
case "pathprefix":
urls[url].pathPrefix = value
}
}

for k, v := range urls {
p = append(p, Provider{
BaseURL: k,
CommitPrefix: v.commitPrefix,
PathPrefix: v.pathPrefix,
})
}
return p
}
75 changes: 75 additions & 0 deletions open/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package open

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/ldez/go-git-cmd-wrapper/v2/config"
"github.com/ldez/go-git-cmd-wrapper/v2/git"
)

const repo = "arbourd/git-open"
Expand Down Expand Up @@ -129,3 +136,71 @@ func TestEscapePath(t *testing.T) {
})
}
}

func TestLoadProviders(t *testing.T) {
gitconfig := filepath.Join(t.TempDir(), ".gitconfig")
_, err := os.Create(gitconfig)
if err != nil {
t.Fatalf("unable create .gitconfig: %s", err)
}

err = os.Setenv("GIT_CONFIG_GLOBAL", gitconfig)
if err != nil {
t.Fatalf("unable to set GIT_CONFIG_GLOBAL: %s", err)
}

cases := map[string]struct {
config []string
expectedProviders []Provider
}{
"empty git config": {
expectedProviders: []Provider{},
},
"single provider": {
config: []string{
"open.https://my.domain.dev.commitprefix -/commit",
"open.https://my.domain.dev.pathprefix -/tree",
},
expectedProviders: []Provider{
{BaseURL: "https://my.domain.dev", CommitPrefix: "-/commit", PathPrefix: "-/tree"},
},
},
"multple providers": {
config: []string{
"open.https://git.example1.dev.commitprefix -/commit",
"open.https://git.example1.dev.pathprefix -/tree",
"open.https://git.example2.dev.commitprefix commit",
"open.https://git.example2.dev.pathprefix tree",
},
expectedProviders: []Provider{
{BaseURL: "https://git.example1.dev", CommitPrefix: "-/commit", PathPrefix: "-/tree"},
{BaseURL: "https://git.example2.dev", CommitPrefix: "commit", PathPrefix: "tree"},
},
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
// removes all `open.https://` Git config entries
out, _ := git.Config(config.Global, config.GetRegexp(getRegex, ""))
for _, v := range strings.Split(strings.TrimSpace(out), "\n") {
key := strings.Split(strings.TrimSpace(v), " ")[0]

git.Config(config.Global, config.Unset(key, ""))
}

for _, v := range c.config {
s := strings.Split(v, " ")
git.Config(config.Global, config.Entry(s[0], s[1]))
}

p := LoadProviders()
if len(p) != len(c.expectedProviders) {
t.Logf("unexpected number of providers\n\t(GOT): %#v\n\t(WNT): %#v", len(p), len(c.expectedProviders))
}
if !cmp.Equal(p, c.expectedProviders) {
t.Fatalf("unexpected providers:\n\t(GOT): %#v\n\t(WNT): %#v", p, c.expectedProviders)
}
})
}
}

0 comments on commit 831c604

Please sign in to comment.