cli/commands/project/view/project_view.go

254 lines
6.3 KiB
Go

package view
import (
"encoding/base64"
"fmt"
"strings"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/internal/glrepo"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"gitlab.com/gitlab-org/cli/pkg/utils"
)
type ViewOptions struct {
ProjectID string
APIClient *gitlab.Client
Web bool
Branch string
Browser string
GlamourStyle string
IO *iostreams.IOStreams
Repo glrepo.Interface
}
func NewCmdView(f *cmdutils.Factory) *cobra.Command {
opts := ViewOptions{
IO: f.IO,
}
var projectViewCmd = &cobra.Command{
Use: "view [repository] [flags]",
Short: "View a project/repository",
Long: heredoc.Doc(`Display the description and README of a project or open it in the browser.
`),
Args: cobra.MaximumNArgs(1),
Example: heredoc.Doc(`
# view project information for the current directory
$ glab repo view
# view project information of specified name
$ glab repo view my-project
$ glab repo view user/repo
$ glab repo view group/namespace/repo
# specify repo by full [git] URL
$ glab repo view git@gitlab.com:user/repo.git
$ glab repo view https://gitlab.company.org/user/repo
$ glab repo view https://gitlab.company.org/user/repo.git
`),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
cfg, err := f.Config()
if err != nil {
return err
}
opts.Branch = strings.TrimSpace(opts.Branch)
if len(args) == 1 {
opts.ProjectID = args[0]
}
apiClient, err := f.HttpClient()
if err != nil {
return err
}
opts.APIClient = apiClient
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()
if opts.Branch == "" {
opts.Branch, _ = f.Branch()
}
}
if opts.ProjectID != "" {
if !strings.Contains(opts.ProjectID, "/") {
currentUser, err := api.CurrentUser(opts.APIClient)
if err != nil {
return cmdutils.WrapError(err, "Failed to retrieve your current user")
}
opts.ProjectID = currentUser.Username + "/" + opts.ProjectID
}
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()
}
opts.Repo = repo
opts.ProjectID = repo.FullName()
}
browser, _ := cfg.Get(opts.Repo.RepoHost(), "browser")
opts.Browser = browser
opts.GlamourStyle, _ = cfg.Get(opts.Repo.RepoHost(), "glamour_style")
return runViewProject(&opts)
},
}
projectViewCmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open a project in the browser")
projectViewCmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "View a specific branch of the repository")
return projectViewCmd
}
func runViewProject(opts *ViewOptions) error {
project, err := opts.Repo.Project(opts.APIClient)
if err != nil {
return cmdutils.WrapError(err, "Failed to retrieve project information")
}
readmeFile, err := getReadmeFile(opts, project)
if err != nil {
return err
}
if opts.Web {
openURL := generateProjectURL(project, opts.Branch)
if opts.IO.IsaTTY {
fmt.Fprintf(opts.IO.StdOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
}
return utils.OpenInBrowser(openURL, opts.Browser)
} else {
if opts.IO.IsaTTY {
if err := opts.IO.StartPager(); err != nil {
return err
}
defer opts.IO.StopPager()
printProjectContentTTY(opts, project, readmeFile)
} else {
printProjectContentRaw(opts, project, readmeFile)
}
}
return nil
}
func getReadmeFile(opts *ViewOptions, project *gitlab.Project) (*gitlab.File, error) {
if project.ReadmeURL == "" {
return nil, nil
}
readmePath := strings.Replace(project.ReadmeURL, project.WebURL+"/-/blob/", "", 1)
readmePathComponents := strings.Split(readmePath, "/")
readmeRef := readmePathComponents[0]
readmeFileName := readmePathComponents[1]
if opts.Branch == "" {
opts.Branch = readmeRef
}
readmeFile, err := api.GetFile(opts.APIClient, project.PathWithNamespace, readmeFileName, opts.Branch)
if err != nil {
return nil, cmdutils.WrapError(err, fmt.Sprintf("Failed to retrieve README file on the %s branch", opts.Branch))
}
decoded, err := base64.StdEncoding.DecodeString(readmeFile.Content)
if err != nil {
return nil, cmdutils.WrapError(err, "Failed to decode README file")
}
readmeFile.Content = string(decoded)
return readmeFile, nil
}
func generateProjectURL(project *gitlab.Project, branch string) string {
if branch != "" && project.DefaultBranch != branch {
return project.WebURL + "/-/tree/" + branch
}
return project.WebURL
}
func printProjectContentTTY(opts *ViewOptions, project *gitlab.Project, readme *gitlab.File) {
var description string
var readmeContent string
var err error
fullName := project.NameWithNamespace
if project.Description != "" {
description, err = utils.RenderMarkdownWithoutIndentations(project.Description, opts.GlamourStyle)
if err != nil {
description = project.Description
}
} else {
description = "\n(No description provided)\n\n"
}
if readme != nil {
readmeContent, err = utils.RenderMarkdown(readme.Content, opts.GlamourStyle)
if err != nil {
readmeContent = readme.Content
}
}
c := opts.IO.Color()
// Header
fmt.Fprint(opts.IO.StdOut, c.Bold(fullName))
fmt.Fprint(opts.IO.StdOut, c.Gray(description))
// Readme
if readme != nil {
fmt.Fprint(opts.IO.StdOut, readmeContent)
} else {
fmt.Fprintln(opts.IO.StdOut, c.Gray("(This repository does not have a README file)"))
}
fmt.Fprintln(opts.IO.StdOut)
fmt.Fprintf(opts.IO.StdOut, c.Gray("View this project on GitLab: %s\n"), project.WebURL)
}
func printProjectContentRaw(opts *ViewOptions, project *gitlab.Project, readme *gitlab.File) {
fullName := project.NameWithNamespace
description := project.Description
fmt.Fprintf(opts.IO.StdOut, "name:\t%s\n", fullName)
fmt.Fprintf(opts.IO.StdOut, "description:\t%s\n", description)
if readme != nil {
fmt.Fprintln(opts.IO.StdOut, "---")
fmt.Fprintf(opts.IO.StdOut, readme.Content)
fmt.Fprintln(opts.IO.StdOut)
}
}