loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add .gitattribute assisted language detection to blame, diff and render (#17590)

Use check attribute code to check the assigned language of a file and send that in to
chroma as a hint for the language of the file.

Signed-off-by: Andrew Thornton <art27@cantab.net>

authored by

zeripath and committed by
GitHub
3c4724d7 81a4fc75

+221 -95
+8
docs/content/doc/advanced/config-cheat-sheet.en-us.md
··· 982 982 To apply a sanitisation rules only for a specify external renderer they must use the renderer name, e.g. `[markup.sanitizer.asciidoc.rule-1]`. 983 983 If the rule is defined above the renderer ini section or the name does not match a renderer it is applied to every renderer. 984 984 985 + ## Highlight Mappings (`highlight.mapping`) 986 + 987 + - `file_extension e.g. .toml`: **language e.g. ini**. File extension to language mapping overrides. 988 + 989 + - Gitea will highlight files using the `linguist-language` or `gitlab-language` attribute from the `.gitattributes` file 990 + if available. If this is not set or the language is unavailable, the file extension will be looked up 991 + in this mapping or the filetype using heuristics. 992 + 985 993 ## Time (`time`) 986 994 987 995 - `FORMAT`: Time format to display on UI. i.e. RFC1123 or 2006-01-02 15:04:05
+16 -1
modules/git/repo_attribute.go
··· 22 22 AllAttributes bool 23 23 Attributes []string 24 24 Filenames []string 25 + IndexFile string 26 + WorkTree string 25 27 } 26 28 27 29 // CheckAttribute return the Blame object of file ··· 29 31 err := LoadGitVersion() 30 32 if err != nil { 31 33 return nil, fmt.Errorf("git version missing: %v", err) 34 + } 35 + 36 + env := []string{} 37 + 38 + if len(opts.IndexFile) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { 39 + env = append(env, "GIT_INDEX_FILE="+opts.IndexFile) 40 + } 41 + if len(opts.WorkTree) > 0 && CheckGitVersionAtLeast("1.7.8") == nil { 42 + env = append(env, "GIT_WORK_TREE="+opts.WorkTree) 43 + } 44 + 45 + if len(env) > 0 { 46 + env = append(os.Environ(), env...) 32 47 } 33 48 34 49 stdOut := new(bytes.Buffer) ··· 61 76 62 77 cmd := NewCommand(cmdArgs...) 63 78 64 - if err := cmd.RunInDirPipeline(repo.Path, stdOut, stdErr); err != nil { 79 + if err := cmd.RunInDirTimeoutEnvPipeline(env, -1, repo.Path, stdOut, stdErr); err != nil { 65 80 return nil, fmt.Errorf("failed to run check-attr: %v\n%s\n%s", err, stdOut.String(), stdErr.String()) 66 81 } 67 82
+7 -5
modules/git/repo_index.go
··· 8 8 "bytes" 9 9 "context" 10 10 "os" 11 + "path/filepath" 11 12 "strings" 12 13 13 14 "code.gitea.io/gitea/modules/log" ··· 45 46 } 46 47 47 48 // ReadTreeToTemporaryIndex reads a treeish to a temporary index file 48 - func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename string, cancel context.CancelFunc, err error) { 49 - tmpIndex, err := os.CreateTemp("", "index") 49 + func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (filename, tmpDir string, cancel context.CancelFunc, err error) { 50 + tmpDir, err = os.MkdirTemp("", "index") 50 51 if err != nil { 51 52 return 52 53 } 53 - filename = tmpIndex.Name() 54 + 55 + filename = filepath.Join(tmpDir, ".tmp-index") 54 56 cancel = func() { 55 - err := util.Remove(filename) 57 + err := util.RemoveAll(tmpDir) 56 58 if err != nil { 57 59 log.Error("failed to remove tmp index file: %v", err) 58 60 } ··· 60 62 err = repo.ReadTreeToIndex(treeish, filename) 61 63 if err != nil { 62 64 defer cancel() 63 - return "", func() {}, err 65 + return "", "", func() {}, err 64 66 } 65 67 return 66 68 }
+34 -27
modules/git/repo_language_stats_gogit.go
··· 11 11 "bytes" 12 12 "context" 13 13 "io" 14 - "os" 14 + "strings" 15 15 16 16 "code.gitea.io/gitea/modules/analyze" 17 17 "code.gitea.io/gitea/modules/log" 18 - "code.gitea.io/gitea/modules/util" 19 18 20 19 "github.com/go-enry/go-enry/v2" 21 20 "github.com/go-git/go-git/v5" ··· 48 47 var checker *CheckAttributeReader 49 48 50 49 if CheckGitVersionAtLeast("1.7.8") == nil { 51 - indexFilename, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) 50 + indexFilename, workTree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) 52 51 if err == nil { 53 52 defer deleteTemporaryFile() 54 - tmpWorkTree, err := os.MkdirTemp("", "empty-work-dir") 55 - if err == nil { 56 - defer func() { 57 - _ = util.RemoveAll(tmpWorkTree) 53 + checker = &CheckAttributeReader{ 54 + Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, 55 + Repo: repo, 56 + IndexFile: indexFilename, 57 + WorkTree: workTree, 58 + } 59 + ctx, cancel := context.WithCancel(DefaultContext) 60 + if err := checker.Init(ctx); err != nil { 61 + log.Error("Unable to open checker for %s. Error: %v", commitID, err) 62 + } else { 63 + go func() { 64 + err = checker.Run() 65 + if err != nil { 66 + log.Error("Unable to open checker for %s. Error: %v", commitID, err) 67 + cancel() 68 + } 58 69 }() 59 - 60 - checker = &CheckAttributeReader{ 61 - Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language"}, 62 - Repo: repo, 63 - IndexFile: indexFilename, 64 - WorkTree: tmpWorkTree, 65 - } 66 - ctx, cancel := context.WithCancel(DefaultContext) 67 - if err := checker.Init(ctx); err != nil { 68 - log.Error("Unable to open checker for %s. Error: %v", commitID, err) 69 - } else { 70 - go func() { 71 - err = checker.Run() 72 - if err != nil { 73 - log.Error("Unable to open checker for %s. Error: %v", commitID, err) 74 - cancel() 75 - } 76 - }() 77 - } 78 - defer cancel() 79 70 } 71 + defer cancel() 80 72 } 81 73 } 82 74 ··· 114 106 sizes[language] += f.Size 115 107 116 108 return nil 109 + } else if language, has := attrs["gitlab-language"]; has && language != "unspecified" && language != "" { 110 + // strip off a ? if present 111 + if idx := strings.IndexByte(language, '?'); idx >= 0 { 112 + language = language[:idx] 113 + } 114 + if len(language) != 0 { 115 + // group languages, such as Pug -> HTML; SCSS -> CSS 116 + group := enry.GetLanguageGroup(language) 117 + if len(group) != 0 { 118 + language = group 119 + } 120 + 121 + sizes[language] += f.Size 122 + return nil 123 + } 117 124 } 118 125 } 119 126 }
+35 -27
modules/git/repo_language_stats_nogogit.go
··· 13 13 "context" 14 14 "io" 15 15 "math" 16 - "os" 16 + "strings" 17 17 18 18 "code.gitea.io/gitea/modules/analyze" 19 19 "code.gitea.io/gitea/modules/log" 20 - "code.gitea.io/gitea/modules/util" 21 20 22 21 "github.com/go-enry/go-enry/v2" 23 22 ) ··· 68 67 var checker *CheckAttributeReader 69 68 70 69 if CheckGitVersionAtLeast("1.7.8") == nil { 71 - indexFilename, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) 70 + indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) 72 71 if err == nil { 73 72 defer deleteTemporaryFile() 74 - tmpWorkTree, err := os.MkdirTemp("", "empty-work-dir") 75 - if err == nil { 76 - defer func() { 77 - _ = util.RemoveAll(tmpWorkTree) 73 + checker = &CheckAttributeReader{ 74 + Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, 75 + Repo: repo, 76 + IndexFile: indexFilename, 77 + WorkTree: worktree, 78 + } 79 + ctx, cancel := context.WithCancel(DefaultContext) 80 + if err := checker.Init(ctx); err != nil { 81 + log.Error("Unable to open checker for %s. Error: %v", commitID, err) 82 + } else { 83 + go func() { 84 + err = checker.Run() 85 + if err != nil { 86 + log.Error("Unable to open checker for %s. Error: %v", commitID, err) 87 + cancel() 88 + } 78 89 }() 79 - 80 - checker = &CheckAttributeReader{ 81 - Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language"}, 82 - Repo: repo, 83 - IndexFile: indexFilename, 84 - WorkTree: tmpWorkTree, 85 - } 86 - ctx, cancel := context.WithCancel(DefaultContext) 87 - if err := checker.Init(ctx); err != nil { 88 - log.Error("Unable to open checker for %s. Error: %v", commitID, err) 89 - } else { 90 - go func() { 91 - err = checker.Run() 92 - if err != nil { 93 - log.Error("Unable to open checker for %s. Error: %v", commitID, err) 94 - cancel() 95 - } 96 - }() 97 - } 98 - defer cancel() 99 90 } 91 + defer cancel() 100 92 } 101 93 } 102 94 ··· 138 130 139 131 sizes[language] += f.Size() 140 132 continue 133 + } else if language, has := attrs["gitlab-language"]; has && language != "unspecified" && language != "" { 134 + // strip off a ? if present 135 + if idx := strings.IndexByte(language, '?'); idx >= 0 { 136 + language = language[:idx] 137 + } 138 + if len(language) != 0 { 139 + // group languages, such as Pug -> HTML; SCSS -> CSS 140 + group := enry.GetLanguageGroup(language) 141 + if len(group) != 0 { 142 + language = group 143 + } 144 + 145 + sizes[language] += f.Size() 146 + continue 147 + } 141 148 } 149 + 142 150 } 143 151 } 144 152
+29 -7
modules/highlight/highlight.go
··· 55 55 } 56 56 57 57 // Code returns a HTML version of code string with chroma syntax highlighting classes 58 - func Code(fileName, code string) string { 58 + func Code(fileName, language, code string) string { 59 59 NewContext() 60 60 61 61 // diff view newline will be passed as empty, change to literal \n so it can be copied ··· 69 69 } 70 70 71 71 var lexer chroma.Lexer 72 - if val, ok := highlightMapping[filepath.Ext(fileName)]; ok { 73 - //use mapped value to find lexer 74 - lexer = lexers.Get(val) 72 + 73 + if len(language) > 0 { 74 + lexer = lexers.Get(language) 75 + 76 + if lexer == nil { 77 + // Attempt stripping off the '?' 78 + if idx := strings.IndexByte(language, '?'); idx > 0 { 79 + lexer = lexers.Get(language[:idx]) 80 + } 81 + } 82 + } 83 + 84 + if lexer == nil { 85 + if val, ok := highlightMapping[filepath.Ext(fileName)]; ok { 86 + //use mapped value to find lexer 87 + lexer = lexers.Get(val) 88 + } 75 89 } 76 90 77 91 if lexer == nil { ··· 119 133 } 120 134 121 135 // File returns a slice of chroma syntax highlighted lines of code 122 - func File(numLines int, fileName string, code []byte) []string { 136 + func File(numLines int, fileName, language string, code []byte) []string { 123 137 NewContext() 124 138 125 139 if len(code) > sizeLimit { ··· 139 153 htmlw := bufio.NewWriter(&htmlbuf) 140 154 141 155 var lexer chroma.Lexer 142 - if val, ok := highlightMapping[filepath.Ext(fileName)]; ok { 143 - lexer = lexers.Get(val) 156 + 157 + // provided language overrides everything 158 + if len(language) > 0 { 159 + lexer = lexers.Get(language) 160 + } 161 + 162 + if lexer == nil { 163 + if val, ok := highlightMapping[filepath.Ext(fileName)]; ok { 164 + lexer = lexers.Get(val) 165 + } 144 166 } 145 167 146 168 if lexer == nil {
+1 -1
modules/highlight/highlight_test.go
··· 96 96 97 97 for _, tt := range tests { 98 98 t.Run(tt.name, func(t *testing.T) { 99 - if got := File(tt.numLines, tt.fileName, []byte(tt.code)); !reflect.DeepEqual(got, tt.want) { 99 + if got := File(tt.numLines, tt.fileName, "", []byte(tt.code)); !reflect.DeepEqual(got, tt.want) { 100 100 t.Errorf("File() = %v, want %v", got, tt.want) 101 101 } 102 102 })
+1 -1
modules/indexer/code/search.go
··· 101 101 Language: result.Language, 102 102 Color: result.Color, 103 103 LineNumbers: lineNumbers, 104 - FormattedLines: highlight.Code(result.Filename, formattedLinesBuffer.String()), 104 + FormattedLines: highlight.Code(result.Filename, "", formattedLinesBuffer.String()), 105 105 }, nil 106 106 } 107 107
+11 -2
modules/repofiles/diff_test.go
··· 9 9 10 10 "code.gitea.io/gitea/models" 11 11 "code.gitea.io/gitea/models/unittest" 12 + "code.gitea.io/gitea/modules/json" 12 13 "code.gitea.io/gitea/modules/test" 13 14 "code.gitea.io/gitea/services/gitdiff" 14 15 ··· 118 119 t.Run("with given branch", func(t *testing.T) { 119 120 diff, err := GetDiffPreview(ctx.Repo.Repository, branch, treePath, content) 120 121 assert.NoError(t, err) 121 - assert.EqualValues(t, expectedDiff, diff) 122 + expectedBs, err := json.Marshal(expectedDiff) 123 + assert.NoError(t, err) 124 + bs, err := json.Marshal(diff) 125 + assert.NoError(t, err) 126 + assert.EqualValues(t, expectedBs, bs) 122 127 }) 123 128 124 129 t.Run("empty branch, same results", func(t *testing.T) { 125 130 diff, err := GetDiffPreview(ctx.Repo.Repository, "", treePath, content) 126 131 assert.NoError(t, err) 127 - assert.EqualValues(t, expectedDiff, diff) 132 + expectedBs, err := json.Marshal(expectedDiff) 133 + assert.NoError(t, err) 134 + bs, err := json.Marshal(diff) 135 + assert.NoError(t, err) 136 + assert.EqualValues(t, expectedBs, bs) 128 137 }) 129 138 } 130 139
+27 -1
routers/web/repo/blame.go
··· 16 16 "code.gitea.io/gitea/modules/context" 17 17 "code.gitea.io/gitea/modules/git" 18 18 "code.gitea.io/gitea/modules/highlight" 19 + "code.gitea.io/gitea/modules/log" 19 20 "code.gitea.io/gitea/modules/templates" 20 21 "code.gitea.io/gitea/modules/timeutil" 21 22 "code.gitea.io/gitea/modules/util" ··· 204 205 func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*models.UserCommit, previousCommits map[string]string) { 205 206 repoLink := ctx.Repo.RepoLink 206 207 208 + language := "" 209 + 210 + indexFilename, worktree, deleteTemporaryFile, err := ctx.Repo.GitRepo.ReadTreeToTemporaryIndex(ctx.Repo.CommitID) 211 + if err == nil { 212 + defer deleteTemporaryFile() 213 + 214 + filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{ 215 + CachedOnly: true, 216 + Attributes: []string{"linguist-language", "gitlab-language"}, 217 + Filenames: []string{ctx.Repo.TreePath}, 218 + IndexFile: indexFilename, 219 + WorkTree: worktree, 220 + }) 221 + if err != nil { 222 + log.Error("Unable to load attributes for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err) 223 + } 224 + 225 + language = filename2attribute2info[ctx.Repo.TreePath]["linguist-language"] 226 + if language == "" || language == "unspecified" { 227 + language = filename2attribute2info[ctx.Repo.TreePath]["gitlab-language"] 228 + } 229 + if language == "unspecified" { 230 + language = "" 231 + } 232 + } 207 233 var lines = make([]string, 0) 208 234 rows := make([]*blameRow, 0) 209 235 ··· 248 274 line += "\n" 249 275 } 250 276 fileName := fmt.Sprintf("%v", ctx.Data["FileName"]) 251 - line = highlight.Code(fileName, line) 277 + line = highlight.Code(fileName, language, line) 252 278 253 279 br.Code = gotemplate.HTML(line) 254 280 rows = append(rows, br)
+27 -1
routers/web/repo/view.go
··· 502 502 lineNums := linesBytesCount(buf) 503 503 ctx.Data["NumLines"] = strconv.Itoa(lineNums) 504 504 ctx.Data["NumLinesSet"] = true 505 - ctx.Data["FileContent"] = highlight.File(lineNums, blob.Name(), buf) 505 + 506 + language := "" 507 + 508 + indexFilename, worktree, deleteTemporaryFile, err := ctx.Repo.GitRepo.ReadTreeToTemporaryIndex(ctx.Repo.CommitID) 509 + if err == nil { 510 + defer deleteTemporaryFile() 511 + 512 + filename2attribute2info, err := ctx.Repo.GitRepo.CheckAttribute(git.CheckAttributeOpts{ 513 + CachedOnly: true, 514 + Attributes: []string{"linguist-language", "gitlab-language"}, 515 + Filenames: []string{ctx.Repo.TreePath}, 516 + IndexFile: indexFilename, 517 + WorkTree: worktree, 518 + }) 519 + if err != nil { 520 + log.Error("Unable to load attributes for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err) 521 + } 522 + 523 + language = filename2attribute2info[ctx.Repo.TreePath]["linguist-language"] 524 + if language == "" || language == "unspecified" { 525 + language = filename2attribute2info[ctx.Repo.TreePath]["gitlab-language"] 526 + } 527 + if language == "unspecified" { 528 + language = "" 529 + } 530 + } 531 + ctx.Data["FileContent"] = highlight.File(lineNums, blob.Name(), language, buf) 506 532 } 507 533 if !isLFSFile { 508 534 if ctx.Repo.CanEnableEditor() {
+24 -21
services/gitdiff/gitdiff.go
··· 31 31 "code.gitea.io/gitea/modules/log" 32 32 "code.gitea.io/gitea/modules/process" 33 33 "code.gitea.io/gitea/modules/setting" 34 - "code.gitea.io/gitea/modules/util" 35 34 36 35 "github.com/sergi/go-diff/diffmatchpatch" 37 36 stdcharset "golang.org/x/net/html/charset" ··· 178 177 179 178 // DiffSection represents a section of a DiffFile. 180 179 type DiffSection struct { 180 + file *DiffFile 181 181 FileName string 182 182 Name string 183 183 Lines []*DiffLine ··· 546 546 diff2 string 547 547 ) 548 548 549 + language := "" 550 + if diffSection.file != nil { 551 + language = diffSection.file.Language 552 + } 553 + 549 554 // try to find equivalent diff line. ignore, otherwise 550 555 switch diffLine.Type { 551 556 case DiffLineSection: ··· 553 558 case DiffLineAdd: 554 559 compareDiffLine = diffSection.GetLine(DiffLineDel, diffLine.RightIdx) 555 560 if compareDiffLine == nil { 556 - return template.HTML(highlight.Code(diffSection.FileName, diffLine.Content[1:])) 561 + return template.HTML(highlight.Code(diffSection.FileName, language, diffLine.Content[1:])) 557 562 } 558 563 diff1 = compareDiffLine.Content 559 564 diff2 = diffLine.Content 560 565 case DiffLineDel: 561 566 compareDiffLine = diffSection.GetLine(DiffLineAdd, diffLine.LeftIdx) 562 567 if compareDiffLine == nil { 563 - return template.HTML(highlight.Code(diffSection.FileName, diffLine.Content[1:])) 568 + return template.HTML(highlight.Code(diffSection.FileName, language, diffLine.Content[1:])) 564 569 } 565 570 diff1 = diffLine.Content 566 571 diff2 = compareDiffLine.Content 567 572 default: 568 573 if strings.IndexByte(" +-", diffLine.Content[0]) > -1 { 569 - return template.HTML(highlight.Code(diffSection.FileName, diffLine.Content[1:])) 574 + return template.HTML(highlight.Code(diffSection.FileName, language, diffLine.Content[1:])) 570 575 } 571 - return template.HTML(highlight.Code(diffSection.FileName, diffLine.Content)) 576 + return template.HTML(highlight.Code(diffSection.FileName, language, diffLine.Content)) 572 577 } 573 578 574 - diffRecord := diffMatchPatch.DiffMain(highlight.Code(diffSection.FileName, diff1[1:]), highlight.Code(diffSection.FileName, diff2[1:]), true) 579 + diffRecord := diffMatchPatch.DiffMain(highlight.Code(diffSection.FileName, language, diff1[1:]), highlight.Code(diffSection.FileName, language, diff2[1:]), true) 575 580 diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord) 576 581 577 582 return diffToHTML(diffSection.FileName, diffRecord, diffLine.Type) ··· 597 602 IsProtected bool 598 603 IsGenerated bool 599 604 IsVendored bool 605 + Language string 600 606 } 601 607 602 608 // GetType returns type of diff file. ··· 1008 1014 line := sb.String() 1009 1015 1010 1016 // Create a new section to represent this hunk 1011 - curSection = &DiffSection{} 1017 + curSection = &DiffSection{file: curFile} 1012 1018 lastLeftIdx = -1 1013 1019 curFile.Sections = append(curFile.Sections, curSection) 1014 1020 ··· 1048 1054 rightLine++ 1049 1055 if curSection == nil { 1050 1056 // Create a new section to represent this hunk 1051 - curSection = &DiffSection{} 1057 + curSection = &DiffSection{file: curFile} 1052 1058 curFile.Sections = append(curFile.Sections, curSection) 1053 1059 lastLeftIdx = -1 1054 1060 } ··· 1074 1080 } 1075 1081 if curSection == nil { 1076 1082 // Create a new section to represent this hunk 1077 - curSection = &DiffSection{} 1083 + curSection = &DiffSection{file: curFile} 1078 1084 curFile.Sections = append(curFile.Sections, curSection) 1079 1085 lastLeftIdx = -1 1080 1086 } ··· 1094 1100 lastLeftIdx = -1 1095 1101 if curSection == nil { 1096 1102 // Create a new section to represent this hunk 1097 - curSection = &DiffSection{} 1103 + curSection = &DiffSection{file: curFile} 1098 1104 curFile.Sections = append(curFile.Sections, curSection) 1099 1105 } 1100 1106 curSection.Lines = append(curSection.Lines, diffLine) ··· 1302 1308 var checker *git.CheckAttributeReader 1303 1309 1304 1310 if git.CheckGitVersionAtLeast("1.7.8") == nil { 1305 - indexFilename, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(afterCommitID) 1311 + indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(afterCommitID) 1306 1312 if err == nil { 1307 1313 defer deleteTemporaryFile() 1308 - workdir, err := os.MkdirTemp("", "empty-work-dir") 1309 - if err != nil { 1310 - log.Error("Unable to create temporary directory: %v", err) 1311 - return nil, err 1312 - } 1313 - defer func() { 1314 - _ = util.RemoveAll(workdir) 1315 - }() 1316 1314 1317 1315 checker = &git.CheckAttributeReader{ 1318 - Attributes: []string{"linguist-vendored", "linguist-generated"}, 1316 + Attributes: []string{"linguist-vendored", "linguist-generated", "linguist-language", "gitlab-language"}, 1319 1317 Repo: gitRepo, 1320 1318 IndexFile: indexFilename, 1321 - WorkTree: workdir, 1319 + WorkTree: worktree, 1322 1320 } 1323 1321 ctx, cancel := context.WithCancel(git.DefaultContext) 1324 1322 if err := checker.Init(ctx); err != nil { ··· 1360 1358 } else { 1361 1359 gotGenerated = generated == "false" 1362 1360 } 1361 + } 1362 + if language, has := attrs["linguist-language"]; has && language != "unspecified" && language != "" { 1363 + diffFile.Language = language 1364 + } else if language, has := attrs["gitlab-language"]; has && language != "unspecified" && language != "" { 1365 + diffFile.Language = language 1363 1366 } 1364 1367 } else { 1365 1368 log.Error("Unexpected error: %v", err)
+1 -1
services/gitdiff/gitdiff_test.go
··· 533 533 534 534 func TestDiffToHTML_14231(t *testing.T) { 535 535 setting.Cfg = ini.Empty() 536 - diffRecord := diffMatchPatch.DiffMain(highlight.Code("main.v", " run()\n"), highlight.Code("main.v", " run(db)\n"), true) 536 + diffRecord := diffMatchPatch.DiffMain(highlight.Code("main.v", "", " run()\n"), highlight.Code("main.v", "", " run(db)\n"), true) 537 537 diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord) 538 538 539 539 expected := ` <span class="n">run</span><span class="added-code"><span class="o">(</span><span class="n">db</span></span><span class="o">)</span>`