Repository: reviewdog/reviewdog
Pull request #1433 (2023-06-13 12:18:23)

feat: add ghe path link support
This PR adds support for a custom github server url via environment variable GITHUB_SERVER_URL. This variable is populated by default in GitHub actions with the current GitHub URL. With this approach we benefit from path link support on self hosted instances of GitHub (GitHub Enterprise), that currently is broken and pointing to `github.com`.

Tell me If you need me to update something. Thanks for the awesome project 💪 

- [x] Updated Unreleased section in [CHANGELOG](https://github.com/reviewdog/reviewdog/blob/master/CHANGELOG.md) or it's not notable changes.




Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9603a98b..a1ae83a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### :rocket: Enhancements
 - [#1170](https://github.com/reviewdog/reviewdog/pull/1170) Calculate check conclusion from annotations
+- [#1433](https://github.com/reviewdog/reviewdog/pull/1433) Add path link support for GitHub Enterprise
 - ...
 
 ### :bug: Fixes
diff --git a/service/github/githubutils/utils.go b/service/github/githubutils/utils.go
index 49a801c4..a6925823 100644
--- a/service/github/githubutils/utils.go
+++ b/service/github/githubutils/utils.go
@@ -2,6 +2,8 @@ package githubutils
 
 import (
 	"fmt"
+	"net/url"
+	"os"
 
 	"github.com/reviewdog/reviewdog/proto/rdf"
 )
@@ -16,12 +18,20 @@ func LinkedMarkdownDiagnostic(owner, repo, sha string, d *rdf.Diagnostic) string
 	}
 	loc := BasicLocationFormat(d)
 	line := int(d.GetLocation().GetRange().GetStart().GetLine())
-	link := PathLink(owner, repo, sha, path, line)
+	link, err := PathLink(owner, repo, sha, path, line)
+	if err != nil {
+		return fmt.Sprintf("%s %s", loc, msg)
+	}
 	return fmt.Sprintf("[%s](%s) %s", loc, link, msg)
 }
 
 // PathLink build a link to GitHub path to given sha, file, and line.
-func PathLink(owner, repo, sha, path string, line int) string {
+func PathLink(owner, repo, sha, path string, line int) (string, error) {
+	serverURL, err := githubServerURL()
+	if err != nil {
+		return "", err
+	}
+
 	if sha == "" {
 		sha = "master"
 	}
@@ -29,8 +39,11 @@ func PathLink(owner, repo, sha, path string, line int) string {
 	if line > 0 {
 		fragment = fmt.Sprintf("#L%d", line)
 	}
-	return fmt.Sprintf("http://github.com/%s/%s/blob/%s/%s%s",
-		owner, repo, sha, path, fragment)
+
+	result := fmt.Sprintf("%s/%s/%s/blob/%s/%s%s",
+		serverURL.String(), owner, repo, sha, path, fragment)
+
+	return result, nil
 }
 
 // BasicLocationFormat format a diagnostic to %f|%l col %c| errorformat.
@@ -47,3 +60,22 @@ func BasicLocationFormat(d *rdf.Diagnostic) string {
 	}
 	return out + "|"
 }
+
+const defaultGitHubServerURL = "https://github.com"
+
+func githubServerURL() (*url.URL, error) {
+	// get GitHub server URL from GitHub Actions' default environment variable GITHUB_SERVER_URL
+	// ref: https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables
+	if baseURL := os.Getenv("GITHUB_SERVER_URL"); baseURL != "" {
+		u, err := url.Parse(baseURL)
+		if err != nil {
+			return nil, fmt.Errorf("GitHub server URL from GITHUB_SERVER_URL is invalid: %v, %w", baseURL, err)
+		}
+		return u, nil
+	}
+	u, err := url.Parse(defaultGitHubServerURL)
+	if err != nil {
+		return nil, fmt.Errorf("GitHub server URL from reviewdog default is invalid: %v, %w", defaultGitHubServerURL, err)
+	}
+	return u, nil
+}


Test patch:
diff --git a/service/github/githubutils/utils_test.go b/service/github/githubutils/utils_test.go
index b839fbe7..b04cc305 100644
--- a/service/github/githubutils/utils_test.go
+++ b/service/github/githubutils/utils_test.go
@@ -1,6 +1,7 @@
 package githubutils
 
 import (
+	"os"
 	"testing"
 
 	"github.com/reviewdog/reviewdog/proto/rdf"
@@ -10,6 +11,7 @@ func TestLinkedMarkdownDiagnostic(t *testing.T) {
 	tests := []struct {
 		owner, repo, sha string
 		d                *rdf.Diagnostic
+		serverUrl	     string
 		want             string
 	}{
 		{
@@ -26,7 +28,8 @@ func TestLinkedMarkdownDiagnostic(t *testing.T) {
 				},
 				Message: "msg",
 			},
-			want: "[path/to/file.txt|1414 col 14|](http://github.com/o/r/blob/s/path/to/file.txt#L1414) msg",
+			serverUrl: "",
+			want: "[path/to/file.txt|1414 col 14|](https://github.com/o/r/blob/s/path/to/file.txt#L1414) msg",
 		},
 		{
 			owner: "o",
@@ -42,7 +45,8 @@ func TestLinkedMarkdownDiagnostic(t *testing.T) {
 				},
 				Message: "msg",
 			},
-			want: "[path/to/file.txt|1414|](http://github.com/o/r/blob/s/path/to/file.txt#L1414) msg",
+			serverUrl: "",
+			want: "[path/to/file.txt|1414|](https://github.com/o/r/blob/s/path/to/file.txt#L1414) msg",
 		},
 		{
 			owner: "o",
@@ -54,10 +58,32 @@ func TestLinkedMarkdownDiagnostic(t *testing.T) {
 				},
 				Message: "msg",
 			},
-			want: "[path/to/file.txt||](http://github.com/o/r/blob/s/path/to/file.txt) msg",
+			serverUrl: "",
+			want: "[path/to/file.txt||](https://github.com/o/r/blob/s/path/to/file.txt) msg",
+		},
+		{
+			owner: "o",
+			repo:  "r",
+			sha:   "s",
+			d: &rdf.Diagnostic{
+				Location: &rdf.Location{
+					Path: "path/to/file.txt",
+				},
+				Message: "msg",
+			},
+			serverUrl: "https://xpto.com",
+			want: "[path/to/file.txt||](https://xpto.com/o/r/blob/s/path/to/file.txt) msg",
 		},
 	}
 	for _, tt := range tests {
+		if errUnset := os.Unsetenv("GITHUB_SERVER_URL"); errUnset != nil {
+			t.Error(errUnset)
+		}
+		if tt.serverUrl != "" {
+			if errSet := os.Setenv("GITHUB_SERVER_URL", tt.serverUrl); errSet != nil {
+				t.Error(errSet)
+			}
+		}
 		if got := LinkedMarkdownDiagnostic(tt.owner, tt.repo, tt.sha, tt.d); got != tt.want {
 			t.Errorf("LinkedMarkdownDiagnostic(%q, %q, %q, %#v) = %q, want %q",
 				tt.owner, tt.repo, tt.sha, tt.d, got, tt.want)