diff --git a/src/segments/git.go b/src/segments/git.go index 1149f523..cc13b716 100644 --- a/src/segments/git.go +++ b/src/segments/git.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/properties" @@ -14,6 +15,19 @@ import ( "gopkg.in/ini.v1" ) +type Commit struct { + // git log -1 --pretty="format:%an%n%ae%n%cn%n%ce%n%at%n%s" + Author *User + Committer *User + Subject string + Timestamp time.Time +} + +type User struct { + Name string + Email string +} + // GitStatus represents part of the status of a git repository type GitStatus struct { ScmStatus @@ -119,6 +133,8 @@ type Git struct { poshgit bool stashCount int worktreeCount int + + commit *Commit } func (g *Git) Template() string { @@ -160,6 +176,43 @@ func (g *Git) Enabled() bool { return true } +func (g *Git) Commit() *Commit { + if g.commit != nil { + return g.commit + } + g.commit = &Commit{ + Author: &User{}, + Committer: &User{}, + } + commitBody := g.getGitCommandOutput("log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s") + splitted := strings.Split(strings.TrimSpace(commitBody), "\n") + for _, line := range splitted { + line = strings.TrimSpace(line) + if len(line) <= 3 { + continue + } + anchor := line[:3] + line = line[3:] + switch anchor { + case "an:": + g.commit.Author.Name = line + case "ae:": + g.commit.Author.Email = line + case "cn:": + g.commit.Committer.Name = line + case "ce:": + g.commit.Committer.Email = line + case "at:": + if t, err := strconv.ParseInt(line, 10, 64); err == nil { + g.commit.Timestamp = time.Unix(t, 0) + } + case "su:": + g.commit.Subject = line + } + } + return g.commit +} + func (g *Git) StashCount() int { if g.poshgit || g.stashCount != 0 { return g.stashCount diff --git a/src/segments/git_test.go b/src/segments/git_test.go index ec048567..223d796a 100644 --- a/src/segments/git_test.go +++ b/src/segments/git_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/jandedobbeleer/oh-my-posh/src/mock" "github.com/jandedobbeleer/oh-my-posh/src/platform" @@ -892,3 +893,85 @@ func TestGitIgnoreSubmodules(t *testing.T) { assert.Equal(t, tc.Expected, got, tc.Case) } } + +func TestGitCommit(t *testing.T) { + cases := []struct { + Case string + Expected *Commit + Output string + }{ + { + Case: "Clean commit", + Output: ` + an:Jan De Dobbeleer + ae:jan@ohmyposh.dev + cn:Jan De Dobbeleer + ce:jan@ohmyposh.dev + at:1673176335 + su:docs(error): you can't use cross segment properties + `, + Expected: &Commit{ + Author: &User{ + Name: "Jan De Dobbeleer", + Email: "jan@ohmyposh.dev", + }, + Committer: &User{ + Name: "Jan De Dobbeleer", + Email: "jan@ohmyposh.dev", + }, + Subject: "docs(error): you can't use cross segment properties", + Timestamp: time.Unix(1673176335, 0), + }, + }, + { + Case: "No commit output", + Expected: &Commit{ + Author: &User{}, + Committer: &User{}, + }, + }, + { + Case: "No author", + Output: ` + an: + ae: + cn:Jan De Dobbeleer + ce:jan@ohmyposh.dev + at:1673176335 + su:docs(error): you can't use cross segment properties + `, + Expected: &Commit{ + Author: &User{}, + Committer: &User{ + Name: "Jan De Dobbeleer", + Email: "jan@ohmyposh.dev", + }, + Subject: "docs(error): you can't use cross segment properties", + Timestamp: time.Unix(1673176335, 0), + }, + }, + { + Case: "Bad timestamp", + Output: ` + at:err + `, + Expected: &Commit{ + Author: &User{}, + Committer: &User{}, + }, + }, + } + + for _, tc := range cases { + env := new(mock.MockedEnvironment) + env.MockGitCommand("", tc.Output, "log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s") + g := &Git{ + scm: scm{ + env: env, + command: "git", + }, + } + got := g.Commit() + assert.Equal(t, tc.Expected, got, tc.Case) + } +} diff --git a/website/docs/segments/git.mdx b/website/docs/segments/git.mdx index 85e4bf56..eaead6c1 100644 --- a/website/docs/segments/git.mdx +++ b/website/docs/segments/git.mdx @@ -145,6 +145,7 @@ You can set the following properties to `true` to enable fetching additional inf | `.IsBare` | `boolean` | if in a bare repo or not, only set when `fetch_bare_info` is set to `true` | | `.Dir` | `string` | the repository's root directory | | `.Kraken` | `string` | a link to the current HEAD in [GitKraken][kraken-ref] for use in [hyperlinks][hyperlinks] in templates `{{ url .HEAD .Kraken }}` | +| `.Commit` | `Commit` | HEAD commit information (see below) | ### GitStatus @@ -158,6 +159,22 @@ You can set the following properties to `true` to enable fetching additional inf | `.Changed` | `boolean` | if the status contains changes or not | | `.String` | `string` | a string representation of the changes above | +### Commit + +| Name | Type | Description | +| ------------ | ----------- | -------------------------------------- | +| `.Author` | `User` | the author or the commit (see below) | +| `.Committer` | `User` | the comitter or the commit (see below) | +| `.Subject` | `string` | the commit subject | +| `.Timestamp` | `time.Time` | the commit timestamp | + +### User + +| Name | Type | Description | +| -------- | -------- | ---------------- | +| `.Name` | `string` | the user's name | +| `.Email` | `string` | the user's email | + Local changes use the following syntax: | Icon | Description |