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:
@@ -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
|
||||||
|
|||||||
20
cmd/main.go
20
cmd/main.go
@@ -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
1
go.mod
@@ -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
2
go.sum
@@ -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
33
print/flat.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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", " ") {
|
||||||
|
|||||||
Reference in New Issue
Block a user