6
0
mirror of https://github.com/grdl/git-get.git synced 2026-02-15 18:40:09 +00:00

Add output flag and simple tree option

This commit is contained in:
Grzegorz Dlugoszewski
2020-06-08 14:00:59 +02:00
parent f3d0df1bfd
commit e5c3285040
8 changed files with 123 additions and 54 deletions

View File

@@ -18,6 +18,14 @@ const (
DefDefaultHost = "github.com" DefDefaultHost = "github.com"
KeyPrivateKey = "privateKey" KeyPrivateKey = "privateKey"
DefPrivateKey = "id_rsa" DefPrivateKey = "id_rsa"
KeyOutput = "out"
DefOutput = OutFlat
)
const (
OutFlat = "flat"
OutSmart = "smart"
OutSimple = "simple"
) )
// gitconfig provides methods for looking up configiration values inside .gitconfig file // gitconfig provides methods for looking up configiration values inside .gitconfig file

View File

@@ -24,7 +24,7 @@ var cmd = &cobra.Command{
Use: "git-get <repo>", Use: "git-get <repo>",
Short: "git get", Short: "git get",
Run: Run, Run: Run,
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1), // TODO: add custom validator
Version: fmt.Sprintf("%s - %s, build at %s", version, commit, date), Version: fmt.Sprintf("%s - %s, build at %s", version, commit, date),
} }
@@ -34,8 +34,10 @@ func init() {
cmd.PersistentFlags().BoolVarP(&list, "list", "l", false, "Lists all repositories inside git-get root") cmd.PersistentFlags().BoolVarP(&list, "list", "l", false, "Lists all repositories inside git-get root")
cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", "", "repos root") cmd.PersistentFlags().StringP(cfg.KeyReposRoot, "r", "", "repos root")
cmd.PersistentFlags().StringP(cfg.KeyPrivateKey, "p", "", "SSH private key path") cmd.PersistentFlags().StringP(cfg.KeyPrivateKey, "p", "", "SSH private key path")
cmd.PersistentFlags().StringP(cfg.KeyOutput, "o", cfg.DefOutput, "output format.")
viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot)) viper.BindPFlag(cfg.KeyReposRoot, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot))
viper.BindPFlag(cfg.KeyPrivateKey, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot)) viper.BindPFlag(cfg.KeyPrivateKey, cmd.PersistentFlags().Lookup(cfg.KeyReposRoot))
viper.BindPFlag(cfg.KeyOutput, cmd.PersistentFlags().Lookup(cfg.KeyOutput))
} }
func Run(cmd *cobra.Command, args []string) { func Run(cmd *cobra.Command, args []string) {
@@ -43,16 +45,26 @@ func Run(cmd *cobra.Command, args []string) {
root := viper.GetString(cfg.KeyReposRoot) root := viper.GetString(cfg.KeyReposRoot)
if list { if list {
// TODO: move it to OpenAll and don't export
paths, err := path.FindRepos() paths, err := path.FindRepos()
exitIfError(err) exitIfError(err)
repos, err := path.OpenAll(paths) repos, err := path.OpenAll(paths)
exitIfError(err) exitIfError(err)
//tree := BuildTree(root, repos) var printer print.Printer
//fmt.Println(RenderSmartTree(tree)) switch viper.GetString(cfg.KeyOutput) {
case cfg.OutFlat:
printer = &print.FlatPrinter{}
case cfg.OutSimple:
printer = &print.SimpleTreePrinter{}
case cfg.OutSmart:
printer = &print.SmartTreePrinter{}
default:
err = fmt.Errorf("invalid --output flag; allowed values: %v", []string{cfg.OutFlat, cfg.OutSimple, cfg.OutSmart})
}
exitIfError(err)
printer := print.NewFlatPrinter()
fmt.Println(printer.Print(root, repos)) fmt.Println(printer.Print(root, repos))
os.Exit(0) os.Exit(0)

1
go.mod
View File

@@ -10,5 +10,6 @@ require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0 github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.7.0 github.com/spf13/viper v1.7.0
github.com/xlab/treeprint v1.0.0
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
) )

2
go.sum
View File

@@ -230,6 +230,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v1.0.0 h1:J0TkWtiuYgtdlrkkrDLISYBQ92M+X5m4LrIIMKrbDTs=
github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=

33
print/flat.go Normal file
View File

@@ -0,0 +1,33 @@
package print
import (
"fmt"
"git-get/git"
"path/filepath"
"strings"
)
type FlatPrinter struct{}
func (p *FlatPrinter) Print(root string, repos []*git.Repo) string {
val := root
for _, repo := range repos {
path := strings.TrimPrefix(repo.Path, root)
path = strings.Trim(path, string(filepath.Separator))
val += fmt.Sprintf("\n%s %s", path, printWorktreeStatus(repo))
for _, branch := range repo.Status.Branches {
// Don't print the status of the current branch. It was already printed above.
if branch.Name == repo.Status.CurrentBranch {
continue
}
indent := strings.Repeat(" ", len(path))
val += fmt.Sprintf("\n%s %s", indent, printBranchStatus(branch))
}
}
return val
}

View File

@@ -3,7 +3,6 @@ package print
import ( import (
"fmt" "fmt"
"git-get/git" "git-get/git"
"path/filepath"
"strings" "strings"
) )
@@ -11,35 +10,6 @@ type Printer interface {
Print(root string, repos []*git.Repo) string Print(root string, repos []*git.Repo) string
} }
type FlatPrinter struct{}
func NewFlatPrinter() *FlatPrinter {
return &FlatPrinter{}
}
func (p *FlatPrinter) Print(root string, repos []*git.Repo) string {
val := root
for _, repo := range repos {
path := strings.TrimPrefix(repo.Path, root)
path = strings.Trim(path, string(filepath.Separator))
val += fmt.Sprintf("\n%s %s", path, renderWorktreeStatus(repo))
for _, branch := range repo.Status.Branches {
// Don't print the status of the current branch. It was already printed above.
if branch.Name == repo.Status.CurrentBranch {
continue
}
indent := strings.Repeat(" ", len(path))
val += fmt.Sprintf("\n%s %s", indent, renderBranchStatus(branch))
}
}
return val
}
const ( const (
ColorRed = "\033[1;31m%s\033[0m" ColorRed = "\033[1;31m%s\033[0m"
ColorGreen = "\033[1;32m%s\033[0m" ColorGreen = "\033[1;32m%s\033[0m"
@@ -47,7 +17,7 @@ const (
ColorYellow = "\033[1;33m%s\033[0m" ColorYellow = "\033[1;33m%s\033[0m"
) )
func renderWorktreeStatus(repo *git.Repo) string { func printWorktreeStatus(repo *git.Repo) string {
clean := true clean := true
var status []string var status []string
@@ -56,7 +26,7 @@ func renderWorktreeStatus(repo *git.Repo) string {
if current := repo.CurrentBranchStatus(); current == nil { if current := repo.CurrentBranchStatus(); current == nil {
status = append(status, fmt.Sprintf(ColorYellow, repo.Status.CurrentBranch)) status = append(status, fmt.Sprintf(ColorYellow, repo.Status.CurrentBranch))
} else { } else {
status = append(status, renderBranchStatus(current)) status = append(status, printBranchStatus(current))
} }
// TODO: this is ugly // TODO: this is ugly
@@ -85,7 +55,7 @@ func renderWorktreeStatus(repo *git.Repo) string {
return strings.Join(status, " ") return strings.Join(status, " ")
} }
func renderBranchStatus(branch *git.BranchStatus) string { func printBranchStatus(branch *git.BranchStatus) string {
// ok indicates that the branch has upstream and is not ahead or behind it // ok indicates that the branch has upstream and is not ahead or behind it
ok := true ok := true
var status []string var status []string

View File

@@ -4,12 +4,39 @@ import (
"git-get/git" "git-get/git"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/xlab/treeprint"
) )
// Node represents a node in a repos tree type SimpleTreePrinter struct{}
type SmartTreePrinter struct {
// length is the size (number of chars) of the currently processed line.
// It's used to correctly indent the lines with branches status.
length int
}
func (p *SmartTreePrinter) Print(root string, repos []*git.Repo) string {
tree := BuildTree(root, repos)
return p.PrintSmartTree(tree)
}
func (p *SimpleTreePrinter) Print(root string, repos []*git.Repo) string {
tree := BuildTree(root, repos)
tp := treeprint.New()
tp.SetValue(root)
p.PrintSimpleTree(tree, tp)
return tp.String()
}
// Node represents a node (ie. path fragment) in a repos tree.
type Node struct { type Node struct {
val string val string
depth int // depth is a nesting depth used when rendering a tree, not an depth level of a node inside the tree depth int // depth is a nesting depth used when rendering a smart tree, not a depth level of a tree node.
parent *Node parent *Node
children []*Node children []*Node
repo *git.Repo repo *git.Repo
@@ -87,7 +114,7 @@ func BuildTree(root string, repos []*git.Repo) *Node {
return tree return tree
} }
// RenderSmartTree returns a string representation of repos tree. // PrintSmartTree returns a string representation of repos tree.
// It's "smart" because it automatically folds branches which only have a single child and indents branches with many children. // It's "smart" because it automatically folds branches which only have a single child and indents branches with many children.
// //
// It recursively traverses the tree and prints its nodes. // It recursively traverses the tree and prints its nodes.
@@ -109,7 +136,7 @@ func BuildTree(root string, repos []*git.Repo) *Node {
// repo2 // repo2
// another/repo // another/repo
// //
func RenderSmartTree(node *Node) string { func (p *SmartTreePrinter) PrintSmartTree(node *Node) string {
if node.children == nil { if node.children == nil {
// If node is a leaf, print repo name and its status and finish processing this node. // If node is a leaf, print repo name and its status and finish processing this node.
value := node.val value := node.val
@@ -120,17 +147,17 @@ func RenderSmartTree(node *Node) string {
return value return value
} }
value += " " + renderWorktreeStatus(node.repo) value += " " + printWorktreeStatus(node.repo)
// Print the status of each branch on a new line, indented to match the position of the current branch name. // Print the status of each branch on a new line, indented to match the position of the current branch name.
indent := "\n" + strings.Repeat(" ", length+len(node.val)) indent := "\n" + strings.Repeat(" ", p.length+len(node.val))
for _, branch := range node.repo.Status.Branches { for _, branch := range node.repo.Status.Branches {
// Don't print the status of the current branch. It was already printed above. // Don't print the status of the current branch. It was already printed above.
if branch.Name == node.repo.Status.CurrentBranch { if branch.Name == node.repo.Status.CurrentBranch {
continue continue
} }
value += indent + renderBranchStatus(branch) value += indent + printBranchStatus(branch)
} }
return value return value
@@ -148,24 +175,40 @@ func RenderSmartTree(node *Node) string {
// node's path has multiple levels above. // node's path has multiple levels above.
node.depth = node.parent.depth node.depth = node.parent.depth
length += len(val) p.length += len(val)
} else { } else {
// If node has multiple children, print each of them on a new line // If node has multiple children, print each of them on a new line
// and indent them once relative to the parent // and indent them once relative to the parent
node.depth = node.parent.depth + 1 node.depth = node.parent.depth + 1
shift = "\n" + strings.Repeat(" ", node.depth) shift = "\n" + strings.Repeat(" ", node.depth)
length = 0 p.length = 0
} }
for _, child := range node.children { for _, child := range node.children {
length += len(shift) p.length += len(shift)
val += shift + RenderSmartTree(child) val += shift + p.PrintSmartTree(child)
length = 0 p.length = 0
} }
return val return val
} }
// lenght is the size (number of chars) of the currently processed line. func (p *SimpleTreePrinter) PrintSimpleTree(node *Node, tp treeprint.Tree) {
// It's used to correctly indent the lines with branches status. if node.children == nil {
var length int tp.SetValue(node.val + " " + printWorktreeStatus(node.repo))
for _, branch := range node.repo.Status.Branches {
// Don't print the status of the current branch. It was already printed above.
if branch.Name == node.repo.Status.CurrentBranch {
continue
}
tp.AddNode(printBranchStatus(branch))
}
}
for _, child := range node.children {
branch := tp.AddBranch(child.val)
p.PrintSimpleTree(child, branch)
}
}

View File

@@ -96,9 +96,9 @@ gitlab.com/
repos = append(repos, git.NewRepo(nil, path)) //&Repo{path: path}) repos = append(repos, git.NewRepo(nil, path)) //&Repo{path: path})
} }
tree := BuildTree("root", repos) printer := SmartTreePrinter{}
// Leading and trailing newlines are added to test cases for readability. We also need to add them to the rendering result. // Leading and trailing newlines are added to test cases for readability. We also need to add them to the rendering result.
got := fmt.Sprintf("\n%s\n", RenderSmartTree(tree)) got := fmt.Sprintf("\n%s\n", printer.Print("root", repos))
// Rendered tree uses spaces for indentation but the test cases use tabs. // Rendered tree uses spaces for indentation but the test cases use tabs.
if got != strings.ReplaceAll(test.want, "\t", " ") { if got != strings.ReplaceAll(test.want, "\t", " ") {