mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-02-02 05:41:10 -08:00
feat: rebase support
This commit is contained in:
parent
e0adc584e5
commit
9f23514268
|
@ -28,7 +28,8 @@ Local changes can also shown by default using the following syntax for both the
|
||||||
"branch_ahead_icon": "↑",
|
"branch_ahead_icon": "↑",
|
||||||
"branch_behind_icon": "↓",
|
"branch_behind_icon": "↓",
|
||||||
"local_working_icon": "",
|
"local_working_icon": "",
|
||||||
"local_staged_icon": ""
|
"local_staged_icon": "",
|
||||||
|
"rebase_icon": " "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -42,3 +43,4 @@ Local changes can also shown by default using the following syntax for both the
|
||||||
- local_working_icon: `string` - the icon to display in front of the working area changes
|
- local_working_icon: `string` - the icon to display in front of the working area changes
|
||||||
- local_staged_icon: `string` - the icon to display in front of the staged area changes
|
- local_staged_icon: `string` - the icon to display in front of the staged area changes
|
||||||
- display_status: `boolean` - display the local changes or not
|
- display_status: `boolean` - display the local changes or not
|
||||||
|
- rebase_icon: `string` - icon/text to display before the context when in a rebase
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -18,6 +19,8 @@ type environmentInfo interface {
|
||||||
getcwd() string
|
getcwd() string
|
||||||
homeDir() string
|
homeDir() string
|
||||||
hasFiles(pattern string) bool
|
hasFiles(pattern string) bool
|
||||||
|
hasFolder(folder string) bool
|
||||||
|
getFileContent(file string) string
|
||||||
getPathSeperator() string
|
getPathSeperator() string
|
||||||
getCurrentUser() (*user.User, error)
|
getCurrentUser() (*user.User, error)
|
||||||
isRunningAsRoot() bool
|
isRunningAsRoot() bool
|
||||||
|
@ -69,6 +72,19 @@ func (env *environment) hasFiles(pattern string) bool {
|
||||||
return len(matches) > 0
|
return len(matches) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (env *environment) hasFolder(folder string) bool {
|
||||||
|
_, err := os.Stat(folder)
|
||||||
|
return !os.IsNotExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *environment) getFileContent(file string) string {
|
||||||
|
content, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(content)
|
||||||
|
}
|
||||||
|
|
||||||
func (env *environment) getPathSeperator() string {
|
func (env *environment) getPathSeperator() string {
|
||||||
return string(os.PathSeparator)
|
return string(os.PathSeparator)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,8 +62,6 @@ func (g *git) enabled() bool {
|
||||||
func (g *git) string() string {
|
func (g *git) string() string {
|
||||||
g.getGitStatus()
|
g.getGitStatus()
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
// branchsymbol
|
|
||||||
buffer.WriteString(g.props.getString(BranchIcon, "Branch: "))
|
|
||||||
// branchName
|
// branchName
|
||||||
fmt.Fprintf(buffer, "%s", g.repo.branch)
|
fmt.Fprintf(buffer, "%s", g.repo.branch)
|
||||||
displayStatus := g.props.getBool(DisplayStatus, true)
|
displayStatus := g.props.getBool(DisplayStatus, true)
|
||||||
|
@ -101,7 +99,7 @@ func (g *git) init(props *properties, env environmentInfo) {
|
||||||
|
|
||||||
func (g *git) getGitStatus() {
|
func (g *git) getGitStatus() {
|
||||||
g.repo = &gitRepo{}
|
g.repo = &gitRepo{}
|
||||||
output := g.getGitOutputForCommand("status", "--porcelain", "-b", "--ignore-submodules")
|
output := g.getGitCommandOutput("status", "--porcelain", "-b", "--ignore-submodules")
|
||||||
splittedOutput := strings.Split(output, "\n")
|
splittedOutput := strings.Split(output, "\n")
|
||||||
g.repo.working = g.parseGitStats(splittedOutput, true)
|
g.repo.working = g.parseGitStats(splittedOutput, true)
|
||||||
g.repo.staging = g.parseGitStats(splittedOutput, false)
|
g.repo.staging = g.parseGitStats(splittedOutput, false)
|
||||||
|
@ -109,35 +107,69 @@ func (g *git) getGitStatus() {
|
||||||
if branchInfo["local"] != "" {
|
if branchInfo["local"] != "" {
|
||||||
g.repo.ahead, _ = strconv.Atoi(branchInfo["ahead"])
|
g.repo.ahead, _ = strconv.Atoi(branchInfo["ahead"])
|
||||||
g.repo.behind, _ = strconv.Atoi(branchInfo["behind"])
|
g.repo.behind, _ = strconv.Atoi(branchInfo["behind"])
|
||||||
g.repo.branch = branchInfo["local"]
|
g.repo.branch = fmt.Sprintf("%s%s", g.props.getString(BranchIcon, "Branch:"), branchInfo["local"])
|
||||||
g.repo.upstream = branchInfo["upstream"]
|
g.repo.upstream = branchInfo["upstream"]
|
||||||
} else {
|
} else {
|
||||||
g.repo.branch = g.getGitDetachedBranch()
|
g.repo.branch = g.getGitDetachedBranchContext()
|
||||||
}
|
}
|
||||||
g.repo.stashCount = g.getStashContext()
|
g.repo.stashCount = g.getStashContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *git) getGitOutputForCommand(args ...string) string {
|
func (g *git) getGitCommandOutput(args ...string) string {
|
||||||
args = append([]string{"-c", "core.quotepath=false", "-c", "color.status=false"}, args...)
|
args = append([]string{"-c", "core.quotepath=false", "-c", "color.status=false"}, args...)
|
||||||
return g.env.runCommand("git", args...)
|
return g.env.runCommand("git", args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *git) getGitDetachedBranch() string {
|
func (g *git) getGitDetachedBranchContext() string {
|
||||||
commit := g.getGitOutputForCommand("rev-parse", "--short", "HEAD")
|
commit := g.getGitCommandOutput("rev-parse", "--short", "HEAD")
|
||||||
rebase := g.getGitOutputForCommand("rebase", "--show-current-patch")
|
rebase := g.getGitCommandOutput("rebase", "--show-current-patch")
|
||||||
if rebase != "" {
|
if rebase != "" {
|
||||||
return fmt.Sprintf("%s%s", g.props.getString(RebaseIcon, "REBASE: "), commit)
|
return g.getGitRebaseContext(commit)
|
||||||
}
|
}
|
||||||
ref := g.getGitOutputForCommand("symbolic-ref", "-q", "--short", "HEAD")
|
// name of branch
|
||||||
|
ref := g.getGitCommandOutput("symbolic-ref", "-q", "--short", "HEAD")
|
||||||
if ref == "" {
|
if ref == "" {
|
||||||
ref = g.getGitOutputForCommand("describe", "--tags", "--exact-match")
|
// get a tag name if there's a match for HEAD
|
||||||
|
ref = g.getGitCommandOutput("describe", "--tags", "--exact-match")
|
||||||
}
|
}
|
||||||
if ref == "" {
|
if ref == "" {
|
||||||
|
// revert to the short commit hash
|
||||||
ref = commit
|
ref = commit
|
||||||
}
|
}
|
||||||
return ref
|
return ref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *git) getGitRebaseContext(commit string) string {
|
||||||
|
if g.env.hasFolder(".git/rebase-merge") {
|
||||||
|
origin := g.getGitRefFileSymbolicName("rebase-merge/orig-head")
|
||||||
|
onto := g.getGitRefFileSymbolicName("rebase-merge/onto")
|
||||||
|
step := g.getGitFileContents("rebase-merge/msgnum")
|
||||||
|
total := g.getGitFileContents("rebase-merge/end")
|
||||||
|
icon := g.props.getString(RebaseIcon, "REBASE:")
|
||||||
|
return fmt.Sprintf("%s%s onto %s (%s/%s) at %s", icon, origin, onto, step, total, commit)
|
||||||
|
}
|
||||||
|
if g.env.hasFolder(".git/rebase-apply") {
|
||||||
|
head := g.getGitFileContents("rebase-apply/head-name")
|
||||||
|
origin := strings.Replace(head, "refs/heads/", "", 1)
|
||||||
|
step := g.getGitFileContents("rebase-apply/next")
|
||||||
|
total := g.getGitFileContents("rebase-apply/last")
|
||||||
|
icon := g.props.getString(RebaseIcon, "REBASING:")
|
||||||
|
return fmt.Sprintf("%s%s (%s/%s) at %s", icon, origin, step, total, commit)
|
||||||
|
}
|
||||||
|
icon := g.props.getString(RebaseIcon, "REBASE:")
|
||||||
|
return fmt.Sprintf("%sUNKNOWN", icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *git) getGitFileContents(file string) string {
|
||||||
|
content := g.env.getFileContent(fmt.Sprintf(".git/%s", file))
|
||||||
|
return strings.Trim(content, " \r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *git) getGitRefFileSymbolicName(refFile string) string {
|
||||||
|
ref := g.getGitFileContents(refFile)
|
||||||
|
return g.getGitCommandOutput("name-rev", "--name-only", "--exclude=tags/*", ref)
|
||||||
|
}
|
||||||
|
|
||||||
func (g *git) parseGitStats(output []string, working bool) *gitStatus {
|
func (g *git) parseGitStats(output []string, working bool) *gitStatus {
|
||||||
status := gitStatus{}
|
status := gitStatus{}
|
||||||
if len(output) <= 1 {
|
if len(output) <= 1 {
|
||||||
|
@ -168,7 +200,7 @@ func (g *git) parseGitStats(output []string, working bool) *gitStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *git) getStashContext() int {
|
func (g *git) getStashContext() int {
|
||||||
stash := g.getGitOutputForCommand("stash", "list")
|
stash := g.getGitCommandOutput("stash", "list")
|
||||||
return numberOfLinesInString(stash)
|
return numberOfLinesInString(stash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,29 +35,118 @@ func TestGetGitOutputForCommand(t *testing.T) {
|
||||||
g := &git{
|
g := &git{
|
||||||
env: env,
|
env: env,
|
||||||
}
|
}
|
||||||
got := g.getGitOutputForCommand(commandArgs...)
|
got := g.getGitCommandOutput(commandArgs...)
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetGitDetachedBranch(t *testing.T) {
|
type detachedContext struct {
|
||||||
want := "master"
|
currentCommit string
|
||||||
|
rebase string
|
||||||
|
rebaseMerge bool
|
||||||
|
rebaseApply bool
|
||||||
|
origin string
|
||||||
|
onto string
|
||||||
|
step string
|
||||||
|
total string
|
||||||
|
branchName string
|
||||||
|
tagName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupDetachedHeadEnv(context *detachedContext) environmentInfo {
|
||||||
env := new(MockedEnvironment)
|
env := new(MockedEnvironment)
|
||||||
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "symbolic-ref", "--short", "HEAD"}).Return(want)
|
env.On("hasFolder", ".git/rebase-merge").Return(context.rebaseMerge)
|
||||||
|
env.On("hasFolder", ".git/rebase-apply").Return(context.rebaseApply)
|
||||||
|
env.On("getFileContent", ".git/rebase-merge/orig-head").Return(context.origin)
|
||||||
|
env.On("getFileContent", ".git/rebase-merge/onto").Return(context.onto)
|
||||||
|
env.On("getFileContent", ".git/rebase-merge/msgnum").Return(context.step)
|
||||||
|
env.On("getFileContent", ".git/rebase-apply/next").Return(context.step)
|
||||||
|
env.On("getFileContent", ".git/rebase-merge/end").Return(context.total)
|
||||||
|
env.On("getFileContent", ".git/rebase-apply/last").Return(context.total)
|
||||||
|
env.On("getFileContent", ".git/rebase-apply/head-name").Return(context.origin)
|
||||||
|
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "rev-parse", "--short", "HEAD"}).Return(context.currentCommit)
|
||||||
|
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "rebase", "--show-current-patch"}).Return(context.rebase)
|
||||||
|
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "symbolic-ref", "-q", "--short", "HEAD"}).Return(context.branchName)
|
||||||
|
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "describe", "--tags", "--exact-match"}).Return(context.tagName)
|
||||||
|
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.origin}).Return(context.origin)
|
||||||
|
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.onto}).Return(context.onto)
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitDetachedCommitHash(t *testing.T) {
|
||||||
|
want := "lalasha1"
|
||||||
|
context := &detachedContext{
|
||||||
|
currentCommit: want,
|
||||||
|
}
|
||||||
|
env := setupDetachedHeadEnv(context)
|
||||||
g := &git{
|
g := &git{
|
||||||
env: env,
|
env: env,
|
||||||
}
|
}
|
||||||
got := g.getGitDetachedBranch()
|
got := g.getGitDetachedBranchContext()
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetGitDetachedBranchEmpty(t *testing.T) {
|
func TestGetGitDetachedTagName(t *testing.T) {
|
||||||
want := "unknown"
|
want := "lalasha1"
|
||||||
env := new(MockedEnvironment)
|
context := &detachedContext{
|
||||||
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "symbolic-ref", "--short", "HEAD"}).Return("")
|
currentCommit: "whatever",
|
||||||
|
tagName: want,
|
||||||
|
}
|
||||||
|
env := setupDetachedHeadEnv(context)
|
||||||
g := &git{
|
g := &git{
|
||||||
env: env,
|
env: env,
|
||||||
}
|
}
|
||||||
got := g.getGitDetachedBranch()
|
got := g.getGitDetachedBranchContext()
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitDetachedRebaseMerge(t *testing.T) {
|
||||||
|
want := "REBASE:cool-feature-bro onto main (2/3) at whatever"
|
||||||
|
context := &detachedContext{
|
||||||
|
currentCommit: "whatever",
|
||||||
|
rebase: "true",
|
||||||
|
rebaseMerge: true,
|
||||||
|
origin: "cool-feature-bro",
|
||||||
|
onto: "main",
|
||||||
|
step: "2",
|
||||||
|
total: "3",
|
||||||
|
}
|
||||||
|
env := setupDetachedHeadEnv(context)
|
||||||
|
g := &git{
|
||||||
|
env: env,
|
||||||
|
}
|
||||||
|
got := g.getGitDetachedBranchContext()
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitDetachedRebaseApply(t *testing.T) {
|
||||||
|
want := "REBASING:cool-feature-bro (2/3) at whatever"
|
||||||
|
context := &detachedContext{
|
||||||
|
currentCommit: "whatever",
|
||||||
|
rebase: "true",
|
||||||
|
rebaseApply: true,
|
||||||
|
origin: "cool-feature-bro",
|
||||||
|
step: "2",
|
||||||
|
total: "3",
|
||||||
|
}
|
||||||
|
env := setupDetachedHeadEnv(context)
|
||||||
|
g := &git{
|
||||||
|
env: env,
|
||||||
|
}
|
||||||
|
got := g.getGitDetachedBranchContext()
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitDetachedRebaseUnknown(t *testing.T) {
|
||||||
|
want := "REBASE:UNKNOWN"
|
||||||
|
context := &detachedContext{
|
||||||
|
currentCommit: "whatever",
|
||||||
|
rebase: "true",
|
||||||
|
}
|
||||||
|
env := setupDetachedHeadEnv(context)
|
||||||
|
g := &git{
|
||||||
|
env: env,
|
||||||
|
}
|
||||||
|
got := g.getGitDetachedBranchContext()
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,16 @@ func (env *MockedEnvironment) hasFiles(pattern string) bool {
|
||||||
return args.Bool(0)
|
return args.Bool(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (env *MockedEnvironment) hasFolder(folder string) bool {
|
||||||
|
args := env.Called(folder)
|
||||||
|
return args.Bool(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (env *MockedEnvironment) getFileContent(file string) string {
|
||||||
|
args := env.Called(file)
|
||||||
|
return args.String(0)
|
||||||
|
}
|
||||||
|
|
||||||
func (env *MockedEnvironment) getPathSeperator() string {
|
func (env *MockedEnvironment) getPathSeperator() string {
|
||||||
args := env.Called(nil)
|
args := env.Called(nil)
|
||||||
return args.String(0)
|
return args.String(0)
|
||||||
|
|
|
@ -52,7 +52,8 @@
|
||||||
"branch_behind_icon": "↓",
|
"branch_behind_icon": "↓",
|
||||||
"branch_gone_icon": "≢",
|
"branch_gone_icon": "≢",
|
||||||
"local_working_icon": "",
|
"local_working_icon": "",
|
||||||
"local_staged_icon": ""
|
"local_staged_icon": "",
|
||||||
|
"rebase_icon": " "
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue