Repository: JanDeDobbeleer/oh-my-posh
Pull request #2084 (2022-04-13 11:00:04)

fix(git): detect upstream-gone status correctly
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [x] The commit message follows the [conventional commits][cc] guidelines
- [x] Tests for the changes have been added (for bug fixes/features)

### Description

I have a local Git repo that has ***no remote*** set for the current branch `master`, and I found some weird behaviors when I use the following `template` of a `git` tooltip in **PowerShell v7.2.2** or **zsh**:

```
"template": " [{{if not .UpstreamGone}}{{.UpstreamIcon}} {{end}}{{.HEAD}}]({{.UpstreamURL}})<#006400>{{.BranchStatus}}</>{{if .Working.Changed}}<#a0522d> \uf044 {{.Working.String}}</>{{end}}{{if .Staging.Changed}}<#006400> \uf046 {{.Staging.String}}</>{{end}} ",
```

#### In PowerShell

See the screenshot below:

![screenshot-bug-1-and-2](https://user-images.githubusercontent.com/83903009/163166053-7f30d5cc-b22c-4fbc-b242-0a4b7796f149.png)

##### 🐛 Bug 1

> The rendered tooltip has an unexpected **8** margin to the right end of the Terminal window. This doesn't appear when a remote is set, nor in a non-tooltip `git` segment.

After debugging, I found that it is caused by an empty remote URL in an ANSI sequence of hyperlink. When `(*color.Ansi).GenerateHyperlink` generates a hyperlink with an empty URL, it cannot be handled correctly in `(*color.Ansi).MeasureText`:

- Raw text before calling `(*color.Ansi).TrimAnsi`: (for convenience, use `\x1b` for the control character `ESC` and `\ue0a0` for the default `branch_icon`, similarly hereinafter)

	```
	 \x1b]8;;\x1b\ \ue0a0master\x1b]8;;\x1b\
	```

- Raw text after calling `(*color.Ansi).TrimAnsi`:

	```
	 ;;\x1b\ \ue0a0master;;\x1b\
	```

As we can see, two of the remaining buggy sequence `;;\x1b\` contribute exactly **8** extra characters in total! So finally, an incorrect length of effective text is passed to `(*color.Ansi).GetCursorForRightWrite`, which calculates an incorrect cursor position and then introduces an extra margin to the right end.

This not only appears in a `git` tooltip, but also in any that includes an underlying hyperlink with an empty URL.

~~My solution is to generate no hyperlink when an empty URL is detected in `(*color.Ansi).GenerateHyperlink`. It works well after the fix.~~ This fix is removed since the helper function `url` is a better choice to handle different cases.

##### 🐛 Bug 2

> A `branch_gone_icon` (default as `\u2262`) is missing. This should have been displayed after the branch name `master` when there's no remote branch.

Look back to ***Bug 1*** first, there is an space character before the `branch_icon` in the text, while this should not be displayed because my `template` disables `{{.UpstreamIcon}} ` when `.UpstreamGone` is `true`. After debugging, I found that it was actually `false` in this use case, so the space is shown after the empty `.UpstreamIcon`.

When I ran `git status -unormal --branch --porcelain=2` manually in my Git repo directory, it showed:

```
# branch.oid 6895350d4c41af3bce2c45dbfaa79a967fbb1c6d
# branch.head master
```

However, only when a line with a prefix of `# branch.upstream` is detected can `.UpstreamGone` be set to `true`.

I think we should assume that upstream is gone before we start to check any `git status` output line, and confirm its existence if a `"# branch.ab"` prefixed line is checked. It works well after the fix.

#### In Zsh

See the screenshot below:

![screenshot-bug-3](https://user-images.githubusercontent.com/83903009/163166061-c80479f7-655e-49be-b427-7c22ffe49193.png)

##### 🐛 Bug 3

> The rendered tooltip is broken and the cursor is reset to the beginning of the next line instead of the original position. Besides, two unexpected backslashes are interpolated in the text. This also appears when a remote is set, but not in a non-tooltip `git` segment.

Even after the two fixes above, the bug persists, so this is related to some zsh-specific logics.

Since I'm not so familiar with zsh, I cannot give an effective fix for the time being, and I don't know how this behaves at other users' ends.

#### About `\nnn` in Bash prompt escape sequences

BTW, I found that `\nnn` is included in the case `shell.BASH` of `(*color.Ansi).initEscapeSequences` method:

```go
{text: `\nnn`, replacement: `\\nnn`},
```

This is just a [placeholder of octal number](https://tldp.org/HOWTO/Bash-Prompt-HOWTO/bash-prompt-escape-sequences.html), but I haven't yet found a solution to handle specific cases properly.

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary


lewis-yeung: Seems that the unit test should be adjusted as well (`ExpectedUpstreamGone: true`) to match the `git` fix. Before a further change, is it just fine to make `.UpstreamGone` include the case where there is no remote?
JanDeDobbeleer: @lewis-yeung too much to read through while on holiday. A question though, what happens when you use the [helper function](https://ohmyposh.dev/docs/config-templates#helper-functions) to create the hyperlink? As the [text](url) format shouldn't be used directly.
lewis-yeung: @JanDeDobbeleer The helper function `url` works well.
JanDeDobbeleer: @lewis-yeung in that case I'd leave the fix for empty URL's out of that function. There's no need to go about hacking internal functionality 😉
lewis-yeung: The template I mentioned [above](https://github.com/JanDeDobbeleer/oh-my-posh/pull/2084#issuecomment-1097986856) handles an empty URL properly, with my fix for `setGitStatus`, though the text is still broken in zsh. I wonder if [this](https://user-images.githubusercontent.com/83903009/163166061-c80479f7-655e-49be-b427-7c22ffe49193.png) happens in zsh at your end.
JanDeDobbeleer: > I wonder if this happens in zsh at your end.

@lewis-yeung no, it does not.

Patch:
diff --git a/src/segments/git.go b/src/segments/git.go
index 02b328f7..431ff8cd 100644
--- a/src/segments/git.go
+++ b/src/segments/git.go
@@ -265,6 +265,8 @@ func (g *Git) setGitStatus() {
 		UPSTREAM     = "# branch.upstream "
 		BRANCHSTATUS = "# branch.ab "
 	)
+	// firstly assume that upstream is gone
+	g.UpstreamGone = true
 	g.Working = &GitStatus{}
 	g.Staging = &GitStatus{}
 	output := g.getGitCommandOutput("status", "-unormal", "--branch", "--porcelain=2")


Test patch:
diff --git a/src/segments/git_test.go b/src/segments/git_test.go
index fa384bd4..26bdd99e 100644
--- a/src/segments/git_test.go
+++ b/src/segments/git_test.go
@@ -374,10 +374,11 @@ func TestSetGitStatus(t *testing.T) {
 			1 .U N...
 			1 A. N...
 			`,
-			ExpectedWorking: &GitStatus{ScmStatus: ScmStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1}},
-			ExpectedStaging: &GitStatus{ScmStatus: ScmStatus{Added: 1}},
-			ExpectedHash:    "1234567",
-			ExpectedRef:     "rework-git-status",
+			ExpectedWorking:      &GitStatus{ScmStatus: ScmStatus{Modified: 4, Added: 2, Deleted: 1, Unmerged: 1}},
+			ExpectedStaging:      &GitStatus{ScmStatus: ScmStatus{Added: 1}},
+			ExpectedHash:         "1234567",
+			ExpectedRef:          "rework-git-status",
+			ExpectedUpstreamGone: true,
 		},
 		{
 			Case: "all different options on working and staging, with remote",