From 991c8dfb106fd52fbbd1ab8023e074dca025d249 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Mon, 7 Aug 2023 12:09:01 +0200 Subject: [PATCH] feat(alias): support git --- src/shell/aliae.go | 5 +++ src/shell/fish.go | 2 +- src/shell/git.go | 82 ++++++++++++++++++++++++++++++++++++ src/shell/git_test.go | 75 +++++++++++++++++++++++++++++++++ src/shell/nu.go | 2 +- src/shell/pwsh.go | 2 +- src/shell/xonsh.go | 2 +- src/shell/zsh.go | 2 +- website/docs/setup/alias.mdx | 15 ++++--- 9 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 src/shell/git.go create mode 100644 src/shell/git_test.go diff --git a/src/shell/aliae.go b/src/shell/aliae.go index 3ea2c22..2e233a0 100644 --- a/src/shell/aliae.go +++ b/src/shell/aliae.go @@ -34,6 +34,7 @@ type Type string const ( Command Type = "command" Function Type = "function" + Git Type = "git" ) func (a *Alias) string() string { @@ -41,6 +42,10 @@ func (a *Alias) string() string { a.Type = Command } + if a.Type == Git { + return a.git() + } + switch context.Current.Shell { case ZSH, BASH: return a.zsh().render() diff --git a/src/shell/fish.go b/src/shell/fish.go index dfffbea..fb26ffc 100644 --- a/src/shell/fish.go +++ b/src/shell/fish.go @@ -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: diff --git a/src/shell/git.go b/src/shell/git.go new file mode 100644 index 0000000..3226257 --- /dev/null +++ b/src/shell/git.go @@ -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] + } +} diff --git a/src/shell/git_test.go b/src/shell/git_test.go new file mode 100644 index 0000000..aa40c67 --- /dev/null +++ b/src/shell/git_test.go @@ -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"]) +} diff --git a/src/shell/nu.go b/src/shell/nu.go index 73b14d3..98305d9 100644 --- a/src/shell/nu.go +++ b/src/shell/nu.go @@ -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: diff --git a/src/shell/pwsh.go b/src/shell/pwsh.go index fd146c9..9650ac4 100644 --- a/src/shell/pwsh.go +++ b/src/shell/pwsh.go @@ -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: diff --git a/src/shell/xonsh.go b/src/shell/xonsh.go index 091f326..8df8bbf 100644 --- a/src/shell/xonsh.go +++ b/src/shell/xonsh.go @@ -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: diff --git a/src/shell/zsh.go b/src/shell/zsh.go index 51cba51..f9d2a84 100644 --- a/src/shell/zsh.go +++ b/src/shell/zsh.go @@ -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: diff --git a/website/docs/setup/alias.mdx b/website/docs/setup/alias.mdx index 75b399a..7d7b59c 100644 --- a/website/docs/setup/alias.mdx +++ b/website/docs/setup/alias.mdx @@ -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` | | -| `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` | | +| `if` | `string` | golang [template][go-text-template] conditional statement, see [if][if] | ### Shell specific configuration