feat: show breadcrumb path in path filtered commit history view (#12116)

Resolves forgejo/forgejo#8754

Add the breadcrumb path that already exists when browsing directories to the commit history of files/directories.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12116
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Mauritz Sjödin 2026-05-01 01:53:10 +02:00 committed by Gusted
parent 75cfa31af5
commit 254a44b97b
7 changed files with 44 additions and 18 deletions

View file

@ -45,10 +45,19 @@ const (
func RefCommits(ctx *context.Context) { func RefCommits(ctx *context.Context) {
switch { switch {
case len(ctx.Repo.TreePath) == 0: case len(ctx.Repo.TreePath) == 0:
ctx.Data["TreeNames"] = []string{}
Commits(ctx) Commits(ctx)
case ctx.Repo.TreePath == "search": case ctx.Repo.TreePath == "search":
ctx.Data["TreeNames"] = []string{}
SearchCommits(ctx) SearchCommits(ctx)
default: default:
treeNames := strings.Split(ctx.Repo.TreePath, "/")
paths := make([]string, len(treeNames))
for i := range treeNames {
paths[i] = strings.Join(treeNames[:i+1], "/")
}
ctx.Data["TreeNames"] = treeNames
ctx.Data["Paths"] = paths
FileHistory(ctx) FileHistory(ctx)
} }
} }
@ -272,6 +281,9 @@ func FileHistory(ctx *context.Context) {
} }
} }
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["BranchLink"] = branchLink
ctx.Data["Commits"] = git_model.ParseCommitsWithStatus(ctx, commits, ctx.Repo.Repository) ctx.Data["Commits"] = git_model.ParseCommitsWithStatus(ctx, commits, ctx.Repo.Repository)
ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Username"] = ctx.Repo.Owner.Name

View file

@ -10,6 +10,7 @@
{{svg "octicon-git-branch"}} {{svg "octicon-git-branch"}}
{{ctx.Locale.Tr "repo.commit_graph"}} {{ctx.Locale.Tr "repo.commit_graph"}}
</a> </a>
{{template "repo/filepath" .}}
</div> </div>
</div> </div>
{{template "repo/commits_table" .}} {{template "repo/commits_table" .}}

View file

@ -0,0 +1,17 @@
{{$n := len .TreeNames}}
{{$l := Eval $n "-" 1}}
{{$showFilePath := (gt $n 0)}}
{{if $showFilePath}}
<span class="breadcrumb repo-path tw-ml-1">
<a class="section" href="{{$.BranchLink}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
{{- range $i, $v := .TreeNames -}}
<span class="breadcrumb-divider">/</span>
{{- if eq $i $l -}}
<span class="active section" title="{{$v}}">{{$v}}</span>
<button class="btn interact-fg tw-p-2" data-clipboard-text="{{$.TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
{{- else -}}
{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
{{- end -}}
{{- end -}}
</span>
{{end}}

View file

@ -102,20 +102,7 @@
{{ctx.Locale.Tr "repo.use_template"}} {{ctx.Locale.Tr "repo.use_template"}}
</a> </a>
{{end}} {{end}}
{{if (not $isHomepage)}} {{template "repo/filepath" .}}
<span class="breadcrumb repo-path tw-ml-1">
<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
{{- range $i, $v := .TreeNames -}}
<span class="breadcrumb-divider">/</span>
{{- if eq $i $l -}}
<span class="active section" title="{{$v}}">{{$v}}</span>
<button class="btn interact-fg tw-p-2" data-clipboard-text="{{$.TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
{{- else -}}
{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
{{- end -}}
{{- end -}}
</span>
{{end}}
</div> </div>
<div class="tw-flex tw-items-center max-[390px]:tw-w-full"> <div class="tw-flex tw-items-center max-[390px]:tw-w-full">
<!-- Only show clone panel in repository home page --> <!-- Only show clone panel in repository home page -->

View file

@ -24,6 +24,7 @@ func testRepoCommitsSearch(t *testing.T, query, commit string) {
doc := NewHTMLParser(t, resp.Body) doc := NewHTMLParser(t, resp.Body)
sel := doc.doc.Find("#commits-table tbody tr td.sha a") sel := doc.doc.Find("#commits-table tbody tr td.sha a")
assert.Equal(t, commit, strings.TrimSpace(sel.Text())) assert.Equal(t, commit, strings.TrimSpace(sel.Text()))
doc.AssertElement(t, ".repo-path", false)
} }
func TestRepoCommitsSearch(t *testing.T) { func TestRepoCommitsSearch(t *testing.T) {

View file

@ -34,6 +34,7 @@ func TestRepoCommits(t *testing.T) {
commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Attr("href") commitURL, exists := doc.doc.Find("#commits-table tbody tr td.sha a").Attr("href")
assert.True(t, exists) assert.True(t, exists)
assert.NotEmpty(t, commitURL) assert.NotEmpty(t, commitURL)
doc.AssertElement(t, ".repo-path", false)
} }
func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {

View file

@ -139,7 +139,7 @@ func TestCommitListActions(t *testing.T) {
[]*files_service.ChangeRepoFile{ []*files_service.ChangeRepoFile{
{ {
Operation: "create", Operation: "create",
TreePath: "test.sh", TreePath: "test/test.sh",
ContentReader: strings.NewReader("Hello there!"), ContentReader: strings.NewReader("Hello there!"),
}, },
}, },
@ -162,7 +162,7 @@ func TestCommitListActions(t *testing.T) {
htmlDoc.AssertElement(t, fmt.Sprintf(".commit-list a[href^='/%s/src/commit/']", repo.FullName()), false) htmlDoc.AssertElement(t, fmt.Sprintf(".commit-list a[href^='/%s/src/commit/']", repo.FullName()), false)
}) })
fileDiffSelector := fmt.Sprintf(".commit-list a[href='/%s/commit/%s?files=test.sh']", repo.FullName(), commitID) fileDiffSelector := fmt.Sprintf(".commit-list a[href='/%s/commit/%s?files=test/test.sh']", repo.FullName(), commitID)
t.Run("Commit list", func(t *testing.T) { t.Run("Commit list", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
@ -177,12 +177,19 @@ func TestCommitListActions(t *testing.T) {
t.Run("File history", func(t *testing.T) { t.Run("File history", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", repo.Link()+"/commits/branch/main/test.sh") req := NewRequest(t, "GET", repo.Link()+"/commits/branch/main/test/test.sh")
resp := session.MakeRequest(t, req, http.StatusOK) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
htmlDoc.AssertElement(t, fmt.Sprintf(".commit-list a[href='/%s/src/commit/%s/test.sh']", repo.FullName(), commitID), true) htmlDoc.AssertElement(t, fmt.Sprintf(".commit-list a[href='/%s/src/commit/%s/test/test.sh']", repo.FullName(), commitID), true)
htmlDoc.AssertElement(t, fileDiffSelector, true) htmlDoc.AssertElement(t, fileDiffSelector, true)
htmlDoc.AssertElement(t, ".repo-path", true)
htmlDoc.AssertElement(t, fmt.Sprintf(".repo-path a[href='/%s/src/branch/main'][title='%s']", repo.FullName(), repo.Name), true)
assert.Equal(t, 2, htmlDoc.Find(".repo-path .breadcrumb-divider").Length())
htmlDoc.AssertElement(t, fmt.Sprintf(".repo-path .section a[href='/%s/src/branch/main/test'][title='test']", repo.FullName()), true)
htmlDoc.AssertElement(t, ".repo-path .active[title='test.sh']", true)
htmlDoc.AssertElement(t, ".repo-path button[data-clipboard-text='test/test.sh']", true)
}) })
}) })
} }