-
-
Notifications
You must be signed in to change notification settings - Fork 25
feat: add support for git branch #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| Feature: branch command | ||
|
|
||
| Background: | ||
| Given a mocked home directory | ||
|
|
||
| @outside-repo | ||
| Scenario: Appropriate error status when not in a git repo | ||
| When I run `scmpuff branch` | ||
| Then the exit status should be 128 | ||
| And the output should contain: | ||
| """ | ||
| Not a git repository (or any of the parent directories) | ||
| """ | ||
|
|
||
| Scenario: Numbered output of local branches | ||
| Given I am in a git repository | ||
| And a file named "base" with: | ||
| """ | ||
| foo | ||
| """ | ||
| And I successfully run `git add base` | ||
| And I successfully run `git commit -m "init"` | ||
| And I successfully run the following commands: | ||
| | git branch branch_a | | ||
| | git branch branch_b | | ||
| When I successfully run `scmpuff branch` | ||
| Then the stdout from "scmpuff branch" should contain "* [1] master" | ||
| And the stdout from "scmpuff branch" should contain " [2] branch_a" | ||
| And the stdout from "scmpuff branch" should contain " [3] branch_b" | ||
|
|
||
| Scenario: Detached HEAD output | ||
| Given I am in a git repository | ||
| And a file named "foo" with: | ||
| """ | ||
| bar | ||
| """ | ||
| And I successfully run `git add foo` | ||
| And I successfully run `git commit -m "first"` | ||
| And I successfully run `git branch feature` | ||
| And I run `git checkout HEAD~0` | ||
| When I successfully run `scmpuff branch` | ||
| Then the stdout from "scmpuff branch" should match /\* \(HEAD detached/ | ||
| And the stdout from "scmpuff branch" should contain "[1] master" | ||
| And the stdout from "scmpuff branch" should contain "[2] feature" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| package branch | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "bytes" | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
| "os/exec" | ||
| "strings" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| const ( | ||
| colorBranch = "\033[32m" | ||
| colorReset = "\033[0m" | ||
| ) | ||
|
|
||
| // CommandBranch lists git branches with numbered shortcuts. | ||
| // The first line of output, when --branchlist is provided, will contain | ||
| // a TAB separated list of branch names suitable for environment expansion. | ||
| func CommandBranch() *cobra.Command { | ||
| var optsBranchlist bool | ||
|
|
||
| var branchCmd = &cobra.Command{ | ||
| Use: "branch", | ||
| Short: "Display numbered git branches", | ||
| Run: func(cmd *cobra.Command, args []string) { | ||
| branches := gitBranchOutput() | ||
| numbered, list := process(branches) | ||
|
|
||
| if optsBranchlist { | ||
| fmt.Println(strings.Join(list, "\t")) | ||
| } | ||
| fmt.Print(numbered) | ||
| }, | ||
| } | ||
|
|
||
| branchCmd.Flags().BoolVarP( | ||
| &optsBranchlist, | ||
| "branchlist", "f", false, | ||
| "include machine-parseable branch list", | ||
| ) | ||
|
|
||
| return branchCmd | ||
| } | ||
|
|
||
| func gitBranchOutput() []byte { | ||
| out, err := exec.Command("git", "branch", "--color=never").Output() | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like there unfortunately isn't a
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another edge case to consider might be whether there is a way to ensure the pager is disabled on a per command basis here, so git doesn't try to run its output through a pager if there more lines of branches than it thinks will fit on the user screen. From the git branch documentation it appears that is the default, we might want to verify if it is automatically disabled somehow based on detecting whether STDOUT is a pipe or tty.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. --format w/ pager forcibly disabled makes sense to me here! |
||
| if err != nil { | ||
| if err.Error() == "exit status 128" { | ||
| msg := "Not a git repository (or any of the parent directories)" | ||
| fmt.Fprintf(os.Stderr, "\033[0;31m%s\033[0m\n", msg) | ||
| os.Exit(128) | ||
| } | ||
| log.Fatal(err) | ||
| } | ||
| return out | ||
| } | ||
|
|
||
| // process takes raw `git branch` output and returns numbered output | ||
| // along with a slice of branch names in order. | ||
| func process(out []byte) (string, []string) { | ||
| scanner := bufio.NewScanner(bytes.NewReader(out)) | ||
| var starLine string | ||
| var starBranch string | ||
| var names []string | ||
|
|
||
| for scanner.Scan() { | ||
| line := scanner.Text() | ||
| if len(line) < 2 { | ||
| continue | ||
| } | ||
| prefix := line[:2] | ||
| name := strings.TrimSpace(line[2:]) | ||
| if prefix == "* " { | ||
| starLine = line | ||
| if !strings.HasPrefix(name, "(") { | ||
| starBranch = name | ||
| } | ||
| continue | ||
| } | ||
| names = append(names, name) | ||
| } | ||
|
|
||
| var b strings.Builder | ||
| var result []string | ||
| n := 1 | ||
| if starLine != "" { | ||
| if starBranch != "" { | ||
| b.WriteString(fmt.Sprintf("* [%d] %s%s%s\n", n, colorBranch, starBranch, colorReset)) | ||
| result = append(result, starBranch) | ||
| n++ | ||
| } else { | ||
| b.WriteString(colorBranch + starLine + colorReset + "\n") | ||
| } | ||
| } | ||
| for _, name := range names { | ||
| b.WriteString(fmt.Sprintf(" [%d] %s\n", n, name)) | ||
| result = append(result, name) | ||
| n++ | ||
| } | ||
| return b.String(), result | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| alias gs='scmpuff_status' | ||
| alias gb='scmpuff_branch' | ||
| alias ga='git add' | ||
| alias gd='git diff' | ||
| alias gl='git log' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,28 @@ function scmpuff_status | |
| end | ||
| end | ||
|
|
||
| function scmpuff_branch | ||
| scmpuff_clear_vars | ||
| set -lx scmpuff_env_char "e" | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clobbering the existing file variable could be problematic: it might not be obvious to a user that if they list branches, the previous file shortcuts that were displayed to them would stop. working. I suspect we need a second variable here (which unfortunately does add to implementation complexity, but I don't know a great way around that).
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was previously a very active user of scm_breeze (I definitely prefer the simplicity of scmpuff!) and fwiw it shared the same env var space between all numbered commands. It definitely wasn’t ideal but was pretty intuitive I think |
||
| set -l cmd_output (/usr/bin/env scmpuff branch --branchlist $argv) | ||
| set -l es "$status" | ||
|
|
||
| if test $es -ne 0 | ||
| return $es | ||
| end | ||
|
|
||
| set -l files (string split \t $cmd_output[1]) | ||
| if test (count $files) -gt 0 | ||
| for e in (seq (count $files)) | ||
| set -gx "$scmpuff_env_char""$e" "$files[$e]" | ||
| end | ||
| end | ||
|
|
||
| for line in $cmd_output[2..-1] | ||
| echo $line | ||
| end | ||
| end | ||
|
|
||
| function scmpuff_clear_vars | ||
| set -l scmpuff_env_char "e" | ||
| set -l scmpuff_env_vars (set -x | awk '{print $1}' | grep -E '^'$scmpuff_env_char'[0-9]+') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,7 +25,7 @@ scmpuff_status() { | |
| local e=1 | ||
| for file in $files; do | ||
| export $scmpuff_env_char$e="$file" | ||
| let e++ | ||
| (( e++ )) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this syntax fully POSIX compatible?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was automatically flagged by the shell linter you have in the repo, can’t see the exact message any more but I believe it mentioned that this has been supported in Bash for decades |
||
| done | ||
| IFS=$' \t\n' | ||
|
|
||
|
|
@@ -37,6 +37,36 @@ scmpuff_status() { | |
| } | ||
|
|
||
|
|
||
| # List git branches with numbered shortcuts | ||
| scmpuff_branch() { | ||
| local scmpuff_env_char="e" | ||
|
|
||
| if [ -n "$ZSH_VERSION" ]; then setopt shwordsplit; fi; | ||
|
|
||
| local cmd_output | ||
| cmd_output="$(/usr/bin/env scmpuff branch --branchlist "$@")" | ||
|
|
||
| local es=$? | ||
| if [ $es -ne 0 ]; then | ||
| return $es | ||
| fi | ||
|
|
||
| files="$(echo "$cmd_output" | head -n 1)" | ||
| scmpuff_clear_vars | ||
| IFS=$'\t' | ||
| local e=1 | ||
| for file in $files; do | ||
| export $scmpuff_env_char$e="$file" | ||
| (( e++ )) | ||
| done | ||
| IFS=$' \t\n' | ||
|
|
||
| echo "$cmd_output" | tail -n +2 | ||
|
|
||
| if [ -n "$ZSH_VERSION" ]; then unsetopt shwordsplit; fi; | ||
| } | ||
|
|
||
|
|
||
| # Clear numbered env variables | ||
| scmpuff_clear_vars() { | ||
| local scmpuff_env_char="e" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really appreciate these integration tests!