diff --git a/git/repo.go b/git/repo.go index 9d1f49d776e763fa2fb30be2c36fcc3f6259305f..e91df362e4558b87ab00ae8a89c5e5c59d2ffd2a 100644 --- a/git/repo.go +++ b/git/repo.go @@ -114,6 +114,19 @@ func (r *Repository) References() ([]*Reference, error) { return rrefs, nil } +// LsTree returns the tree for the given reference. +func (r *Repository) LsTree(ref string) (*Tree, error) { + tree, err := r.Repository.LsTree(ref) + if err != nil { + return nil, err + } + return &Tree{ + Tree: tree, + Path: "", + Repository: r, + }, nil +} + // Tree returns the tree for the given reference. func (r *Repository) Tree(ref *Reference) (*Tree, error) { if ref == nil { @@ -123,15 +136,7 @@ func (r *Repository) Tree(ref *Reference) (*Tree, error) { } ref = rref } - tree, err := r.LsTree(ref.Hash.String()) - if err != nil { - return nil, err - } - return &Tree{ - Tree: tree, - Path: "", - Repository: r, - }, nil + return r.LsTree(ref.Hash.String()) } // TreePath returns the tree for the given path. diff --git a/server/cmd/show.go b/server/cmd/blob.go similarity index 59% rename from server/cmd/show.go rename to server/cmd/blob.go index 14981dc27d95c1ecaa97ec189c3b615e57dbf5b8..58e2361f9342c32d1c482b8b2f5ad2031104c00f 100644 --- a/server/cmd/show.go +++ b/server/cmd/blob.go @@ -7,7 +7,7 @@ import ( "github.com/alecthomas/chroma/lexers" gansi "github.com/charmbracelet/glamour/ansi" "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/soft-serve/server/backend" + "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/ui/common" "github.com/muesli/termenv" "github.com/spf13/cobra" @@ -21,60 +21,99 @@ var ( filemodeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#777777")) ) -// showCommand returns a command that prints the contents of a file. -func showCommand() *cobra.Command { +// blobCommand returns a command that prints the contents of a file. +func blobCommand() *cobra.Command { var linenumber bool var color bool + var raw bool - showCmd := &cobra.Command{ - Use: "show PATH", - Aliases: []string{"cat"}, - Short: "Read the contents of file at path.", - Args: cobra.ExactArgs(1), + cmd := &cobra.Command{ + Use: "blob REPOSITORY [REFERENCE] [PATH]", + Aliases: []string{"cat", "show"}, + Short: "Print out the contents of file at path.", + Args: cobra.RangeArgs(1, 3), PersistentPreRunE: checkIfReadable, RunE: func(cmd *cobra.Command, args []string) error { cfg, _ := fromContext(cmd) - // FIXME: nested repos are not supported. - ps := strings.Split(args[0], "/") - rn := strings.TrimSuffix(ps[0], ".git") - fp := strings.Join(ps[1:], "/") - var repo backend.Repository - repoExists := false - repos, err := cfg.Backend.Repositories() + rn := args[0] + ref := "" + fp := "" + switch len(args) { + case 2: + fp = args[1] + case 3: + ref = args[1] + fp = args[2] + } + + repo, err := cfg.Backend.Repository(rn) if err != nil { return err } - for _, rp := range repos { - if rp.Name() == rn { - repoExists = true - repo = rp - break - } - } - if !repoExists { - return ErrRepoNotFound - } - c, _, err := backend.LatestFile(repo, fp) + + r, err := repo.Open() if err != nil { return err } - if color { - c, err = withFormatting(fp, c) + + if ref == "" { + head, err := r.HEAD() if err != nil { return err } + ref = head.Hash.String() + } + + tree, err := r.LsTree(ref) + if err != nil { + return err + } + + te, err := tree.TreeEntry(fp) + if err != nil { + return err + } + + if te.Type() != "blob" { + return git.ErrFileNotFound + } + + bts, err := te.Contents() + if err != nil { + return err } - if linenumber { - c = withLineNumber(c, color) + + c := string(bts) + isBin, _ := te.File().IsBinary() + if isBin { + if raw { + cmd.Println(c) + } else { + return fmt.Errorf("binary file: use --raw to print") + } + } else { + if color { + c, err = withFormatting(fp, c) + if err != nil { + return err + } + } + + if linenumber { + c = withLineNumber(c, color) + } + + cmd.Println(c) } - cmd.Println(c) return nil }, } - showCmd.Flags().BoolVarP(&linenumber, "linenumber", "l", false, "Print line numbers") - showCmd.Flags().BoolVarP(&color, "color", "c", false, "Colorize output") - return showCmd + cmd.Flags().BoolVarP(&raw, "raw", "r", false, "Print raw contents") + cmd.Flags().BoolVarP(&linenumber, "linenumber", "l", false, "Print line numbers") + cmd.Flags().BoolVarP(&color, "color", "c", false, "Colorize output") + + return cmd } func withLineNumber(s string, color bool) string { diff --git a/server/cmd/cmd.go b/server/cmd/cmd.go index e32836abb31be39b40829fddcb9f8082aaa8b60e..a23ee8afdb5c752b710b576b579aeec1fdbb5b18 100644 --- a/server/cmd/cmd.go +++ b/server/cmd/cmd.go @@ -55,8 +55,9 @@ func rootCommand() *cobra.Command { listCommand(), privateCommand(), renameCommand(), - showCommand(), + blobCommand(), tagCommand(), + treeCommand(), ) return rootCmd diff --git a/server/cmd/list.go b/server/cmd/list.go index 86f95d7770087a404814c5c23313a5ec03ac61bc..8ed558a15cdfc55d8c9cd19889b03a258c8988d4 100644 --- a/server/cmd/list.go +++ b/server/cmd/list.go @@ -1,11 +1,6 @@ package cmd import ( - "fmt" - "path/filepath" - "strings" - - "github.com/charmbracelet/soft-serve/git" "github.com/charmbracelet/soft-serve/server/backend" "github.com/spf13/cobra" ) @@ -13,80 +8,20 @@ import ( // listCommand returns a command that list file or directory at path. func listCommand() *cobra.Command { listCmd := &cobra.Command{ - Use: "list PATH", + Use: "list", Aliases: []string{"ls"}, - Short: "List files at repository.", - Args: cobra.RangeArgs(0, 1), + Short: "List repositories.", + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { cfg, s := fromContext(cmd) - rn := "" - path := "" - ps := []string{} - if len(args) > 0 { - // FIXME: nested repos are not supported. - path = filepath.Clean(args[0]) - ps = strings.Split(path, "/") - rn = strings.TrimSuffix(ps[0], ".git") - auth := cfg.Backend.AccessLevel(rn, s.PublicKey()) - if auth < backend.ReadOnlyAccess { - return ErrUnauthorized - } - } - if path == "" || path == "." || path == "/" { - repos, err := cfg.Backend.Repositories() - if err != nil { - return err - } - for _, r := range repos { - if cfg.Backend.AccessLevel(r.Name(), s.PublicKey()) >= backend.ReadOnlyAccess { - cmd.Println(r.Name()) - } - } - return nil - } - rr, err := cfg.Backend.Repository(rn) - if err != nil { - return err - } - r, err := rr.Open() - if err != nil { - return err - } - head, err := r.HEAD() - if err != nil { - if bs, err := r.Branches(); err != nil && len(bs) == 0 { - return fmt.Errorf("repository is empty") - } - return err - } - tree, err := r.TreePath(head, "") + repos, err := cfg.Backend.Repositories() if err != nil { return err } - subpath := strings.Join(ps[1:], "/") - ents := git.Entries{} - te, err := tree.TreeEntry(subpath) - if err == git.ErrRevisionNotExist { - return ErrFileNotFound - } - if err != nil { - return err - } - if te.Type() == "tree" { - tree, err = tree.SubTree(subpath) - if err != nil { - return err + for _, r := range repos { + if cfg.Backend.AccessLevel(r.Name(), s.PublicKey()) >= backend.ReadOnlyAccess { + cmd.Println(r.Name()) } - ents, err = tree.Entries() - if err != nil { - return err - } - } else { - ents = append(ents, te) - } - ents.Sort() - for _, ent := range ents { - cmd.Printf("%s\t%d\t %s\n", ent.Mode(), ent.Size(), ent.Name()) } return nil }, diff --git a/server/cmd/tree.go b/server/cmd/tree.go new file mode 100644 index 0000000000000000000000000000000000000000..43be304c6a1eb640ed7748d1408ad6b1fdb5b128 --- /dev/null +++ b/server/cmd/tree.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "fmt" + + "github.com/charmbracelet/soft-serve/git" + "github.com/dustin/go-humanize" + "github.com/spf13/cobra" +) + +// treeCommand returns a command that list file or directory at path. +func treeCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "tree REPOSITORY [REFERENCE] [PATH]", + Short: "Print repository tree at path.", + Args: cobra.RangeArgs(1, 3), + PersistentPreRunE: checkIfReadable, + RunE: func(cmd *cobra.Command, args []string) error { + cfg, _ := fromContext(cmd) + rn := args[0] + path := "" + ref := "" + switch len(args) { + case 2: + path = args[1] + case 3: + ref = args[1] + path = args[2] + } + rr, err := cfg.Backend.Repository(rn) + if err != nil { + return err + } + + r, err := rr.Open() + if err != nil { + return err + } + + if ref == "" { + head, err := r.HEAD() + if err != nil { + if bs, err := r.Branches(); err != nil && len(bs) == 0 { + return fmt.Errorf("repository is empty") + } + return err + } + + ref = head.Hash.String() + } + + tree, err := r.LsTree(ref) + if err != nil { + return err + } + + ents := git.Entries{} + if path != "" && path != "/" { + te, err := tree.TreeEntry(path) + if err == git.ErrRevisionNotExist { + return ErrFileNotFound + } + if err != nil { + return err + } + if te.Type() == "tree" { + tree, err = tree.SubTree(path) + if err != nil { + return err + } + ents, err = tree.Entries() + if err != nil { + return err + } + } else { + ents = append(ents, te) + } + } else { + ents, err = tree.Entries() + if err != nil { + return err + } + } + ents.Sort() + for _, ent := range ents { + size := ent.Size() + ssize := "" + if size == 0 { + ssize = "-" + } else { + ssize = humanize.Bytes(uint64(size)) + } + cmd.Printf("%s\t%s\t %s\n", ent.Mode(), ssize, ent.Name()) + } + return nil + }, + } + return cmd +}