feat(git): mapped branches

resolves #4979
This commit is contained in:
Jan De Dobbeleer 2024-07-26 23:06:10 +02:00 committed by Jan De Dobbeleer
parent 1c908a8ce6
commit 7a6478269c
10 changed files with 195 additions and 137 deletions

View file

@ -35,7 +35,6 @@ require (
github.com/goccy/go-yaml v1.11.3 github.com/goccy/go-yaml v1.11.3
github.com/gookit/goutil v0.6.16 github.com/gookit/goutil v0.6.16
github.com/hashicorp/hcl/v2 v2.21.0 github.com/hashicorp/hcl/v2 v2.21.0
github.com/mattn/go-runewidth v0.0.16
github.com/pelletier/go-toml/v2 v2.2.2 github.com/pelletier/go-toml/v2 v2.2.2
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
@ -79,6 +78,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect

View file

@ -111,6 +111,8 @@ const (
UntrackedModes properties.Property = "untracked_modes" UntrackedModes properties.Property = "untracked_modes"
// IgnoreSubmodules list the optional ignore-submodules mode per repo // IgnoreSubmodules list the optional ignore-submodules mode per repo
IgnoreSubmodules properties.Property = "ignore_submodules" IgnoreSubmodules properties.Property = "ignore_submodules"
// MappedBranches allows overriding certain branches with an icon/text
MappedBranches properties.Property = "mapped_branches"
DETACHED = "(detached)" DETACHED = "(detached)"
BRANCHPREFIX = "ref: refs/heads/" BRANCHPREFIX = "ref: refs/heads/"
@ -333,7 +335,7 @@ func (g *Git) getBareRepoInfo() {
head := g.FileContents(g.workingDir, "HEAD") head := g.FileContents(g.workingDir, "HEAD")
branchIcon := g.props.GetString(BranchIcon, "\uE0A0") branchIcon := g.props.GetString(BranchIcon, "\uE0A0")
g.Ref = strings.Replace(head, "ref: refs/heads/", "", 1) g.Ref = strings.Replace(head, "ref: refs/heads/", "", 1)
g.HEAD = fmt.Sprintf("%s%s", branchIcon, g.Ref) g.HEAD = fmt.Sprintf("%s%s", branchIcon, g.formatBranch(g.Ref))
if !g.props.GetBool(FetchUpstreamIcon, false) { if !g.props.GetBool(FetchUpstreamIcon, false) {
return return
} }
@ -550,23 +552,28 @@ func (g *Git) setGitStatus() {
g.Working.add(workingCode) g.Working.add(workingCode)
g.Staging.add(stagingCode) g.Staging.add(stagingCode)
} }
const ( const (
HASH = "# branch.oid " HASH = "# branch.oid "
REF = "# branch.head " REF = "# branch.head "
UPSTREAM = "# branch.upstream " UPSTREAM = "# branch.upstream "
BRANCHSTATUS = "# branch.ab " BRANCHSTATUS = "# branch.ab "
) )
// firstly assume that upstream is gone // firstly assume that upstream is gone
g.UpstreamGone = true g.UpstreamGone = true
statusFormats := g.props.GetKeyValueMap(StatusFormats, map[string]string{}) statusFormats := g.props.GetKeyValueMap(StatusFormats, map[string]string{})
g.Working = &GitStatus{ScmStatus: ScmStatus{Formats: statusFormats}} g.Working = &GitStatus{ScmStatus: ScmStatus{Formats: statusFormats}}
g.Staging = &GitStatus{ScmStatus: ScmStatus{Formats: statusFormats}} g.Staging = &GitStatus{ScmStatus: ScmStatus{Formats: statusFormats}}
untrackedMode := g.getUntrackedFilesMode() untrackedMode := g.getUntrackedFilesMode()
args := []string{"status", untrackedMode, "--branch", "--porcelain=2"} args := []string{"status", untrackedMode, "--branch", "--porcelain=2"}
ignoreSubmodulesMode := g.getIgnoreSubmodulesMode() ignoreSubmodulesMode := g.getIgnoreSubmodulesMode()
if len(ignoreSubmodulesMode) > 0 { if len(ignoreSubmodulesMode) > 0 {
args = append(args, ignoreSubmodulesMode) args = append(args, ignoreSubmodulesMode)
} }
output := g.getGitCommandOutput(args...) output := g.getGitCommandOutput(args...)
for _, line := range strings.Split(output, "\n") { for _, line := range strings.Split(output, "\n") {
if strings.HasPrefix(line, HASH) && len(line) >= len(HASH)+7 { if strings.HasPrefix(line, HASH) && len(line) >= len(HASH)+7 {
@ -574,16 +581,19 @@ func (g *Git) setGitStatus() {
g.Hash = line[len(HASH):] g.Hash = line[len(HASH):]
continue continue
} }
if strings.HasPrefix(line, REF) && len(line) > len(REF) { if strings.HasPrefix(line, REF) && len(line) > len(REF) {
g.Ref = line[len(REF):] g.Ref = line[len(REF):]
continue continue
} }
if strings.HasPrefix(line, UPSTREAM) && len(line) > len(UPSTREAM) { if strings.HasPrefix(line, UPSTREAM) && len(line) > len(UPSTREAM) {
// status reports upstream, but upstream may be gone (must check BRANCHSTATUS) // status reports upstream, but upstream may be gone (must check BRANCHSTATUS)
g.Upstream = line[len(UPSTREAM):] g.Upstream = line[len(UPSTREAM):]
g.UpstreamGone = true g.UpstreamGone = true
continue continue
} }
if strings.HasPrefix(line, BRANCHSTATUS) && len(line) > len(BRANCHSTATUS) { if strings.HasPrefix(line, BRANCHSTATUS) && len(line) > len(BRANCHSTATUS) {
status := line[len(BRANCHSTATUS):] status := line[len(BRANCHSTATUS):]
splitted := strings.Split(status, " ") splitted := strings.Split(status, " ")
@ -596,6 +606,7 @@ func (g *Git) setGitStatus() {
g.UpstreamGone = false g.UpstreamGone = false
continue continue
} }
addToStatus(line) addToStatus(line)
} }
} }
@ -615,7 +626,7 @@ func (g *Git) setGitHEADContext() {
g.Detached = true g.Detached = true
g.setPrettyHEADName() g.setPrettyHEADName()
} else { } else {
head := g.formatHEAD(g.Ref) head := g.formatBranch(g.Ref)
g.HEAD = fmt.Sprintf("%s%s", branchIcon, head) g.HEAD = fmt.Sprintf("%s%s", branchIcon, head)
} }
@ -633,7 +644,7 @@ func (g *Git) setGitHEADContext() {
origin = formatDetached() origin = formatDetached()
} else { } else {
head = strings.Replace(head, "refs/heads/", "", 1) head = strings.Replace(head, "refs/heads/", "", 1)
origin = branchIcon + g.formatHEAD(head) origin = branchIcon + g.formatBranch(head)
} }
return origin return origin
} }
@ -642,7 +653,7 @@ func (g *Git) setGitHEADContext() {
g.Rebase = true g.Rebase = true
origin := getPrettyNameOrigin("rebase-merge/head-name") origin := getPrettyNameOrigin("rebase-merge/head-name")
onto := g.getGitRefFileSymbolicName("rebase-merge/onto") onto := g.getGitRefFileSymbolicName("rebase-merge/onto")
onto = g.formatHEAD(onto) onto = g.formatBranch(onto)
step := g.FileContents(g.workingDir, "rebase-merge/msgnum") step := g.FileContents(g.workingDir, "rebase-merge/msgnum")
total := g.FileContents(g.workingDir, "rebase-merge/end") total := g.FileContents(g.workingDir, "rebase-merge/end")
icon := g.props.GetString(RebaseIcon, "\uE728 ") icon := g.props.GetString(RebaseIcon, "\uE728 ")
@ -680,7 +691,7 @@ func (g *Git) setGitHEADContext() {
theirs = g.formatSHA(matches["theirs"]) theirs = g.formatSHA(matches["theirs"])
default: default:
headIcon = branchIcon headIcon = branchIcon
theirs = g.formatHEAD(matches["theirs"]) theirs = g.formatBranch(matches["theirs"])
} }
g.HEAD = fmt.Sprintf("%s%s%s into %s", icon, headIcon, theirs, formatDetached()) g.HEAD = fmt.Sprintf("%s%s%s into %s", icon, headIcon, theirs, formatDetached())
return return
@ -732,15 +743,6 @@ func (g *Git) setGitHEADContext() {
g.HEAD = formatDetached() g.HEAD = formatDetached()
} }
func (g *Git) formatHEAD(head string) string {
maxLength := g.props.GetInt(BranchMaxLength, 0)
if maxLength == 0 || len(head) < maxLength {
return head
}
symbol := g.props.GetString(TruncateSymbol, "")
return head[0:maxLength] + symbol
}
func (g *Git) formatSHA(sha string) string { func (g *Git) formatSHA(sha string) string {
if len(sha) <= 7 { if len(sha) <= 7 {
return sha return sha
@ -765,7 +767,7 @@ func (g *Git) setPrettyHEADName() {
if strings.HasPrefix(HEADRef, BRANCHPREFIX) { if strings.HasPrefix(HEADRef, BRANCHPREFIX) {
branchName := strings.TrimPrefix(HEADRef, BRANCHPREFIX) branchName := strings.TrimPrefix(HEADRef, BRANCHPREFIX)
g.Ref = branchName g.Ref = branchName
g.HEAD = fmt.Sprintf("%s%s", g.props.GetString(BranchIcon, "\uE0A0"), g.formatHEAD(branchName)) g.HEAD = fmt.Sprintf("%s%s", g.props.GetString(BranchIcon, "\uE0A0"), g.formatBranch(branchName))
return return
} }
// no branch, points to commit // no branch, points to commit

View file

@ -140,23 +140,27 @@ func (p *Plastic) getHeadChangeset() int {
func (p *Plastic) setSelector() { func (p *Plastic) setSelector() {
var ref string var ref string
selector := p.FileContents(p.plasticWorkspaceFolder+"/.plastic/", "plastic.selector") selector := p.FileContents(p.plasticWorkspaceFolder+"/.plastic/", "plastic.selector")
// changeset // changeset
ref = p.parseChangesetSelector(selector) ref = p.parseChangesetSelector(selector)
if len(ref) > 0 { if len(ref) > 0 {
p.Selector = fmt.Sprintf("%s%s", p.props.GetString(CommitIcon, "\uF417"), ref) p.Selector = fmt.Sprintf("%s%s", p.props.GetString(CommitIcon, "\uF417"), ref)
return return
} }
// fallback to label // fallback to label
ref = p.parseLabelSelector(selector) ref = p.parseLabelSelector(selector)
if len(ref) > 0 { if len(ref) > 0 {
p.Selector = fmt.Sprintf("%s%s", p.props.GetString(TagIcon, "\uF412"), ref) p.Selector = fmt.Sprintf("%s%s", p.props.GetString(TagIcon, "\uF412"), ref)
return return
} }
// fallback to branch/smartbranch // fallback to branch/smartbranch
ref = p.parseBranchSelector(selector) ref = p.parseBranchSelector(selector)
if len(ref) > 0 { if len(ref) > 0 {
ref = p.truncateBranch(ref) ref = p.formatBranch(ref)
} }
p.Selector = fmt.Sprintf("%s%s", p.props.GetString(BranchIcon, "\uE0A0"), ref) p.Selector = fmt.Sprintf("%s%s", p.props.GetString(BranchIcon, "\uE0A0"), ref)
} }

View file

@ -93,5 +93,5 @@ func (g *Git) parsePoshGitHEAD(head string) string {
return fmt.Sprintf("%s%s", g.props.GetString(TagIcon, "\uF412"), head) return fmt.Sprintf("%s%s", g.props.GetString(TagIcon, "\uF412"), head)
} }
// regular branch // regular branch
return fmt.Sprintf("%s%s", g.props.GetString(BranchIcon, "\uE0A0"), g.formatHEAD(head)) return fmt.Sprintf("%s%s", g.props.GetString(BranchIcon, "\uE0A0"), g.formatBranch(head))
} }

View file

@ -3,6 +3,7 @@ package segments
import ( import (
"fmt" "fmt"
"strings" "strings"
"unicode/utf8"
"github.com/jandedobbeleer/oh-my-posh/src/properties" "github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/runtime"
@ -109,21 +110,40 @@ func (s *scm) Init(props properties.Properties, env runtime.Environment) {
s.env = env s.env = env
} }
func (s *scm) truncateBranch(branch string) string { func (s *scm) formatBranch(branch string) string {
fullBranchPath := s.props.GetBool(FullBranchPath, true) mappedBranches := s.props.GetKeyValueMap(MappedBranches, make(map[string]string))
maxLength := s.props.GetInt(BranchMaxLength, 0) for key, value := range mappedBranches {
matchSubFolders := strings.HasSuffix(key, "*")
if matchSubFolders && len(key) > 1 {
key = key[0 : len(key)-1] // remove trailing /* or \*
}
if !strings.HasPrefix(branch, key) {
continue
}
branch = strings.Replace(branch, key, value, 1)
break
}
fullBranchPath := s.props.GetBool(FullBranchPath, true)
if !fullBranchPath && strings.Contains(branch, "/") { if !fullBranchPath && strings.Contains(branch, "/") {
index := strings.LastIndex(branch, "/") index := strings.LastIndex(branch, "/")
branch = branch[index+1:] branch = branch[index+1:]
} }
maxLength := s.props.GetInt(BranchMaxLength, 0)
if maxLength == 0 || len(branch) <= maxLength { if maxLength == 0 || len(branch) <= maxLength {
return branch return branch
} }
symbol := s.props.GetString(TruncateSymbol, "") truncateSymbol := s.props.GetString(TruncateSymbol, "")
return branch[0:maxLength] + symbol lenTruncateSymbol := utf8.RuneCountInString(truncateSymbol)
maxLength -= lenTruncateSymbol
runes := []rune(branch)
return string(runes[0:maxLength]) + truncateSymbol
} }
func (s *scm) shouldIgnoreRootRepository(rootDir string) bool { func (s *scm) shouldIgnoreRootRepository(rootDir string) bool {

View file

@ -105,78 +105,6 @@ func TestScmStatusString(t *testing.T) {
} }
} }
func TestTruncateBranch(t *testing.T) {
cases := []struct {
Case string
Expected string
Branch string
FullBranch bool
MaxLength any
}{
{Case: "No limit", Expected: "are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: false},
{Case: "No limit - larger", Expected: "are-belong", Branch: "/all-your-base/are-belong-to-us", FullBranch: false, MaxLength: 10.0},
{Case: "No limit - smaller", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 13.0},
{Case: "Invalid setting", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: "burp"},
{Case: "Lower than limit", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 20.0},
{Case: "No limit - full branch", Expected: "/all-your-base/are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: true},
{Case: "No limit - larger - full branch", Expected: "/all-your-base", Branch: "/all-your-base/are-belong-to-us", FullBranch: true, MaxLength: 14.0},
{Case: "No limit - smaller - full branch ", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 14.0},
{Case: "Invalid setting - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: "burp"},
{Case: "Lower than limit - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 20.0},
}
for _, tc := range cases {
props := properties.Map{
BranchMaxLength: tc.MaxLength,
FullBranchPath: tc.FullBranch,
}
p := &Plastic{
scm: scm{
props: props,
},
}
assert.Equal(t, tc.Expected, p.truncateBranch(tc.Branch), tc.Case)
}
}
func TestTruncateBranchWithSymbol(t *testing.T) {
cases := []struct {
Case string
Expected string
Branch string
FullBranch bool
MaxLength any
TruncateSymbol any
}{
{Case: "No limit", Expected: "are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: false, TruncateSymbol: "..."},
{Case: "No limit - larger", Expected: "are-belong...", Branch: "/all-your-base/are-belong-to-us", FullBranch: false, MaxLength: 10.0, TruncateSymbol: "..."},
{Case: "No limit - smaller", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 13.0, TruncateSymbol: "..."},
{Case: "Invalid setting", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: "burp", TruncateSymbol: "..."},
{Case: "Lower than limit", Expected: "all-your-base", Branch: "/all-your-base", FullBranch: false, MaxLength: 20.0, TruncateSymbol: "..."},
{Case: "No limit - full branch", Expected: "/all-your-base/are-belong-to-us", Branch: "/all-your-base/are-belong-to-us", FullBranch: true, TruncateSymbol: "..."},
{Case: "No limit - larger - full branch", Expected: "/all-your-base...", Branch: "/all-your-base/are-belong-to-us", FullBranch: true, MaxLength: 14.0, TruncateSymbol: "..."},
{Case: "No limit - smaller - full branch ", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 14.0, TruncateSymbol: "..."},
{Case: "Invalid setting - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: "burp", TruncateSymbol: "..."},
{Case: "Lower than limit - full branch", Expected: "/all-your-base", Branch: "/all-your-base", FullBranch: true, MaxLength: 20.0, TruncateSymbol: "..."},
}
for _, tc := range cases {
props := properties.Map{
BranchMaxLength: tc.MaxLength,
TruncateSymbol: tc.TruncateSymbol,
FullBranchPath: tc.FullBranch,
}
p := &Plastic{
scm: scm{
props: props,
},
}
assert.Equal(t, tc.Expected, p.truncateBranch(tc.Branch), tc.Case)
}
}
func TestHasCommand(t *testing.T) { func TestHasCommand(t *testing.T) {
cases := []struct { cases := []struct {
Case string Case string
@ -212,3 +140,90 @@ func TestHasCommand(t *testing.T) {
assert.Equal(t, tc.ExpectedCommand, s.command, tc.Case) assert.Equal(t, tc.ExpectedCommand, s.command, tc.Case)
} }
} }
func TestFormatBranch(t *testing.T) {
cases := []struct {
Case string
Expected string
Input string
MappedBranches map[string]string
BranchMaxLength int
TruncateSymbol string
NoFullBranchPath bool
}{
{
Case: "No settings",
Input: "main",
Expected: "main",
},
{
Case: "BranchMaxLength higher than branch name",
Input: "main",
Expected: "main",
BranchMaxLength: 10,
},
{
Case: "BranchMaxLength lower than branch name",
Input: "feature/test-this-branch",
Expected: "featu",
BranchMaxLength: 5,
},
{
Case: "BranchMaxLength lower than branch name, with truncate symbol",
Input: "feature/test-this-branch",
Expected: "feat…",
BranchMaxLength: 5,
TruncateSymbol: "…",
},
{
Case: "BranchMaxLength lower than branch name, with truncate symbol and no FullBranchPath",
Input: "feature/test-this-branch",
Expected: "test…",
BranchMaxLength: 5,
TruncateSymbol: "…",
NoFullBranchPath: true,
},
{
Case: "BranchMaxLength lower to branch name, with truncate symbol",
Input: "feat",
Expected: "feat",
BranchMaxLength: 5,
TruncateSymbol: "…",
},
{
Case: "Branch mapping, no BranchMaxLength",
Input: "feat/my-new-feature",
Expected: "🚀 my-new-feature",
MappedBranches: map[string]string{
"feat/*": "🚀 ",
"bug/*": "🐛 ",
},
},
{
Case: "Branch mapping, with BranchMaxLength",
Input: "feat/my-new-feature",
Expected: "🚀 my-",
BranchMaxLength: 5,
MappedBranches: map[string]string{
"feat/*": "🚀 ",
"bug/*": "🐛 ",
},
},
}
for _, tc := range cases {
g := &Git{
scm: scm{
props: properties.Map{
MappedBranches: tc.MappedBranches,
BranchMaxLength: tc.BranchMaxLength,
TruncateSymbol: tc.TruncateSymbol,
FullBranchPath: !tc.NoFullBranchPath,
},
},
}
got := g.formatBranch(tc.Input)
assert.Equal(t, tc.Expected, got, tc.Case)
}
}

View file

@ -4,18 +4,14 @@ import (
"fmt" "fmt"
"os" "os"
"strings" "strings"
"unicode/utf8"
"github.com/jandedobbeleer/oh-my-posh/src/color" "github.com/jandedobbeleer/oh-my-posh/src/color"
"github.com/jandedobbeleer/oh-my-posh/src/log" "github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/regex"
"github.com/jandedobbeleer/oh-my-posh/src/shell" "github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/mattn/go-runewidth"
) )
func init() {
runewidth.DefaultCondition.EastAsianWidth = false
}
type style struct { type style struct {
AnchorStart string AnchorStart string
AnchorEnd string AnchorEnd string
@ -400,7 +396,7 @@ func write(s rune) {
} }
} }
length += runewidth.RuneWidth(s) length += utf8.RuneCountInString(string(s))
lastRune = s lastRune = s
builder.WriteRune(s) builder.WriteRune(s)
} }

View file

@ -144,9 +144,15 @@
"full_branch_path": { "full_branch_path": {
"type": "boolean", "type": "boolean",
"title": "Full branch path", "title": "Full branch path",
"description": "display the full branch path instead of only the branch name", "description": "display the full branch path instead of only the last part (e.g. feature/branch instead of branch)",
"default": true "default": true
}, },
"mapped_branches": {
"type": "object",
"title": "Mapped Branches",
"description": "Custom glyph/text for specific branches",
"default": {}
},
"extra_prompt": { "extra_prompt": {
"type": "object", "type": "object",
"default": {}, "default": {},
@ -1467,6 +1473,12 @@
"title": "Status string formats", "title": "Status string formats",
"description": "a key, value map representing the remote URL (or a part of that URL) and icon to use in case the upstream URL contains the key. These get precedence over the standard icons", "description": "a key, value map representing the remote URL (or a part of that URL) and icon to use in case the upstream URL contains the key. These get precedence over the standard icons",
"default": {} "default": {}
},
"mapped_branches": {
"$ref": "#/definitions/mapped_branches"
},
"full_branch_path": {
"$ref": "#/definitions/full_branch_path"
} }
} }
} }
@ -3655,6 +3667,9 @@
}, },
"native_fallback": { "native_fallback": {
"$ref": "#/definitions/native_fallback" "$ref": "#/definitions/native_fallback"
},
"mapped_branches": {
"$ref": "#/definitions/mapped_branches"
} }
} }
} }

View file

@ -9,26 +9,6 @@ sidebar_label: Git
Display git information when in a git repository. Also works for subfolders. For maximum compatibility, Display git information when in a git repository. Also works for subfolders. For maximum compatibility,
make sure your `git` executable is up-to-date (when branch or status information is incorrect for example). make sure your `git` executable is up-to-date (when branch or status information is incorrect for example).
:::tip
If you want to display the default [posh-git][poshgit] output, **do not** use this segment
but add the following snippet after initializing Oh My Posh in your `$PROFILE`:
```powershell
function Set-PoshGitStatus {
$global:GitStatus = Get-GitStatus
$env:POSH_GIT_STRING = Write-GitStatus -Status $global:GitStatus
}
New-Alias -Name 'Set-PoshContext' -Value 'Set-PoshGitStatus' -Scope Global -Force
```
You can then use the `POSH_GIT_STRING` environment variable in a [text segment][text]:
```json
"template": "{{ if .Env.POSH_GIT_STRING }} {{ .Env.POSH_GIT_STRING }} {{ end }}"
```
:::
## Sample Configuration ## Sample Configuration
import Config from "@site/src/components/Config.js"; import Config from "@site/src/components/Config.js";
@ -55,6 +35,10 @@ import Config from "@site/src/components/Config.js";
"/Users/user/Projects/oh-my-posh/": "no", "/Users/user/Projects/oh-my-posh/": "no",
}, },
source: "cli", source: "cli",
mapped_branches: {
"feat/*": "🚀 ",
"bug/*": "🐛 ",
},
}, },
}} }}
/> />
@ -78,6 +62,8 @@ You can set the following properties to `true` to enable fetching additional inf
| `fetch_user` | [`User`](#user) | `false` | fetch the current configured user for the repository | | `fetch_user` | [`User`](#user) | `false` | fetch the current configured user for the repository |
| `status_formats` | `map[string]string` | | a key, value map allowing to override how individual status items are displayed. For example, `"status_formats": { "Added": "Added: %d" }` will display the added count as `Added: 1` instead of `+1`. See the [Status](#status) section for available overrides. | | `status_formats` | `map[string]string` | | a key, value map allowing to override how individual status items are displayed. For example, `"status_formats": { "Added": "Added: %d" }` will display the added count as `Added: 1` instead of `+1`. See the [Status](#status) section for available overrides. |
| `source` | `string` | `cli` | <ul><li>`cli`: fetch the information using the git CLI</li><li>`pwsh`: fetch the information from the [posh-git][poshgit] PowerShell Module</li></ul> | | `source` | `string` | `cli` | <ul><li>`cli`: fetch the information using the git CLI</li><li>`pwsh`: fetch the information from the [posh-git][poshgit] PowerShell Module</li></ul> |
| `mapped_branches` | `object` | | custom glyph/text for specific branches. You can use `*` at the end as a wildcard character for matching |
| `full_branch_path` | `bool` | `true` | display the full branch path instead of only the last part (e.g. `feature/branch` instead of `branch`) |
### Icons ### Icons
@ -197,11 +183,30 @@ Local changes use the following syntax:
| `.Name` | `string` | the user's name | | `.Name` | `string` | the user's name |
| `.Email` | `string` | the user's email | | `.Email` | `string` | the user's email |
## posh-git
If you want to display the default [posh-git][poshgit] output, **do not** use this segment
but add the following snippet after initializing Oh My Posh in your `$PROFILE`:
```powershell
function Set-PoshGitStatus {
$global:GitStatus = Get-GitStatus
$env:POSH_GIT_STRING = Write-GitStatus -Status $global:GitStatus
}
New-Alias -Name 'Set-PoshContext' -Value 'Set-PoshGitStatus' -Scope Global -Force
```
You can then use the `POSH_GIT_STRING` environment variable in a [text segment][text]:
```json
"template": "{{ if .Env.POSH_GIT_STRING }} {{ .Env.POSH_GIT_STRING }} {{ end }}"
```
[poshgit]: https://github.com/dahlbyk/posh-git [poshgit]: https://github.com/dahlbyk/posh-git
[templates]: /docs/configuration/templates [templates]: /docs/configuration/templates
[hyperlinks]: /docs/configuration/templates#custom [hyperlinks]: /docs/configuration/templates#custom
[untracked]: https://git-scm.com/docs/git-status#Documentation/git-status.txt---untracked-filesltmodegt [untracked]: https://git-scm.com/docs/git-status#Documentation/git-status.txt---untracked-filesltmodegt
[submodules]: https://git-scm.com/docs/git-status#Documentation/git-status.txt---ignore-submodulesltwhengt [submodules]: https://git-scm.com/docs/git-status#Documentation/git-status.txt---ignore-submodulesltwhengt
[kraken-ref]: https://www.gitkraken.com/invite/nQmDPR9D [kraken-ref]: https://www.gitkraken.com/invite/nQmDPR9D
[text]: text.mdx [text]: /docs/segments/system/text
[exclude_folders]: /docs/configuration/segment#include--exclude-folders [exclude_folders]: /docs/configuration/segment#include--exclude-folders

View file

@ -50,25 +50,26 @@ by leaving a like!
As doing multiple `cm` calls can slow down the prompt experience, we do not fetch information by default. As doing multiple `cm` calls can slow down the prompt experience, we do not fetch information by default.
You can set the following property to `true` to enable fetching additional information (and populate the template). You can set the following property to `true` to enable fetching additional information (and populate the template).
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ---------------- | :-----------------: | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ---------------- | :-----------------: | :-----: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fetch_status` | `boolean` | `false` | fetch the local changes | | `fetch_status` | `boolean` | `false` | fetch the local changes |
| `status_formats` | `map[string]string` | | a key, value map allowing to override how individual status items are displayed. For example, `"status_formats": { "Added": "Added: %d" }` will display the added count as `Added: 1` instead of `+1`. See the [Status](#status) section for available overrides | | `status_formats` | `map[string]string` | | a key, value map allowing to override how individual status items are displayed. For example, `"status_formats": { "Added": "Added: %d" }` will display the added count as `Added: 1` instead of `+1`. See the [Status](#status) section for available overrides |
### Icons ### Icons
#### Branch #### Branch
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ------------------- | :------: | :------: | -------------------------------------------------------------------------- | | ------------------- | :------: | :------: | -------------------------------------------------------------------------------------------------------- |
| `branch_icon` | `string` | `\uE0A0` | the icon to use in front of the git branch name | | `branch_icon` | `string` | `\uE0A0` | the icon to use in front of the git branch name |
| `full_branch_path` | `bool` | `true` | display the full branch path: `_/main/fix-001_` instead of `_fix-001_` | | `full_branch_path` | `bool` | `true` | display the full branch path instead of only the last part (e.g. `feature/branch` instead of `branch`) |
| `branch_max_length` | `int` | `0` | the max length for the displayed branch name where `0` implies full length | | `branch_max_length` | `int` | `0` | the max length for the displayed branch name where `0` implies full length |
| `truncate_symbol` | `string` | | the icon to display when a branch name is truncated | | `truncate_symbol` | `string` | | the icon to display when a branch name is truncated |
| `mapped_branches` | `object` | | custom glyph/text for specific branches. You can use `*` at the end as a wildcard character for matching |
#### Selector #### Selector
| Name | Type | Default | Description | | Name | Type | Default | Description |
| ------------- | :------: | :------: | -------------------------------------------------------------- | | ------------- | :------: | :------: | -------------------------------------------------------------- |
| `commit_icon` | `string` | `\uF417` | icon/text to display before the commit context (detached HEAD) | | `commit_icon` | `string` | `\uF417` | icon/text to display before the commit context (detached HEAD) |
| `tag_icon` | `string` | `\uF412` | icon/text to display before the tag context | | `tag_icon` | `string` | `\uF412` | icon/text to display before the tag context |
@ -85,11 +86,11 @@ You can set the following property to `true` to enable fetching additional infor
### Properties ### Properties
| Name | Type | Description | | Name | Type | Description |
| --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.Selector` | `string` | the current selector context (branch/changeset/label) | | `.Selector` | `string` | the current selector context (branch/changeset/label) |
| `.Behind` | `bool` | the current workspace is behind and changes are incoming | | `.Behind` | `bool` | the current workspace is behind and changes are incoming |
| `.Status` | `Status` | changes in the workspace (see below) | | `.Status` | `Status` | changes in the workspace (see below) |
| `.MergePending` | `bool` | if a merge is pending and needs to be committed (known issue: when no file is left after a _Change/Delete conflict_ merge, the `MergePending` property is not set) | | `.MergePending` | `bool` | if a merge is pending and needs to be committed (known issue: when no file is left after a _Change/Delete conflict_ merge, the `MergePending` property is not set) |
### Status ### Status