Skip to content

Commit d501227

Browse files
committed
Initial implementation
0 parents  commit d501227

File tree

7 files changed

+464
-0
lines changed

7 files changed

+464
-0
lines changed

cmd/git-goopen/main.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"runtime"
10+
"strings"
11+
12+
"github.com/go-git/go-git/v5"
13+
"github.com/kevinburke/ssh_config"
14+
"github.com/mogensen/go-git-open/internal/gitupstreams"
15+
gurl "github.com/whilp/git-urls"
16+
17+
"net/url"
18+
)
19+
20+
func main() {
21+
gitRepo, err := git.PlainOpen(".")
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
26+
url, err := getURLFromGitRepo(gitRepo)
27+
if err != nil {
28+
log.Fatal(err)
29+
}
30+
31+
openbrowser(url)
32+
}
33+
34+
func getURLFromGitRepo(gitRepo *git.Repository) (string, error) {
35+
list, err := gitRepo.Remotes()
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
40+
for _, r := range list {
41+
42+
// if domain is set in git options we override with this
43+
domain := getOverwriteDomain(gitRepo)
44+
branch := ""
45+
46+
h, err := gitRepo.Head()
47+
if err != nil {
48+
return "", err
49+
}
50+
if h.Name().IsBranch() {
51+
branch = h.Name().Short()
52+
}
53+
54+
url, err := getBrowerURL(r.Config().URLs[0], domain, branch)
55+
if err != nil {
56+
return "", err
57+
}
58+
59+
return url, nil
60+
}
61+
62+
return "", fmt.Errorf("No remote url found")
63+
}
64+
65+
func getBrowerURL(remoteURL string, domain, branch string) (string, error) {
66+
url, err := getURL(remoteURL)
67+
if err != nil {
68+
return "", err
69+
}
70+
71+
f, err := os.Open(filepath.Join(os.Getenv("HOME"), ".ssh", "config"))
72+
if err != nil {
73+
return "", err
74+
}
75+
cfg, err := ssh_config.Decode(f)
76+
if err != nil {
77+
return "", err
78+
}
79+
sshConfigDomain, _ := cfg.Get(url.Host, "HostName")
80+
81+
if sshConfigDomain != "" && domain == "" {
82+
domain = sshConfigDomain
83+
}
84+
85+
if strings.Contains(url.Host, "bitbucket.org") {
86+
url, err = gitupstreams.BitbucketOrgURL(url, branch)
87+
if err != nil {
88+
return "", err
89+
}
90+
} else if strings.Contains(url.Host, "azure.com") {
91+
url, err = gitupstreams.AzureURL(url, branch)
92+
if err != nil {
93+
return "", err
94+
}
95+
} else {
96+
url, err = gitupstreams.GenericURL(url, branch)
97+
if err != nil {
98+
return "", err
99+
}
100+
}
101+
102+
fmt.Println("----")
103+
fmt.Printf(" - branch: %s\n", branch)
104+
fmt.Printf(" - domain: %s\n", domain)
105+
106+
if domain != "" {
107+
url.Host = domain
108+
}
109+
fmt.Printf("%s\n", url)
110+
111+
fmt.Println(remoteURL)
112+
return url.String(), nil
113+
}
114+
115+
func getOverwriteDomain(gitRepo *git.Repository) string {
116+
117+
conf, err := gitRepo.Config()
118+
if err != nil {
119+
panic(err)
120+
}
121+
122+
sections := conf.Raw.Sections
123+
124+
for _, s := range sections {
125+
if s.Name == "open" {
126+
return s.Options.Get("domain")
127+
}
128+
}
129+
130+
return ""
131+
}
132+
133+
func getURL(remote string) (*url.URL, error) {
134+
135+
u, err := gurl.Parse(remote)
136+
if err != nil {
137+
return nil, err
138+
}
139+
140+
browserURL := url.URL{
141+
Scheme: "https",
142+
Host: u.Host,
143+
Path: strings.TrimSuffix(u.Path, ".git"),
144+
}
145+
146+
// If the URL is provided as "http", preserve that
147+
if u.Scheme == "http" {
148+
browserURL.Scheme = "http"
149+
}
150+
151+
return &browserURL, nil
152+
}
153+
154+
func openbrowser(url string) {
155+
var err error
156+
157+
switch runtime.GOOS {
158+
case "linux":
159+
err = exec.Command("xdg-open", url).Start()
160+
case "windows":
161+
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
162+
case "darwin":
163+
err = exec.Command("open", url).Start()
164+
default:
165+
err = fmt.Errorf("unsupported platform")
166+
}
167+
if err != nil {
168+
log.Fatal(err)
169+
}
170+
171+
}

cmd/git-goopen/main_test.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/go-git/go-billy/v5/memfs"
9+
"github.com/go-git/go-git/v5"
10+
"github.com/go-git/go-git/v5/plumbing"
11+
"github.com/go-git/go-git/v5/storage/memory"
12+
)
13+
14+
func Test_getBrowerURL(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
branch string
18+
remoteURL string
19+
domainOverwrite string
20+
want string
21+
wantErr bool
22+
}{
23+
{
24+
name: "gh: basic",
25+
branch: "master",
26+
remoteURL: "[email protected]:user/repo.git",
27+
want: "https://github.com/user/repo",
28+
wantErr: false,
29+
},
30+
{
31+
name: "gh: basic with branch",
32+
branch: "develop",
33+
remoteURL: "[email protected]:user/repo.git",
34+
want: "https://github.com/user/repo/tree/develop",
35+
wantErr: false,
36+
},
37+
{
38+
name: "gh: basic http",
39+
branch: "master",
40+
remoteURL: "http://github.com/user/repo.git",
41+
want: "http://github.com/user/repo",
42+
wantErr: false,
43+
},
44+
{
45+
name: "gh: basic with domain overwrite",
46+
branch: "master",
47+
remoteURL: "[email protected]:user/repo.git",
48+
domainOverwrite: "repo.git.com",
49+
want: "https://repo.git.com/user/repo",
50+
wantErr: false,
51+
},
52+
{
53+
name: "azure devops: basic",
54+
branch: "master",
55+
remoteURL: "https://[email protected]/v3/CORP/Project/GitRepo",
56+
want: "https://dev.azure.com/CORP/Project/_git/GitRepo",
57+
wantErr: false,
58+
},
59+
{
60+
name: "azure devops: ssh",
61+
branch: "master",
62+
remoteURL: "[email protected]:v3/CORP/Project/GitRepo",
63+
want: "https://dev.azure.com/CORP/Project/_git/GitRepo",
64+
wantErr: false,
65+
},
66+
}
67+
for _, tt := range tests {
68+
t.Run(tt.name, func(t *testing.T) {
69+
got, err := getBrowerURL(tt.remoteURL, tt.domainOverwrite, tt.branch)
70+
if (err != nil) != tt.wantErr {
71+
t.Errorf("getBrowerURL() error = %v, wantErr %v", err, tt.wantErr)
72+
return
73+
}
74+
if !reflect.DeepEqual(got, tt.want) {
75+
t.Errorf("getBrowerURL() = %v, want %v", got, tt.want)
76+
}
77+
})
78+
}
79+
}
80+
81+
func Test_getURLFromGitRepo(t *testing.T) {
82+
type args struct {
83+
gitRemote string
84+
gitBranch string
85+
}
86+
tests := []struct {
87+
name string
88+
args args
89+
want string
90+
wantErr bool
91+
}{
92+
{
93+
name: "gh: basic",
94+
args: args{
95+
gitRemote: "[email protected]:git-fixtures/basic.git",
96+
gitBranch: "master",
97+
},
98+
want: "https://github.com/git-fixtures/basic",
99+
wantErr: false,
100+
},
101+
{
102+
name: "gh: basic",
103+
args: args{
104+
gitRemote: "[email protected]:git-fixtures/basic.git",
105+
gitBranch: "branch",
106+
},
107+
want: "https://github.com/git-fixtures/basic/tree/branch",
108+
wantErr: false,
109+
},
110+
}
111+
for _, tt := range tests {
112+
t.Run(tt.name, func(t *testing.T) {
113+
114+
fs := memfs.New()
115+
// Git objects storer based on memory
116+
storer := memory.NewStorage()
117+
118+
// Clones the repository into the worktree (fs) and storer all the .git
119+
// content into the storer
120+
gitRepo, err := git.Clone(storer, fs, &git.CloneOptions{
121+
URL: tt.args.gitRemote,
122+
SingleBranch: true,
123+
ReferenceName: plumbing.NewBranchReferenceName(tt.args.gitBranch),
124+
})
125+
if err != nil {
126+
log.Fatal(err)
127+
}
128+
129+
got, err := getURLFromGitRepo(gitRepo)
130+
if (err != nil) != tt.wantErr {
131+
t.Errorf("getURLFromGitRepo() error = %v, wantErr %v", err, tt.wantErr)
132+
return
133+
}
134+
if got != tt.want {
135+
t.Errorf("getURLFromGitRepo() = %v, want %v", got, tt.want)
136+
}
137+
})
138+
}
139+
}

go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module github.com/mogensen/go-git-open
2+
3+
go 1.14
4+
5+
require (
6+
github.com/go-git/go-billy/v5 v5.0.0
7+
github.com/go-git/go-git/v5 v5.1.0
8+
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd
9+
github.com/whilp/git-urls v0.0.0-20191001220047-6db9661140c0
10+
)

0 commit comments

Comments
 (0)