From c8df67c0d06bc0d0fa58c4fc71a14869b3a32588 Mon Sep 17 00:00:00 2001 From: Elisei Roca Date: Wed, 22 Jul 2026 17:56:29 +0200 Subject: [PATCH] fix(pulls): respect diff.orderFile in diff file tree (#38566) Co-authored-by: wxiaoguang --- modules/git/config.go | 16 ++++++++++- modules/git/git.go | 9 +++++- services/gitdiff/git_diff_tree.go | 5 ++++ services/gitdiff/git_diff_tree_test.go | 40 ++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/modules/git/config.go b/modules/git/config.go index b7f41251f8..6e07aea0c0 100644 --- a/modules/git/config.go +++ b/modules/git/config.go @@ -91,7 +91,7 @@ func syncGitConfig(ctx context.Context) (err error) { } } - // By default partial clones are disabled, enable them from git v2.22 + // By default, partial clones are disabled, enable them from git v2.22 if !setting.Git.DisablePartialClone && DefaultFeatures().CheckVersionAtLeast("2.22") { if err = configSet(ctx, "uploadpack.allowfilter", "true"); err != nil { return err @@ -114,9 +114,23 @@ func syncGitConfig(ctx context.Context) (err error) { } } + GlobalConfig = &GlobalConfigStruct{} + // HINT: GIT-DIFF-TREE-UI-CONFIG: Git's bug: git-diff-tree loads config with /* no "diff" UI options */ (since 20 years ago). + // https://github.com/git/git/blame/5d2e7709234afea1b6ddb25cd4f60d3d5fb3c200/builtin/diff-tree.c#L127 + // Although document and manual say that "git-diff-tree" supports "diff.orderfile" option, but it is not actually supported. + // So we need to apply the diff.orderfile explicitly in our code. + GlobalConfig.DiffOrderFile, _ = configGet(ctx, "diff.orderfile") return nil } +func configGet(ctx context.Context, key string) (string, error) { + stdout, _, err := gitcmd.NewCommand("config", "--global", "--get").AddDynamicArguments(key).RunStdString(ctx) + if err != nil && !gitcmd.IsErrorExitCode(err, 1) { + return "", fmt.Errorf("failed to get git config %s, err: %w", key, err) + } + return strings.TrimRight(stdout, "\r\n"), nil +} + func configSet(ctx context.Context, key, value string) error { stdout, _, err := gitcmd.NewCommand("config", "--global", "--get"). AddDynamicArguments(key). diff --git a/modules/git/git.go b/modules/git/git.go index 70164c2699..09d87c55fd 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -37,7 +37,14 @@ type Features struct { SupportGitMergeTree bool // >= 2.40 // we also need "--merge-base" } -var defaultFeatures *Features +type GlobalConfigStruct struct { + DiffOrderFile string +} + +var ( + defaultFeatures *Features + GlobalConfig *GlobalConfigStruct +) func (f *Features) CheckVersionAtLeast(atLeast string) bool { return f.gitVersion.Compare(version.Must(version.NewVersion(atLeast))) >= 0 diff --git a/services/gitdiff/git_diff_tree.go b/services/gitdiff/git_diff_tree.go index 015ecc66fc..7708be2068 100644 --- a/services/gitdiff/git_diff_tree.go +++ b/services/gitdiff/git_diff_tree.go @@ -60,6 +60,11 @@ func runGitDiffTree(ctx context.Context, gitRepo *git.Repository, useMergeBase b cmd := gitcmd.NewCommand("diff-tree", "--raw", "-r", "--root"). AddOptionFormat("--find-renames=%s", setting.Git.DiffRenameSimilarityThreshold) + // HINT: GIT-DIFF-TREE-UI-CONFIG: apply the diff.orderfile explicitly + if git.GlobalConfig.DiffOrderFile != "" { + cmd.AddOptionFormat("-O%s", git.GlobalConfig.DiffOrderFile) + } + if useMergeBase { cmd.AddArguments("--merge-base") } diff --git a/services/gitdiff/git_diff_tree_test.go b/services/gitdiff/git_diff_tree_test.go index da55224dfc..91b4c36a52 100644 --- a/services/gitdiff/git_diff_tree_test.go +++ b/services/gitdiff/git_diff_tree_test.go @@ -4,10 +4,13 @@ package gitdiff import ( + "os" + "path/filepath" "strings" "testing" "gitea.dev/modules/git" + "gitea.dev/modules/git/gitcmd" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -217,6 +220,43 @@ func TestGitDiffTree(t *testing.T) { } } +func TestGitDiffTreeRespectsDiffOrderFile(t *testing.T) { + gitRepo, err := git.OpenRepositoryLocal("../../modules/git/tests/repos/repo5_pulls") + require.NoError(t, err) + defer gitRepo.Close() + + testDiffTree := func(t *testing.T) (filePaths []string) { + t.Helper() + diffTree, err := GetDiffTree(t.Context(), gitRepo, false, "72866af952e98d02a73003501836074b286a78f6", "d8e0bbb45f200e67d9a784ce55bd90821af45ebd") + require.NoError(t, err) + for _, f := range diffTree.Files { + filePaths = append(filePaths, f.HeadPath) + } + return filePaths + } + + t.Run("NoDiffOrderFile", func(t *testing.T) { + assert.Equal(t, []string{"LICENSE", "README.md"}, testDiffTree(t)) + }) + + t.Run("GlobalDiffOrderFile", func(t *testing.T) { + diffOrderFilePath := filepath.Join(t.TempDir(), "test-diff-order.txt") + err = os.WriteFile(diffOrderFilePath, []byte("README.md\nLICENSE\n"), 0o644) + require.NoError(t, err) + + _, _, err = gitcmd.NewCommand("config", "set", "--global").AddDynamicArguments("diff.orderFile", diffOrderFilePath).RunStdString(t.Context()) + require.NoError(t, err) + require.NoError(t, git.InitFull()) + defer func() { + _, _, err = gitcmd.NewCommand("config", "unset", "--global").AddDynamicArguments("diff.orderFile").RunStdString(t.Context()) + require.NoError(t, err) + require.NoError(t, git.InitFull()) + }() + + assert.Equal(t, []string{"README.md", "LICENSE"}, testDiffTree(t)) + }) +} + func TestParseGitDiffTree(t *testing.T) { test := []struct { Name string