fix(repo view): consider current host when viewing a repo details

This commit is contained in:
Jay McCure 2023-06-27 11:23:40 +10:00
parent 0d31c25301
commit 8d45b94ca5
No known key found for this signature in database
5 changed files with 303 additions and 40 deletions

View File

@ -189,6 +189,9 @@ the `GITLAB_HOST` environment variable, like this:
- `GITLAB_HOST=gitlab.example.com glab repo clone group/project`
- `GITLAB_HOST=gitlab.example.com glab issue list -R group/project`
When inside a git repository `glab` will use that repository's GitLab host by default. For example `glab issue list`
will list all issues of the current directory's git repository.
### Configure `glab` to use self-signed certificates for self-managed instances
The GitLab CLI can be configured to support self-managed instances using self-signed certificate authorities by making either of the following changes:

View File

@ -40,7 +40,7 @@ func NewCmdView(f *cmdutils.Factory) *cobra.Command {
`),
Args: cobra.MaximumNArgs(1),
Example: heredoc.Doc(`
# view project information for the current directory
# view project information for the current directory (must be a git repository)
$ glab repo view
# view project information of specified name
@ -67,27 +67,31 @@ func NewCmdView(f *cmdutils.Factory) *cobra.Command {
opts.ProjectID = args[0]
}
apiClient, err := f.HttpClient()
if err != nil {
return err
}
opts.APIClient = apiClient
// No project argument - use current repository
if opts.ProjectID == "" {
opts.Repo, err = f.BaseRepo()
if err != nil {
return cmdutils.WrapError(err, "`repository` is required when not running in a git repository")
}
opts.ProjectID = opts.Repo.FullName()
// Configure client to have host of current repository
client, err := api.NewClientWithCfg(opts.Repo.RepoHost(), cfg, false)
if err != nil {
return err
}
opts.APIClient = client.Lab()
if opts.Branch == "" {
opts.Branch, _ = f.Branch()
}
}
if opts.ProjectID != "" {
} else {
// If the ProjectID is a single token, use current user's namespace
if !strings.Contains(opts.ProjectID, "/") {
currentUser, err := api.CurrentUser(opts.APIClient)
apiClient, err := f.HttpClient()
if err != nil {
return err
}
currentUser, err := api.CurrentUser(apiClient)
if err != nil {
return cmdutils.WrapError(err, "Failed to retrieve your current user")
}
@ -95,27 +99,22 @@ func NewCmdView(f *cmdutils.Factory) *cobra.Command {
opts.ProjectID = currentUser.Username + "/" + opts.ProjectID
}
repo, err := glrepo.FromFullName(opts.ProjectID)
// Get the repo full name from the ProjectID which can be a full URL or a group/repo format
opts.Repo, err = glrepo.FromFullName(opts.ProjectID)
if err != nil {
return err
}
if !glrepo.IsSame(repo, opts.Repo) {
client, err := api.NewClientWithCfg(repo.RepoHost(), cfg, false)
if err != nil {
return err
}
opts.APIClient = client.Lab()
client, err := api.NewClientWithCfg(opts.Repo.RepoHost(), cfg, false)
if err != nil {
return err
}
opts.Repo = repo
opts.ProjectID = repo.FullName()
opts.APIClient = client.Lab()
}
browser, _ := cfg.Get(opts.Repo.RepoHost(), "browser")
opts.Browser = browser
opts.GlamourStyle, _ = cfg.Get(opts.Repo.RepoHost(), "glamour_style")
return runViewProject(&opts)
},
}

View File

@ -5,6 +5,8 @@ import (
"os/exec"
"testing"
"gitlab.com/gitlab-org/cli/internal/glrepo"
"github.com/MakeNowJust/heredoc"
"github.com/stretchr/testify/assert"
@ -15,7 +17,7 @@ import (
"gitlab.com/gitlab-org/cli/test"
)
func runCommand(rt http.RoundTripper, isTTY bool, cli string, stub bool) (*test.CmdOut, error, func()) {
func runCommand(rt http.RoundTripper, isTTY bool, cli string, stub bool, repoHost string) (*test.CmdOut, error, func()) {
ios, _, stdout, stderr := cmdtest.InitIOStreams(isTTY, "")
factory := cmdtest.InitFactory(ios, rt)
@ -24,6 +26,14 @@ func runCommand(rt http.RoundTripper, isTTY bool, cli string, stub bool) (*test.
return "#current-branch", nil
}
factory.BaseRepo = func() (glrepo.Interface, error) {
if repoHost == "" {
return glrepo.New("OWNER", "REPO"), nil
} else {
return glrepo.NewWithHost("OWNER", "REPO", repoHost), nil
}
}
_, _ = factory.HttpClient()
cmd := NewCmdView(factory)
@ -55,6 +65,7 @@ func TestProjectView(t *testing.T) {
httpMocks []httpMock
isTTY bool
stub bool
repoHost string
expectedOutput string
}{
@ -64,7 +75,7 @@ func TestProjectView(t *testing.T) {
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/OWNER/REPO?license=true&statistics=true&with_custom_attributes=true",
"https://gitlab.com/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
@ -82,7 +93,7 @@ func TestProjectView(t *testing.T) {
},
{
http.MethodGet,
"/api/v4/projects/OWNER/REPO/repository/files/README%2Emd?ref=%23current-branch",
"https://gitlab.com/api/v4/projects/OWNER%2FREPO/repository/files/README%2Emd?ref=%23current-branch",
http.StatusOK,
`{"file_name": "README.md",
"file_path": "README.md",
@ -106,13 +117,13 @@ func TestProjectView(t *testing.T) {
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/user",
"https://gitlab.com/api/v4/user",
http.StatusOK,
`{ "username": "test_user" }`,
},
{
http.MethodGet,
"/api/v4/projects/test_user/foo?license=true&statistics=true&with_custom_attributes=true",
"https://gitlab.com/api/v4/projects/test_user%2Ffoo?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
@ -130,7 +141,7 @@ func TestProjectView(t *testing.T) {
},
{
http.MethodGet,
"/api/v4/projects/test_user/foo/repository/files/README%2Emd?ref=main",
"https://gitlab.com/api/v4/projects/test_user%2Ffoo/repository/files/README%2Emd?ref=main",
http.StatusOK,
`{"file_name": "README.md",
"file_path": "README.md",
@ -154,7 +165,7 @@ func TestProjectView(t *testing.T) {
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/foo/bar?license=true&statistics=true&with_custom_attributes=true",
"https://gitlab.com/api/v4/projects/foo%2Fbar?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
@ -172,7 +183,7 @@ func TestProjectView(t *testing.T) {
},
{
http.MethodGet,
"/api/v4/projects/foo/bar/repository/files/README%2Emd?ref=main",
"https://gitlab.com/api/v4/projects/foo%2Fbar/repository/files/README%2Emd?ref=main",
http.StatusOK,
`{
"file_name": "README.md",
@ -197,7 +208,7 @@ func TestProjectView(t *testing.T) {
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/group/foo/bar?license=true&statistics=true&with_custom_attributes=true",
"https://gitlab.com/api/v4/projects/group%2Ffoo%2Fbar?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
@ -215,7 +226,7 @@ func TestProjectView(t *testing.T) {
},
{
http.MethodGet,
"/api/v4/projects/group/foo/bar/repository/files/README%2Emd?ref=main",
"https://gitlab.com/api/v4/projects/group%2Ffoo%2Fbar/repository/files/README%2Emd?ref=main",
http.StatusOK,
`{
"file_name": "README.md",
@ -235,12 +246,99 @@ func TestProjectView(t *testing.T) {
`),
},
{
name: "view project branch on web",
name: "view a project details from a project not hosted on the default host",
cli: "",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "bar",
"name_with_namespace": "OWNER / REPO",
"path": "bar",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "main",
"http_url_to_repo": "https://gitlab.company.org/OWNER/REPO.git",
"web_url": "https://gitlab.company.org/OWNER/REPO",
"readme_url": "https://gitlab.company.org/OWNER/REPO/-/blob/main/README.md"
}`,
},
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO/repository/files/README%2Emd?ref=%23current-branch",
http.StatusOK,
`{
"file_name": "README.md",
"file_path": "README.md",
"encoding": "base64",
"ref": "main",
"execute_filemode": false,
"content": "dGVzdCByZWFkbWUK"
}`,
},
},
repoHost: "gitlab.company.org",
expectedOutput: heredoc.Doc(`name: OWNER / REPO
description: this is a test description
---
test readme
`),
},
{
name: "view project details from a git URL",
cli: "https://gitlab.company.org/OWNER/REPO.git",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "bar",
"name_with_namespace": "OWNER / REPO",
"path": "bar",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "main",
"http_url_to_repo": "https://gitlab.company.org/OWNER/REPO.git",
"web_url": "https://gitlab.company.org/OWNER/REPO",
"readme_url": "https://gitlab.company.org/OWNER/REPO/-/blob/main/README.md"
}`,
},
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO/repository/files/README%2Emd?ref=main",
http.StatusOK,
`{
"file_name": "README.md",
"file_path": "README.md",
"encoding": "base64",
"ref": "main",
"execute_filemode": false,
"content": "dGVzdCByZWFkbWUK"
}`,
},
},
expectedOutput: heredoc.Doc(`name: OWNER / REPO
description: this is a test description
---
test readme
`),
},
{
name: "view project on web where current branch is different to default branch",
cli: "--web",
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/OWNER/REPO?license=true&statistics=true&with_custom_attributes=true",
"https://gitlab.com/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
@ -267,7 +365,7 @@ func TestProjectView(t *testing.T) {
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/OWNER/REPO?license=true&statistics=true&with_custom_attributes=true",
"https://gitlab.com/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
@ -288,12 +386,175 @@ func TestProjectView(t *testing.T) {
stub: true,
expectedOutput: "Opening gitlab.com/OWNER/REPO in your browser.\n",
},
{
name: "view project when passing a https git URL on web",
cli: "https://gitlab.company.org/OWNER/REPO.git --web",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "REPO",
"name_with_namespace": "Test User / REPO",
"path": "REPO",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "#current-branch",
"http_url_to_repo": "https://gitlab.company.org/OWNER/REPO.git",
"web_url": "https://gitlab.company.org/OWNER/REPO",
"readme_url": "https://gitlab.company.org/OWNER/REPO/-/blob/main/README.md"
}`,
},
},
isTTY: true,
stub: true,
expectedOutput: "Opening gitlab.company.org/OWNER/REPO in your browser.\n",
},
{
name: "view project when passing a https git URL on web",
cli: "https://gitlab.company.org/OWNER/REPO.git --web --branch foobranch",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "REPO",
"name_with_namespace": "Test User / REPO",
"path": "REPO",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "#current-branch",
"http_url_to_repo": "https://gitlab.company.org/OWNER/REPO.git",
"web_url": "https://gitlab.company.org/OWNER/REPO",
"readme_url": "https://gitlab.company.org/OWNER/REPO/-/blob/main/README.md"
}`,
},
},
isTTY: true,
stub: true,
expectedOutput: "Opening gitlab.company.org/OWNER/REPO/-/tree/foobranch in your browser.\n",
},
{
name: "view project when passing a https URL on web",
cli: "https://gitlab.company.org/OWNER/REPO --web",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "REPO",
"name_with_namespace": "Test User / REPO",
"path": "REPO",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "#current-branch",
"http_url_to_repo": "https://gitlab.company.org/OWNER/REPO.git",
"web_url": "https://gitlab.company.org/OWNER/REPO",
"readme_url": "https://gitlab.company.org/OWNER/REPO/-/blob/main/README.md"
}`,
},
},
isTTY: true,
stub: true,
expectedOutput: "Opening gitlab.company.org/OWNER/REPO in your browser.\n",
},
{
name: "view project when passing a git URL on web",
cli: "git@gitlab.company.org:OWNER/REPO.git --web",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "REPO",
"name_with_namespace": "Test User / REPO",
"path": "REPO",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "#current-branch",
"http_url_to_repo": "https://gitlab.company.org/OWNER/REPO.git",
"web_url": "https://gitlab.company.org/OWNER/REPO",
"readme_url": "https://gitlab.company.org/OWNER/REPO/-/blob/main/README.md"
}`,
},
},
isTTY: true,
stub: true,
expectedOutput: "Opening gitlab.company.org/OWNER/REPO in your browser.\n",
},
{
name: "view a project that isn't on the default host on web",
cli: "--web",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.company.org/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "REPO",
"name_with_namespace": "Test User / REPO",
"path": "REPO",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "#current-branch",
"http_url_to_repo": "https://gitlab.company.org/OWNER/REPO.git",
"web_url": "https://gitlab.company.org/OWNER/REPO",
"readme_url": "https://gitlab.company.org/OWNER/REPO/-/blob/main/README.md"
}`,
},
},
isTTY: true,
stub: true,
repoHost: "gitlab.company.org",
expectedOutput: "Opening gitlab.company.org/OWNER/REPO in your browser.\n",
},
{
name: "view a specific project branch on the web",
cli: "--branch foo --web",
httpMocks: []httpMock{
{
http.MethodGet,
"https://gitlab.com/api/v4/projects/OWNER%2FREPO?license=true&statistics=true&with_custom_attributes=true",
http.StatusOK,
`{
"id": 37777023,
"description": "this is a test description",
"name": "REPO",
"name_with_namespace": "Test User / REPO",
"path": "REPO",
"path_with_namespace": "OWNER/REPO",
"created_at": "2022-07-13T02:04:56.151Z",
"default_branch": "#current-branch",
"http_url_to_repo": "https://gitlab.com/OWNER/REPO.git",
"web_url": "https://gitlab.com/OWNER/REPO",
"readme_url": "https://gitlab.com/OWNER/REPO/-/blob/main/README.md"
}`,
},
},
isTTY: true,
stub: true,
expectedOutput: "Opening gitlab.com/OWNER/REPO/-/tree/foo in your browser.\n",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
fakeHTTP := &httpmock.Mocker{
MatchURL: httpmock.PathAndQuerystring,
MatchURL: httpmock.FullURL,
}
defer fakeHTTP.Verify(t)
@ -301,7 +562,7 @@ func TestProjectView(t *testing.T) {
fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, mock.body))
}
output, err, restoreCmd := runCommand(fakeHTTP, tc.isTTY, tc.cli, tc.stub)
output, err, restoreCmd := runCommand(fakeHTTP, tc.isTTY, tc.cli, tc.stub, tc.repoHost)
if restoreCmd != nil {
defer restoreCmd()
}

View File

@ -24,7 +24,7 @@ glab repo view [repository] [flags]
## Examples
```plaintext
# view project information for the current directory
# view project information for the current directory (must be a git repository)
$ glab repo view
# view project information of specified name

View File

@ -133,7 +133,7 @@ func FromFullName(nwo string) (Interface, error) {
return NewWithHost(parts[1], repo, normalizeHostname(parts[0])), nil
}
// Dots (.) are allowed in group names by GitLab.
// So we check if if the first part contains a dot.
// So we check if the first part contains a dot.
// However, it could be that the user is specifying a hostname but we can't be sure of that
// So we check in the list of authenticated hosts and see if it matches any
// if not, we assume it is a group name that contains a dot