Repository: JanDeDobbeleer/oh-my-posh
Pull request #5592 (2024-09-09 13:55:05)

feat(git): add lists of commit's head, tag and remote references
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md].
- [x] The commit message follows the [conventional commits][cc] guidelines.
- [x] Tests for the changes have been added (for bug fixes / features).
- [x] Docs have been added/updated (for bug fixes / features).

### Description

Add `.Commit.Refs` which contain lists of head, remote and tag references of the given commit.

I updated the [`log` command](https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/src/segments/git.go#L214) at  to include the references.

It adds a new line to the output, starting with `rf:`.
Using  the [`%D` placeholder](https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emDem) and [`--decorate=full` option](https://git-scm.com/docs/git-log#Documentation/git-log.txt---decorateshortfullautono) to print the references.

Those are then parsed based on the `/refs/<type>` prefix to get the

```go
type Refs struct {
	Heads   []string
	Tags    []string
	Remotes []string
}
```

references.

I'm not sure if we're ok with an array of strings being an output of these. But I found it useful to be able to `join` those in the template myself.

Feel free to suggest different approach.

<!---

Tips:

If you're not comfortable with working with Git,
we're working a guide (https://ohmyposh.dev/docs/contributing/git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/src/segments/git.go b/src/segments/git.go
index d318a484..a91cfb83 100644
--- a/src/segments/git.go
+++ b/src/segments/git.go
@@ -22,6 +22,13 @@ type Commit struct {
 	Subject   string
 	Timestamp time.Time
 	Sha       string
+	Refs      *Refs
+}
+
+type Refs struct {
+	Heads   []string
+	Tags    []string
+	Remotes []string
 }
 
 type User struct {
@@ -210,8 +217,9 @@ func (g *Git) Commit() *Commit {
 	g.commit = &Commit{
 		Author:    &User{},
 		Committer: &User{},
+		Refs:      &Refs{},
 	}
-	commitBody := g.getGitCommandOutput("log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s%nha:%H")
+	commitBody := g.getGitCommandOutput("log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s%nha:%H%nrf:%D", "--decorate=full")
 	splitted := strings.Split(strings.TrimSpace(commitBody), "\n")
 	for _, line := range splitted {
 		line = strings.TrimSpace(line)
@@ -237,6 +245,25 @@ func (g *Git) Commit() *Commit {
 			g.commit.Subject = line
 		case "ha:":
 			g.commit.Sha = line
+		case "rf:":
+			refs := strings.Split(line, ", ")
+			for _, ref := range refs {
+				ref = strings.TrimSpace(ref)
+				switch {
+				case strings.HasSuffix(ref, "HEAD"):
+					continue
+				case strings.HasPrefix(ref, "tag: refs/tags/"):
+					g.commit.Refs.Tags = append(g.commit.Refs.Tags, strings.TrimPrefix(ref, "tag: refs/tags/"))
+				case strings.HasPrefix(ref, "refs/remotes/"):
+					g.commit.Refs.Remotes = append(g.commit.Refs.Remotes, strings.TrimPrefix(ref, "refs/remotes/"))
+				case strings.HasPrefix(ref, "HEAD -> refs/heads/"):
+					g.commit.Refs.Heads = append(g.commit.Refs.Heads, strings.TrimPrefix(ref, "HEAD -> refs/heads/"))
+				case strings.HasPrefix(ref, "refs/heads/"):
+					g.commit.Refs.Heads = append(g.commit.Refs.Heads, strings.TrimPrefix(ref, "refs/heads/"))
+				default:
+					g.commit.Refs.Heads = append(g.commit.Refs.Heads, ref)
+				}
+			}
 		}
 	}
 	return g.commit
diff --git a/website/docs/segments/scm/git.mdx b/website/docs/segments/scm/git.mdx
index 2857950f..bfd6763d 100644
--- a/website/docs/segments/scm/git.mdx
+++ b/website/docs/segments/scm/git.mdx
@@ -37,7 +37,7 @@ import Config from "@site/src/components/Config.js";
       source: "cli",
       mapped_branches: {
         "feat/*": "🚀 ",
-				"bug/*":  "🐛 ",
+        "bug/*": "🐛 ",
       },
     },
   }}
@@ -175,6 +175,7 @@ Local changes use the following syntax:
 | `.Subject`   | `string`    | the commit subject                      |
 | `.Timestamp` | `time.Time` | the commit timestamp                    |
 | `.Sha`       | `string`    | the commit SHA1                         |
+| `.Refs`      | `Refs`      | the commit references                   |
 
 ### User
 
@@ -183,6 +184,20 @@ Local changes use the following syntax:
 | `.Name`  | `string` | the user's name  |
 | `.Email` | `string` | the user's email |
 
+### Refs
+
+| Name       | Type       | Description       |
+| ---------- | ---------- | ----------------- |
+| `.Heads`   | `[]string` | branches          |
+| `.Tags`    | `[]string` | commit's tags     |
+| `.Remotes` | `[]string` | remote references |
+
+As these are arrays of strings, you can join them using the `join` function:
+
+```template
+{{ join ", " .Commit.Refs.Tags }}
+```
+
 ## posh-git
 
 If you want to display the default [posh-git][poshgit] output, **do not** use this segment


Test patch:
diff --git a/src/segments/git_test.go b/src/segments/git_test.go
index c9b6e42b..a83c9640 100644
--- a/src/segments/git_test.go
+++ b/src/segments/git_test.go
@@ -990,6 +990,7 @@ func TestGitCommit(t *testing.T) {
 			at:1673176335
 			su:docs(error): you can't use cross segment properties
 			ha:1234567891011121314
+			rf:HEAD -> refs/heads/main, tag: refs/tags/tag-1, tag: refs/tags/0.3.4, refs/remotes/origin/main, refs/remotes/origin/dev, refs/heads/dev, refs/remotes/origin/HEAD
 			`,
 			Expected: &Commit{
 				Author: &User{
@@ -1002,7 +1003,12 @@ func TestGitCommit(t *testing.T) {
 				},
 				Subject:   "docs(error): you can't use cross segment properties",
 				Timestamp: time.Unix(1673176335, 0),
-				Sha:       "1234567891011121314",
+				Refs: &Refs{
+					Tags:    []string{"tag-1", "0.3.4"},
+					Heads:   []string{"main", "dev"},
+					Remotes: []string{"origin/main", "origin/dev"},
+				},
+				Sha: "1234567891011121314",
 			},
 		},
 		{
@@ -1010,6 +1016,7 @@ func TestGitCommit(t *testing.T) {
 			Expected: &Commit{
 				Author:    &User{},
 				Committer: &User{},
+				Refs:      &Refs{},
 			},
 		},
 		{
@@ -1030,6 +1037,44 @@ func TestGitCommit(t *testing.T) {
 				},
 				Subject:   "docs(error): you can't use cross segment properties",
 				Timestamp: time.Unix(1673176335, 0),
+				Refs:      &Refs{},
+			},
+		},
+		{
+			Case: "No refs",
+			Output: `
+			rf:HEAD
+			`,
+			Expected: &Commit{
+				Author:    &User{},
+				Committer: &User{},
+				Refs:      &Refs{},
+			},
+		},
+		{
+			Case: "Just tag ref",
+			Output: `
+			rf:HEAD, tag: refs/tags/tag-1
+			`,
+			Expected: &Commit{
+				Author:    &User{},
+				Committer: &User{},
+				Refs: &Refs{
+					Tags: []string{"tag-1"},
+				},
+			},
+		},
+		{
+			Case: "Feature branch including slash",
+			Output: `
+			rf:HEAD, tag: refs/tags/feat/feat-1
+			`,
+			Expected: &Commit{
+				Author:    &User{},
+				Committer: &User{},
+				Refs: &Refs{
+					Tags: []string{"feat/feat-1"},
+				},
 			},
 		},
 		{
@@ -1040,13 +1085,14 @@ func TestGitCommit(t *testing.T) {
 			Expected: &Commit{
 				Author:    &User{},
 				Committer: &User{},
+				Refs:      &Refs{},
 			},
 		},
 	}
 
 	for _, tc := range cases {
 		env := new(mock.Environment)
-		env.MockGitCommand("", tc.Output, "log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s%nha:%H")
+		env.MockGitCommand("", tc.Output, "log", "-1", "--pretty=format:an:%an%nae:%ae%ncn:%cn%nce:%ce%nat:%at%nsu:%s%nha:%H%nrf:%D", "--decorate=full")
 		g := &Git{
 			scm: scm{
 				env:     env,