Detailed changes
@@ -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.
@@ -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 {
@@ -55,8 +55,9 @@ func rootCommand() *cobra.Command {
listCommand(),
privateCommand(),
renameCommand(),
- showCommand(),
+ blobCommand(),
tagCommand(),
+ treeCommand(),
)
return rootCmd
@@ -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
},
@@ -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
+}