Skip to content

Commit

Permalink
feat(alias): support git
Browse files Browse the repository at this point in the history
  • Loading branch information
JanDeDobbeleer committed Aug 7, 2023
1 parent 77e7c96 commit 991c8df
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 11 deletions.
5 changes: 5 additions & 0 deletions src/shell/aliae.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,18 @@ type Type string
const (
Command Type = "command"
Function Type = "function"
Git Type = "git"
)

func (a *Alias) string() string {
if len(a.Type) == 0 {
a.Type = Command
}

if a.Type == Git {
return a.git()
}

switch context.Current.Shell {
case ZSH, BASH:
return a.zsh().render()
Expand Down
2 changes: 1 addition & 1 deletion src/shell/fish.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const (
)

func (a *Alias) fish() *Alias {
switch a.Type {
switch a.Type { //nolint:exhaustive
case Command:
a.template = `alias {{ .Name }} '{{ .Value }}'`
case Function:
Expand Down
82 changes: 82 additions & 0 deletions src/shell/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package shell

import (
"fmt"
"os/exec"
"strings"
)

var (
gitError bool
gitConfigCache map[string]string
)

func (a *Alias) git() string {
if a.If.Ignore() {
return ""
}

if gitError {
return ""
}

// make sure we have the aliases
if err := a.makeGitAliae(); err != nil {
gitError = true
return ""
}

// in case we already have this alias, we do not add it again
if match, OK := gitConfigCache[a.Name]; OK && match == string(a.Value) {
return ""
}

// safe to add the alias
format := `git config --global alias.%s "%s"`
return fmt.Sprintf(format, a.Name, a.Value)
}

func (a *Alias) makeGitAliae() error {
if gitConfigCache != nil {
return nil
}

config, err := a.getGitAliasOutput()
if err != nil {
return err
}

a.parsegitConfig(config)

return nil
}

func (a *Alias) getGitAliasOutput() (string, error) {
path, err := exec.LookPath("git")
if err != nil {
return "", err
}

cmd := exec.Command(path, "config", "--get-regexp", "^alias\\.")
// when no aliae have been set, it causes git to panic
raw, _ := cmd.Output()
return string(raw), nil
}

func (a *Alias) parsegitConfig(config string) {
gitConfigCache = make(map[string]string)

for _, line := range strings.Split(config, "\n") {
if len(line) == 0 {
continue
}

parts := strings.SplitN(line, " ", 2)

if len(parts) != 2 || !strings.HasPrefix(parts[0], "alias.") {
continue
}

gitConfigCache[parts[0][6:]] = parts[1]
}
}
75 changes: 75 additions & 0 deletions src/shell/git_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package shell

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestGit(t *testing.T) {
cases := []struct {
Case string
Alias *Alias
Expected string
Error bool
}{
{
Case: "Known value",
Alias: &Alias{Name: "hello", Value: "!echo world"},
},
{
Case: "Error",
Alias: &Alias{Name: "hello", Value: "!echo world"},
Error: true,
},
{
Case: "Unknown value",
Alias: &Alias{Name: "h", Value: "log --oneline --graph --decorate --all"},
Expected: `git config --global alias.h "log --oneline --graph --decorate --all"`,
},
}

for _, tc := range cases {
gitConfigCache = map[string]string{
"hello": "!echo world",
}
gitError = tc.Error
assert.Equal(t, tc.Expected, tc.Alias.git(), tc.Case)
}
}

func TestGitConfigParser(t *testing.T) {
config := `alias.h log --graph --pretty=format:'%C(white)%h%Creset - %C(blue)%d%Creset %s %Cgreen(%cr) %C(cyan)<%an>%Creset'
alias.dd difftool --dir-diff
alias.u reset HEAD
alias.uc reset --soft HEAD^
alias.l log -1 HEAD
alias.a commit -a --amend --no-edit
alias.d diff --color-moved -w
alias.cp cherry-pick
alias.p !git push --set-upstream ${1-origin} HEAD
alias.s status --branch --show-stash
alias.ap !git add .;git commit --amend --no-edit;git push ${1-origin} +${2-HEAD}
alias.pf !git fetch ${1-origin};git reset --hard ${1-origin}/$(git rev-parse --abbrev-ref HEAD)
alias.pa pull --all --recurse-submodules
alias.fp !git push ${1-origin} +HEAD
alias.kill reset --hard HEAD
alias.nuke !sh -c 'git branch -D $1 && git push origin :$1' -
alias.cleanup !f() { git branch --merged ${1:-main} | egrep -v "(^\*|${1:-master})" | xargs --no-run-if-empty git branch -d; };f
alias.ignored ls-files -o -i --exclude-standard
alias.parent-branch !git show-branch | sed 's/].*//' | grep '\*' | grep -v $(git rev-parse --abbrev-ref HEAD) | head -n1 | cut -d'[' -f2
alias.co checkout
alias.nb !git switch -c ${1-temp}; git fetch ${2-origin}; git rebase ${2-origin}/${3-main}
alias.clean-local !git branch -D $(git branch -av | cut -c 1- | awk '$3 =/\[gone\]/ { print $1 }')
alias.rf !GIT_SEQUENCE_EDITOR=: git rebase -i ${1-origin/master}
alias.sync !git fetch origin; git rebase origin/main
alias.hello !echo hello
`

alias := &Alias{}
alias.parsegitConfig(config)

assert.Equal(t, 25, len(gitConfigCache))
assert.Equal(t, "log --graph --pretty=format:'%C(white)%h%Creset - %C(blue)%d%Creset %s %Cgreen(%cr) %C(cyan)<%an>%Creset'", gitConfigCache["h"])
}
2 changes: 1 addition & 1 deletion src/shell/nu.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const (
)

func (a *Alias) nu() *Alias {
switch a.Type {
switch a.Type { //nolint:exhaustive
case Command:
a.template = `alias {{ .Name }} = {{ .Value }}`
case Function:
Expand Down
2 changes: 1 addition & 1 deletion src/shell/pwsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (a *Alias) pwsh() *Alias {
a.Type = Function
}

switch a.Type {
switch a.Type { //nolint:exhaustive
case Command:
a.template = `Set-Alias -Name {{ .Name }} -Value {{ .Value }}{{ if .Description }} -Description '{{ .Description }}'{{ end }}{{ if .Force }} -Force{{ end }}{{ if isPwshOption .Option }} -Option {{ .Option }}{{ end }}{{ if isPwshScope .Scope }} -Scope {{ .Scope }}{{ end }}` //nolint: lll
case Function:
Expand Down
2 changes: 1 addition & 1 deletion src/shell/xonsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const (
)

func (a *Alias) xonsh() *Alias {
switch a.Type {
switch a.Type { //nolint:exhaustive
case Command:
a.template = `aliases['{{ .Name }}'] = '{{ .Value }}'`
case Function:
Expand Down
2 changes: 1 addition & 1 deletion src/shell/zsh.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const (
)

func (a *Alias) zsh() *Alias {
switch a.Type {
switch a.Type { //nolint:exhaustive
case Command:
a.template = `alias {{ .Name }}="{{ .Value }}"`
case Function:
Expand Down
15 changes: 9 additions & 6 deletions website/docs/setup/alias.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ alias:
- name: hello-world
value: echo "hello world"
type: function
- name: sync
value: "!git fetch origin; git rebase origin/main"
type: git
```
### Alias
| Name | Type | Description |
| ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | the alias name |
| `value` | `string` | the command(s) you want to execute, supports [templating][templates] |
| `type` | `string` | <ul><li>`command`: a regular alias, value is a one-liner (**default**) </li><li>`function`: a code block to be placed inside a function</li></ul> |
| `if` | `string` | golang [template][go-text-template] conditional statement, see [if][if] |
| Name | Type | Description |
| ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | the alias name |
| `value` | `string` | the command(s) you want to execute, supports [templating][templates] |
| `type` | `string` | <ul><li>`command`: a regular alias, value is a one-liner (**default**) </li><li>`function`: a code block to be placed inside a function</li><li>`git`: a git alias definition</li></ul> |
| `if` | `string` | golang [template][go-text-template] conditional statement, see [if][if] |

### Shell specific configuration

Expand Down

0 comments on commit 991c8df

Please sign in to comment.