diff --git a/cmd/git-get/main.go b/cmd/git-get/main.go index 853fafb..cbf446f 100644 --- a/cmd/git-get/main.go +++ b/cmd/git-get/main.go @@ -18,15 +18,30 @@ var cmd = &cobra.Command{ Use: "git-get ", Short: "git get", Run: Run, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Version: fmt.Sprintf("%s - %s, build at %s", version, commit, date), } +var list bool + func init() { pkg.LoadConf() + + cmd.PersistentFlags().BoolVarP(&list, "list", "l", false, "Lists all repositories inside git-get root") } func Run(cmd *cobra.Command, args []string) { + if list { + paths, err := pkg.FindRepos() + exitIfError(err) + + repos, err := pkg.OpenAll(paths) + exitIfError(err) + + pkg.PrintRepos(repos) + os.Exit(0) + } + url, err := pkg.ParseURL(args[0]) exitIfError(err) diff --git a/pkg/list.go b/pkg/list.go index beba3f5..d91a6be 100644 --- a/pkg/list.go +++ b/pkg/list.go @@ -119,8 +119,6 @@ func PrintRepos(repos []*Repo) { seg[i] = make([]string, len(subpaths)) - //t.AddBranch(fmt.Sprintf("\033[1;31m%s\033[0m", path)) - branch := t for j, sub := range subpaths { seg[i][j] = sub @@ -134,7 +132,7 @@ func PrintRepos(repos []*Repo) { // if this is the last segment, it means that's the name of the repository and we need to print its status if j == len(seg[i])-1 { - value = value + PrintRepoStatus(repo) + value = value + " " + renderWorktreeStatus(repo) } branch = branch.AddBranch(value) @@ -146,21 +144,88 @@ func PrintRepos(repos []*Repo) { const ( ColorRed = "\033[1;31m%s\033[0m" - ColorGreen = "\033[0;32m%s\033[0m" + ColorGreen = "\033[1;32m%s\033[0m" ColorBlue = "\033[1;34m%s\033[0m" ColorYellow = "\033[1;33m%s\033[0m" ) -func PrintRepoStatus(repo *Repo) string { - status := fmt.Sprintf(ColorGreen, StatusOk) +func renderWorktreeStatus(repo *Repo) string { + clean := true + var status []string + + // if current branch status can't be found it's probably a detached head + // TODO: what if current HEAD points to a tag? + if current := repo.findCurrentBranchStatus(); current == nil { + status = append(status, fmt.Sprintf(ColorYellow, repo.Status.CurrentBranch)) + } else { + status = append(status, renderBranchStatus(current)) + } + + // TODO: this is ugly + // unset clean flag to use it to render braces around worktree status and remove "ok" from branch status if it's there + if repo.Status.HasUncommittedChanges || repo.Status.HasUntrackedFiles { + clean = false + } + + if !clean { + status[len(status)-1] = strings.TrimSuffix(status[len(status)-1], StatusOk) + status = append(status, "[") + } if repo.Status.HasUntrackedFiles { - status = fmt.Sprintf(ColorRed, StatusUntracked) + status = append(status, fmt.Sprintf(ColorRed, StatusUntracked)) } if repo.Status.HasUncommittedChanges { - status = fmt.Sprintf(ColorRed, StatusUncommitted) + status = append(status, fmt.Sprintf(ColorRed, StatusUncommitted)) } - return " " + status + if !clean { + status = append(status, "]") + } + + return strings.Join(status, " ") +} + +func renderBranchStatus(branch *BranchStatus) string { + // ok indicates that the branch has upstream and is not ahead or behind it + ok := true + var status []string + + status = append(status, fmt.Sprintf(ColorBlue, branch.Name)) + + if branch.Upstream == "" { + ok = false + status = append(status, fmt.Sprintf(ColorYellow, StatusNoUpstream)) + } + + if branch.NeedsPull { + ok = false + status = append(status, fmt.Sprintf(ColorYellow, StatusBehind)) + } + + if branch.NeedsPush { + ok = false + status = append(status, fmt.Sprintf(ColorYellow, StatusAhead)) + } + + if ok { + status = append(status, fmt.Sprintf(ColorGreen, StatusOk)) + } + + return strings.Join(status, " ") +} + +func (r *Repo) findCurrentBranchStatus() *BranchStatus { + if r.Status.CurrentBranch == StatusDetached || r.Status.CurrentBranch == StatusUnknown { + return nil + } + + for _, b := range r.Status.Branches { + if b.Name == r.Status.CurrentBranch { + return b + } + } + + return nil } diff --git a/pkg/list_test.go b/pkg/list_test.go deleted file mode 100644 index 6158162..0000000 --- a/pkg/list_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package pkg - -import ( - "os" - "testing" -) - -func TestList(t *testing.T) { - _ = os.Setenv(EnvReposRoot, "/home/gru/workspace") - - paths, err := FindRepos() - checkFatal(t, err) - - repos, _ := OpenAll(paths) - - PrintRepos(repos) -} diff --git a/pkg/status.go b/pkg/status.go index 9e362b9..3373558 100644 --- a/pkg/status.go +++ b/pkg/status.go @@ -16,6 +16,9 @@ import ( const ( StatusUnknown = "unknown" StatusDetached = "detached HEAD" + StatusNoUpstream = "no upstream" + StatusAhead = "ahead" + StatusBehind = "behind" StatusOk = "ok" StatusUncommitted = "uncommitted" StatusUntracked = "untracked" @@ -24,7 +27,7 @@ const ( type RepoStatus struct { HasUntrackedFiles bool HasUncommittedChanges bool - Current string + CurrentBranch string Branches []*BranchStatus } @@ -63,7 +66,7 @@ func (r *Repo) LoadStatus() error { r.Status.HasUncommittedChanges = hasUncommitted(status) r.Status.HasUntrackedFiles = hasUntracked(status) - r.Status.Current = currentBranch(r) + r.Status.CurrentBranch = currentBranch(r) err = r.loadBranchesStatus() if err != nil { diff --git a/pkg/status_test.go b/pkg/status_test.go index aa3b4cd..8de1afd 100644 --- a/pkg/status_test.go +++ b/pkg/status_test.go @@ -13,25 +13,25 @@ func TestStatus(t *testing.T) { {newRepoEmpty, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: StatusUnknown, + CurrentBranch: StatusUnknown, Branches: nil, }}, {newRepoWithUntracked, &RepoStatus{ HasUntrackedFiles: true, HasUncommittedChanges: false, - Current: StatusUnknown, + CurrentBranch: StatusUnknown, Branches: nil, }}, {newRepoWithStaged, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: true, - Current: StatusUnknown, + CurrentBranch: StatusUnknown, Branches: nil, }}, {newRepoWithCommit, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: "master", + CurrentBranch: "master", Branches: []*BranchStatus{ { Name: "master", @@ -44,7 +44,7 @@ func TestStatus(t *testing.T) { {newRepoWithModified, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: true, - Current: "master", + CurrentBranch: "master", Branches: []*BranchStatus{ { Name: "master", @@ -57,7 +57,7 @@ func TestStatus(t *testing.T) { {newRepoWithIgnored, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: "master", + CurrentBranch: "master", Branches: []*BranchStatus{ { Name: "master", @@ -70,7 +70,7 @@ func TestStatus(t *testing.T) { {newRepoWithLocalBranch, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: "master", + CurrentBranch: "master", Branches: []*BranchStatus{ { Name: "local", @@ -88,7 +88,7 @@ func TestStatus(t *testing.T) { {newRepoWithClonedBranch, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: "local", + CurrentBranch: "local", Branches: []*BranchStatus{ { Name: "local", @@ -106,7 +106,7 @@ func TestStatus(t *testing.T) { {newRepoWithDetachedHead, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: StatusDetached, + CurrentBranch: StatusDetached, Branches: []*BranchStatus{ { Name: "master", @@ -119,7 +119,7 @@ func TestStatus(t *testing.T) { {newRepoWithBranchAhead, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: "master", + CurrentBranch: "master", Branches: []*BranchStatus{ { Name: "master", @@ -132,7 +132,7 @@ func TestStatus(t *testing.T) { {newRepoWithBranchBehind, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: "master", + CurrentBranch: "master", Branches: []*BranchStatus{ { Name: "master", @@ -145,7 +145,7 @@ func TestStatus(t *testing.T) { {newRepoWithBranchAheadAndBehind, &RepoStatus{ HasUntrackedFiles: false, HasUncommittedChanges: false, - Current: "master", + CurrentBranch: "master", Branches: []*BranchStatus{ { Name: "master", @@ -170,5 +170,6 @@ func TestStatus(t *testing.T) { } // TODO: test branch status when tracking a local branch +// TODO: test head pointing to a tag // TODO: newRepoWithGlobalGitignore // TODO: newRepoWithGlobalGitignoreSymlink