Skip to content

Commit 4799444

Browse files
committed
Modify Diff View FileTree to show all files
== Changes * removes Show Status button on diff * uses `git diff-tree` to generate the file tree for the diff
1 parent a6613d9 commit 4799444

File tree

11 files changed

+146
-154
lines changed

11 files changed

+146
-154
lines changed

routers/web/repo/pull.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,14 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi
812812
}
813813
}
814814

815+
// note: use mergeBase is set to false because we already have the merge base from the pull request info
816+
diffTree, err := gitdiff.GetDiffTree(ctx, gitRepo, false, pull.MergeBase, headCommitID)
817+
if err != nil {
818+
ctx.ServerError("GetDiffTree", err)
819+
return
820+
}
821+
822+
ctx.Data["DiffFiles"] = transformDiffTreeForUI(diffTree, nil)
815823
ctx.Data["Diff"] = diff
816824
ctx.Data["DiffNotAvailable"] = diff.NumFiles == 0
817825

routers/web/repo/treelist.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ package repo
66
import (
77
"net/http"
88

9+
pull_model "code.gitea.io/gitea/models/pull"
910
"code.gitea.io/gitea/modules/base"
1011
"code.gitea.io/gitea/modules/git"
1112
"code.gitea.io/gitea/services/context"
13+
"code.gitea.io/gitea/services/gitdiff"
1214

1315
"github.com/go-enry/go-enry/v2"
1416
)
@@ -52,3 +54,40 @@ func isExcludedEntry(entry *git.TreeEntry) bool {
5254

5355
return false
5456
}
57+
58+
type FileDiffFile struct {
59+
Name string
60+
NameHash string
61+
IsSubmodule bool
62+
IsBinary bool
63+
IsViewed bool
64+
Status string
65+
}
66+
67+
// transformDiffTreeForUI transforms a DiffTree into a slice of FileDiffFile for UI rendering
68+
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
69+
func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) []FileDiffFile {
70+
files := make([]FileDiffFile, 0, len(diffTree.Files))
71+
72+
for _, file := range diffTree.Files {
73+
nameHash := git.HashFilePathForWebUI(file.HeadPath)
74+
isSubmodule := file.HeadMode == git.EntryModeCommit
75+
isBinary := file.HeadMode == git.EntryModeExec
76+
77+
isViewed := false
78+
if fileViewedState, ok := filesViewedState[file.Path()]; ok {
79+
isViewed = (fileViewedState == pull_model.Viewed)
80+
}
81+
82+
files = append(files, FileDiffFile{
83+
Name: file.HeadPath,
84+
NameHash: nameHash,
85+
IsSubmodule: isSubmodule,
86+
IsBinary: isBinary,
87+
IsViewed: isViewed,
88+
Status: file.Status,
89+
})
90+
}
91+
92+
return files
93+
}

services/gitdiff/git_diff_tree.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ type DiffTreeRecord struct {
3434
BaseBlobID string
3535
}
3636

37+
func (d *DiffTreeRecord) Path() string {
38+
if d.HeadPath != "" {
39+
return d.HeadPath
40+
}
41+
return d.BasePath
42+
}
43+
3744
// GetDiffTree returns the list of path of the files that have changed between the two commits.
3845
// If useMergeBase is true, the diff will be calculated using the merge base of the two commits.
3946
// This is the same behavior as using a three-dot diff in git diff.

templates/repo/diff/box.tmpl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
</div>
5959
{{end}}
6060
<script id="diff-data-script" type="module">
61-
const diffDataFiles = [{{range $i, $file := .Diff.Files}}{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Type:{{$file.Type}},IsBin:{{$file.IsBin}},IsSubmodule:{{$file.IsSubmodule}},Addition:{{$file.Addition}},Deletion:{{$file.Deletion}},IsViewed:{{$file.IsViewed}}},{{end}}];
61+
const diffDataFiles = [{{range $i, $file := .DiffFiles}}
62+
{Name:"{{$file.Name}}",NameHash:"{{$file.NameHash}}",Status:{{$file.Status}},IsSubmodule:{{$file.IsSubmodule}},IsViewed:{{$file.IsViewed}}},{{end}}];
6263
const diffData = {
6364
isIncomplete: {{.Diff.IsIncomplete}},
6465
tooManyFilesMessage: "{{ctx.Locale.Tr "repo.diff.too_many_files"}}",

templates/repo/diff/options_dropdown.tmpl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<div class="ui dropdown tiny basic button" data-tooltip-content="{{ctx.Locale.Tr "repo.diff.options_button"}}">
22
{{svg "octicon-kebab-horizontal"}}
33
<div class="menu">
4-
<a class="item" id="show-file-list-btn">{{ctx.Locale.Tr "repo.diff.show_diff_stats"}}</a>
54
{{if .Issue.Index}}
65
<a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.patch" download="{{.Issue.Index}}.patch">{{ctx.Locale.Tr "repo.diff.download_patch"}}</a>
76
<a class="item" href="{{$.RepoLink}}/pulls/{{.Issue.Index}}.diff" download="{{.Issue.Index}}.diff">{{ctx.Locale.Tr "repo.diff.download_diff"}}</a>

web_src/js/components/DiffFileList.vue

Lines changed: 0 additions & 60 deletions
This file was deleted.

web_src/js/components/DiffFileTree.vue

Lines changed: 3 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -5,71 +5,15 @@ import {toggleElem} from '../utils/dom.ts';
55
import {diffTreeStore} from '../modules/stores.ts';
66
import {setFileFolding} from '../features/file-fold.ts';
77
import {computed, onMounted, onUnmounted} from 'vue';
8+
import { pathListToTree, mergeChildIfOnlyOneDir} from './file_tree.ts';
89
910
const LOCAL_STORAGE_KEY = 'diff_file_tree_visible';
1011
1112
const store = diffTreeStore();
1213
1314
const fileTree = computed(() => {
14-
const result: Array<Item> = [];
15-
for (const file of store.files) {
16-
// Split file into directories
17-
const splits = file.Name.split('/');
18-
let index = 0;
19-
let parent = null;
20-
let isFile = false;
21-
for (const split of splits) {
22-
index += 1;
23-
// reached the end
24-
if (index === splits.length) {
25-
isFile = true;
26-
}
27-
let newParent: Item = {
28-
name: split,
29-
children: [],
30-
isFile,
31-
};
32-
33-
if (isFile === true) {
34-
newParent.file = file;
35-
}
36-
37-
if (parent) {
38-
// check if the folder already exists
39-
const existingFolder = parent.children.find(
40-
(x) => x.name === split,
41-
);
42-
if (existingFolder) {
43-
newParent = existingFolder;
44-
} else {
45-
parent.children.push(newParent);
46-
}
47-
} else {
48-
const existingFolder = result.find((x) => x.name === split);
49-
if (existingFolder) {
50-
newParent = existingFolder;
51-
} else {
52-
result.push(newParent);
53-
}
54-
}
55-
parent = newParent;
56-
}
57-
}
58-
const mergeChildIfOnlyOneDir = (entries: Array<Record<string, any>>) => {
59-
for (const entry of entries) {
60-
if (entry.children) {
61-
mergeChildIfOnlyOneDir(entry.children);
62-
}
63-
if (entry.children.length === 1 && entry.children[0].isFile === false) {
64-
// Merge it to the parent
65-
entry.name = `${entry.name}/${entry.children[0].name}`;
66-
entry.children = entry.children[0].children;
67-
}
68-
}
69-
};
70-
// Merge folders with just a folder as children in order to
71-
// reduce the depth of our tree.
72-
mergeChildIfOnlyOneDir(result);
15+
let result = pathListToTree(store.files);
16+
mergeChildIfOnlyOneDir(result); // mutation
7317
return result;
7418
});
7519

web_src/js/components/DiffFileTreeItem.vue

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,8 @@
22
import {SvgIcon, type SvgName} from '../svg.ts';
33
import {diffTreeStore} from '../modules/stores.ts';
44
import {ref} from 'vue';
5+
import { type Item, type File, type FileStatus } from './file_tree.ts';
56
6-
type File = {
7-
Name: string;
8-
NameHash: string;
9-
Type: number;
10-
IsViewed: boolean;
11-
IsSubmodule: boolean;
12-
}
13-
14-
export type Item = {
15-
name: string;
16-
isFile: boolean;
17-
file?: File;
18-
children?: Item[];
19-
};
207
218
defineProps<{
229
item: Item,
@@ -25,15 +12,15 @@ defineProps<{
2512
const store = diffTreeStore();
2613
const collapsed = ref(false);
2714
28-
function getIconForDiffType(pType: number) {
29-
const diffTypes: Record<string, {name: SvgName, classes: Array<string>}> = {
30-
'1': {name: 'octicon-diff-added', classes: ['text', 'green']},
31-
'2': {name: 'octicon-diff-modified', classes: ['text', 'yellow']},
32-
'3': {name: 'octicon-diff-removed', classes: ['text', 'red']},
33-
'4': {name: 'octicon-diff-renamed', classes: ['text', 'teal']},
34-
'5': {name: 'octicon-diff-renamed', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok
15+
function getIconForDiffType(pType: FileStatus) {
16+
const diffTypes: Record<FileStatus, {name: SvgName, classes: Array<string>}> = {
17+
'added': {name: 'octicon-diff-added', classes: ['text', 'green']},
18+
'modified': {name: 'octicon-diff-modified', classes: ['text', 'yellow']},
19+
'deleted': {name: 'octicon-diff-removed', classes: ['text', 'red']},
20+
'renamed': {name: 'octicon-diff-renamed', classes: ['text', 'teal']},
21+
'typechange': {name: 'octicon-diff-modified', classes: ['text', 'green']}, // there is no octicon for copied, so renamed should be ok
3522
};
36-
return diffTypes[String(pType)];
23+
return diffTypes[pType];
3724
}
3825
3926
function fileIcon(file: File) {
@@ -54,7 +41,7 @@ function fileIcon(file: File) {
5441
<!-- file -->
5542
<SvgIcon :name="fileIcon(item.file)"/>
5643
<span class="gt-ellipsis tw-flex-1">{{ item.name }}</span>
57-
<SvgIcon :name="getIconForDiffType(item.file.Type).name" :class="getIconForDiffType(item.file.Type).classes"/>
44+
<SvgIcon :name="getIconForDiffType(item.file.Status).name" :class="getIconForDiffType(item.file.Status).classes"/>
5845
</a>
5946
<div v-else class="item-directory" :title="item.name" @click.stop="collapsed = !collapsed">
6047
<!-- directory -->

web_src/js/components/file_tree.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
export type File = {
2+
Name: string;
3+
NameHash: string;
4+
Status: FileStatus;
5+
IsViewed: boolean;
6+
IsSubmodule: boolean;
7+
}
8+
export type FileStatus = "added" | "modified" | "deleted" | "renamed" | "typechange";
9+
10+
export type Item = {
11+
name: string;
12+
path: string;
13+
isFile: boolean;
14+
file?: File;
15+
children?: Item[];
16+
};
17+
18+
export function pathListToTree(fileEntries: File[]): Item[] {
19+
const pathToItem = new Map<string, Item>();
20+
21+
// init root node
22+
const root: Item = { name: '', path: '', isFile: false, children: [] };
23+
pathToItem.set('', root);
24+
25+
for (const fileEntry of fileEntries) {
26+
const [parentPath, fileName] = splitPathLast(fileEntry.Name);
27+
28+
let parentItem = pathToItem.get(parentPath);
29+
if (!parentItem) {
30+
parentItem = constructParents(pathToItem, parentPath);
31+
}
32+
33+
const fileItem: Item = { name: fileName, path: fileEntry.Name, isFile: true, file: fileEntry };
34+
35+
parentItem.children.push(fileItem);
36+
}
37+
38+
return root.children;
39+
}
40+
41+
42+
function constructParents(pathToItem: Map<string, Item>, dirPath: string): Item {
43+
const [dirParentPath, dirName] = splitPathLast(dirPath);
44+
45+
let parentItem = pathToItem.get(dirParentPath);
46+
if (!parentItem) {
47+
// if the parent node does not exist, create it
48+
parentItem = constructParents(pathToItem, dirParentPath);
49+
}
50+
51+
const dirItem: Item = { name: dirName, path: dirPath, isFile: false, children: [] };
52+
parentItem.children.push(dirItem);
53+
pathToItem.set(dirPath, dirItem);
54+
55+
return dirItem;
56+
}
57+
58+
function splitPathLast(path: string): [string, string] {
59+
const lastSlash = path.lastIndexOf('/');
60+
return [path.substring(0, lastSlash), path.substring(lastSlash + 1)];
61+
}
62+
63+
export function mergeChildIfOnlyOneDir(nodes: Item[]): void {
64+
for (const node of nodes) {
65+
if (node.children) {
66+
mergeChildIfOnlyOneDir(node.children);
67+
}
68+
69+
if (node.children?.length === 1 && node.children[0].children) {
70+
const child = node.children[0];
71+
node.name = `${node.name}/${child.name}`;
72+
node.path = child.path;
73+
node.children = child.children;
74+
}
75+
}
76+
}
Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {createApp} from 'vue';
22
import DiffFileTree from '../components/DiffFileTree.vue';
3-
import DiffFileList from '../components/DiffFileList.vue';
43

54
export function initDiffFileTree() {
65
const el = document.querySelector('#diff-file-tree');
@@ -10,10 +9,3 @@ export function initDiffFileTree() {
109
fileTreeView.mount(el);
1110
}
1211

13-
export function initDiffFileList() {
14-
const fileListElement = document.querySelector('#diff-file-list');
15-
if (!fileListElement) return;
16-
17-
const fileListView = createApp(DiffFileList);
18-
fileListView.mount(fileListElement);
19-
}

0 commit comments

Comments
 (0)