Skip to content

Commit 99b714a

Browse files
committed
feat: add repo command to print repository name
Add a 'repo' subcommand that prints the current repository name in host/owner/repo form (e.g. github.com/zhaochunqi/git-open). Extract the shared repo/remote/web-URL resolution into resolveWebURL() to avoid duplication between the root command and the new subcommand.
1 parent bc5af00 commit 99b714a

5 files changed

Lines changed: 152 additions & 15 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ Navigate to your project's directory and run the following command:
7373

7474
This will open your repository in the default web browser.
7575

76+
To print the repository name (e.g. `github.com/zhaochunqi/git-open`):
77+
78+
`git-open repo`
79+
7680
## Testing
7781

7882
This project follows Go testing best practices. Here's how to run the tests:

cmd/git.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,27 @@ func getRemoteURL(repo *git.Repository) (string, error) {
5858
return getRemoteURLFunc(repo)
5959
}
6060

61+
// resolveWebURL returns the repository, its remote URL, and the converted web URL
62+
// for the Git repository in the current working directory.
63+
func resolveWebURL() (*git.Repository, string, string, error) {
64+
repo, err := getCurrentGitDirectory()
65+
if err != nil {
66+
return nil, "", "", fmt.Errorf("error getting git directory: %w", err)
67+
}
68+
69+
remoteURL, err := getRemoteURL(repo)
70+
if err != nil {
71+
return nil, "", "", fmt.Errorf("error getting remote URL: %w", err)
72+
}
73+
74+
webURL := convertToWebURL(remoteURL)
75+
if webURL == "" {
76+
return nil, "", "", fmt.Errorf("unsupported remote URL format: %s", remoteURL)
77+
}
78+
79+
return repo, remoteURL, webURL, nil
80+
}
81+
6182
var scpRemoteURLPattern = regexp.MustCompile(`^(?:[^@]+@)?([^:]+):(.+)$`)
6283

6384
func convertToWebURL(rawURL string) string {

cmd/repo.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
// repoCmd represents the repo command
11+
var repoCmd = &cobra.Command{
12+
Use: "repo",
13+
Short: "Print the repository name",
14+
Long: `Print the name of the Git repository in the current working directory,
15+
in the form of host/owner/repo (e.g. github.com/zhaochunqi/git-open).`,
16+
RunE: func(cmd *cobra.Command, args []string) error {
17+
// Get the repository name from the web URL of the remote
18+
_, _, webURL, err := resolveWebURL()
19+
if err != nil {
20+
return err
21+
}
22+
23+
fmt.Fprintln(cmd.OutOrStdout(), repoNameFromWebURL(webURL))
24+
return nil
25+
},
26+
}
27+
28+
// repoNameFromWebURL strips the scheme from a web URL,
29+
// e.g. "https://github.com/zhaochunqi/git-open" -> "github.com/zhaochunqi/git-open".
30+
func repoNameFromWebURL(webURL string) string {
31+
name := strings.TrimPrefix(webURL, "https://")
32+
name = strings.TrimPrefix(name, "http://")
33+
return strings.TrimSuffix(name, "/")
34+
}
35+
36+
func init() {
37+
rootCmd.AddCommand(repoCmd)
38+
}

cmd/repo_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
8+
"github.com/go-git/go-git/v5"
9+
"github.com/spf13/cobra"
10+
"github.com/zhaochunqi/git-open/internal/testhelper"
11+
)
12+
13+
func Test_repoNameFromWebURL(t *testing.T) {
14+
tests := []struct {
15+
name string
16+
webURL string
17+
want string
18+
}{
19+
{"https URL", "https://github.com/zhaochunqi/git-open", "github.com/zhaochunqi/git-open"},
20+
{"http URL", "http://gitlab.com/user/repo", "gitlab.com/user/repo"},
21+
{"trailing slash", "https://github.com/user/repo/", "github.com/user/repo"},
22+
{"no scheme", "github.com/user/repo", "github.com/user/repo"},
23+
}
24+
25+
for _, tt := range tests {
26+
t.Run(tt.name, func(t *testing.T) {
27+
if got := repoNameFromWebURL(tt.webURL); got != tt.want {
28+
t.Errorf("repoNameFromWebURL(%q) = %q, want %q", tt.webURL, got, tt.want)
29+
}
30+
})
31+
}
32+
}
33+
34+
func Test_repoCmd(t *testing.T) {
35+
tests := []struct {
36+
name string
37+
remoteURL string
38+
want string
39+
}{
40+
{"https URL", "https://github.com/zhaochunqi/git-open.git", "github.com/zhaochunqi/git-open\n"},
41+
{"ssh URL", "git@github.com:zhaochunqi/git-open.git", "github.com/zhaochunqi/git-open\n"},
42+
{"gitlab URL", "https://gitlab.com/user/repo.git", "gitlab.com/user/repo\n"},
43+
}
44+
45+
for _, tt := range tests {
46+
t.Run(tt.name, func(t *testing.T) {
47+
_, cleanup := testhelper.SetupTestRepo(t, tt.remoteURL, "main")
48+
defer cleanup()
49+
50+
buf := new(bytes.Buffer)
51+
cmd := &cobra.Command{}
52+
cmd.SetOut(buf)
53+
54+
if err := repoCmd.RunE(cmd, []string{}); err != nil {
55+
t.Fatalf("repoCmd.RunE() error = %v", err)
56+
}
57+
58+
if got := buf.String(); got != tt.want {
59+
t.Errorf("repoCmd output = %q, want %q", got, tt.want)
60+
}
61+
})
62+
}
63+
}
64+
65+
func Test_repoCmd_InvalidRemoteURLFormat(t *testing.T) {
66+
originalGetRemoteURLFunc := getRemoteURLFunc
67+
defer func() { getRemoteURLFunc = originalGetRemoteURLFunc }()
68+
69+
_, cleanup := testhelper.SetupTestRepo(t, "https://github.com/test/repo.git", "main")
70+
defer cleanup()
71+
72+
getRemoteURLFunc = func(repo *git.Repository) (string, error) {
73+
return "invalid-remote", nil
74+
}
75+
76+
cmd := &cobra.Command{}
77+
cmd.SetOut(new(bytes.Buffer))
78+
79+
err := repoCmd.RunE(cmd, []string{})
80+
if err == nil {
81+
t.Fatal("repoCmd.RunE() expected error for invalid remote URL format, got nil")
82+
}
83+
if !strings.Contains(err.Error(), "unsupported remote URL format") {
84+
t.Fatalf("repoCmd.RunE() error = %v, want message containing 'unsupported remote URL format'", err)
85+
}
86+
}

cmd/root.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,10 @@ and converts it to a web URL. The web URL is then printed to the console.`,
2525
fmt.Fprintf(cmd.OutOrStdout(), "Build Date: %s\n", BuildDate)
2626
return nil
2727
}
28-
// Get the Git repository in the current working directory
29-
repo, err := getCurrentGitDirectory()
28+
// Get the repository, its remote URL, and the converted web URL
29+
repo, remoteURL, webURL, err := resolveWebURL()
3030
if err != nil {
31-
return fmt.Errorf("error getting git directory: %w", err)
32-
}
33-
34-
// Get the remote URL of the Git repository
35-
remoteURL, err := getRemoteURL(repo)
36-
if err != nil {
37-
return fmt.Errorf("error getting remote URL: %w", err)
38-
}
39-
40-
// Convert the remote URL to a web URL
41-
webURL := convertToWebURL(remoteURL)
42-
if webURL == "" {
43-
return fmt.Errorf("unsupported remote URL format: %s", remoteURL)
31+
return err
4432
}
4533

4634
branchName, err := getBranchName(repo)

0 commit comments

Comments
 (0)