Mark parent directory as viewed when all files are viewed (#33958)

Fix #25644 

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Kerwin Bryant
2025-04-15 22:35:22 +08:00
committed by GitHub
parent 18a673bad1
commit 2b99a58f54
15 changed files with 313 additions and 280 deletions

View File

@ -369,7 +369,7 @@ func Diff(ctx *context.Context) {
return
}
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil)
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
}
statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll)

View File

@ -639,7 +639,7 @@ func PrepareCompareDiff(
return false
}
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, nil)
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, nil)
}
headCommit, err := ci.HeadGitRepo.GetCommit(headCommitID)

View File

@ -759,12 +759,9 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
// have to load only the diff and not get the viewed information
// as the viewed information is designed to be loaded only on latest PR
// diff and if you're signed in.
shouldGetUserSpecificDiff := false
if !ctx.IsSigned || willShowSpecifiedCommit || willShowSpecifiedCommitRange {
// do nothing
} else {
shouldGetUserSpecificDiff = true
err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions, files...)
var reviewState *pull_model.ReviewState
if ctx.IsSigned && !willShowSpecifiedCommit && !willShowSpecifiedCommitRange {
reviewState, err = gitdiff.SyncUserSpecificDiff(ctx, ctx.Doer.ID, pull, gitRepo, diff, diffOptions)
if err != nil {
ctx.ServerError("SyncUserSpecificDiff", err)
return
@ -823,18 +820,11 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
ctx.ServerError("GetDiffTree", err)
return
}
filesViewedState := make(map[string]pull_model.ViewedState)
if shouldGetUserSpecificDiff {
// This sort of sucks because we already fetch this when getting the diff
review, err := pull_model.GetNewestReviewState(ctx, ctx.Doer.ID, issue.ID)
if err == nil && review != nil && review.UpdatedFiles != nil {
// If there wasn't an error and we have a review with updated files, use that
filesViewedState = review.UpdatedFiles
}
var filesViewedState map[string]pull_model.ViewedState
if reviewState != nil {
filesViewedState = reviewState.UpdatedFiles
}
ctx.PageData["DiffFiles"] = transformDiffTreeForUI(diffTree, filesViewedState)
ctx.PageData["DiffFileTree"] = transformDiffTreeForWeb(diffTree, filesViewedState)
}
ctx.Data["Diff"] = diff

View File

@ -5,6 +5,7 @@ package repo
import (
"net/http"
"strings"
pull_model "code.gitea.io/gitea/models/pull"
"code.gitea.io/gitea/modules/base"
@ -57,34 +58,85 @@ func isExcludedEntry(entry *git.TreeEntry) bool {
return false
}
type FileDiffFile struct {
Name string
// WebDiffFileItem is used by frontend, check the field names in frontend before changing
type WebDiffFileItem struct {
FullName string
DisplayName string
NameHash string
IsSubmodule bool
DiffStatus string
EntryMode string
IsViewed bool
Status string
Children []*WebDiffFileItem
// TODO: add icon support in the future
}
// transformDiffTreeForUI transforms a DiffTree into a slice of FileDiffFile for UI rendering
// WebDiffFileTree is used by frontend, check the field names in frontend before changing
type WebDiffFileTree struct {
TreeRoot WebDiffFileItem
}
// transformDiffTreeForWeb transforms a gitdiff.DiffTree into a WebDiffFileTree for Web UI rendering
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile {
files := make([]FileDiffFile, 0, len(diffTree.Files))
for _, file := range diffTree.Files {
nameHash := git.HashFilePathForWebUI(file.HeadPath)
isSubmodule := file.HeadMode == git.EntryModeCommit
isViewed := filesViewedState[file.HeadPath] == pull_model.Viewed
files = append(files, FileDiffFile{
Name: file.HeadPath,
NameHash: nameHash,
IsSubmodule: isSubmodule,
IsViewed: isViewed,
Status: file.Status,
})
func transformDiffTreeForWeb(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) (dft WebDiffFileTree) {
dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot}
addItem := func(item *WebDiffFileItem) {
var parentPath string
pos := strings.LastIndexByte(item.FullName, '/')
if pos == -1 {
item.DisplayName = item.FullName
} else {
parentPath = item.FullName[:pos]
item.DisplayName = item.FullName[pos+1:]
}
parentNode, parentExists := dirNodes[parentPath]
if !parentExists {
parentNode = &dft.TreeRoot
fields := strings.Split(parentPath, "/")
for idx, field := range fields {
nodePath := strings.Join(fields[:idx+1], "/")
node, ok := dirNodes[nodePath]
if !ok {
node = &WebDiffFileItem{EntryMode: "tree", DisplayName: field, FullName: nodePath}
dirNodes[nodePath] = node
parentNode.Children = append(parentNode.Children, node)
}
parentNode = node
}
}
parentNode.Children = append(parentNode.Children, item)
}
return files
for _, file := range diffTree.Files {
item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
item.NameHash = git.HashFilePathForWebUI(item.FullName)
switch file.HeadMode {
case git.EntryModeTree:
item.EntryMode = "tree"
case git.EntryModeCommit:
item.EntryMode = "commit" // submodule
default:
// default to empty, and will be treated as "blob" file because there is no "symlink" support yet
}
addItem(item)
}
var mergeSingleDir func(node *WebDiffFileItem)
mergeSingleDir = func(node *WebDiffFileItem) {
if len(node.Children) == 1 {
if child := node.Children[0]; child.EntryMode == "tree" {
node.FullName = child.FullName
node.DisplayName = node.DisplayName + "/" + child.DisplayName
node.Children = child.Children
mergeSingleDir(node)
}
}
}
for _, node := range dft.TreeRoot.Children {
mergeSingleDir(node)
}
return dft
}
func TreeViewNodes(ctx *context.Context) {

View File

@ -0,0 +1,60 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"testing"
pull_model "code.gitea.io/gitea/models/pull"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/services/gitdiff"
"github.com/stretchr/testify/assert"
)
func TestTransformDiffTreeForWeb(t *testing.T) {
ret := transformDiffTreeForWeb(&gitdiff.DiffTree{Files: []*gitdiff.DiffTreeRecord{
{
Status: "changed",
HeadPath: "dir-a/dir-a-x/file-deep",
HeadMode: git.EntryModeBlob,
},
{
Status: "added",
HeadPath: "file1",
HeadMode: git.EntryModeBlob,
},
}}, map[string]pull_model.ViewedState{
"dir-a/dir-a-x/file-deep": pull_model.Viewed,
})
assert.Equal(t, WebDiffFileTree{
TreeRoot: WebDiffFileItem{
Children: []*WebDiffFileItem{
{
EntryMode: "tree",
DisplayName: "dir-a/dir-a-x",
FullName: "dir-a/dir-a-x",
Children: []*WebDiffFileItem{
{
EntryMode: "",
DisplayName: "file-deep",
FullName: "dir-a/dir-a-x/file-deep",
NameHash: "4acf7eef1c943a09e9f754e93ff190db8583236b",
DiffStatus: "changed",
IsViewed: true,
},
},
},
{
EntryMode: "",
DisplayName: "file1",
FullName: "file1",
NameHash: "60b27f004e454aca81b0480209cce5081ec52390",
DiffStatus: "added",
},
},
},
}, ret)
}