From b5e46e2769f1b0b5eb2b250df95799129818950c Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 25 Jun 2021 15:09:20 +0000 Subject: [PATCH 1/2] Sync .gitattributes to repo.git/info/attributes. --- services/repository/attributes.go | 51 +++++++++++++++++++++++++++++++ services/repository/push.go | 6 ++++ 2 files changed, 57 insertions(+) create mode 100644 services/repository/attributes.go diff --git a/services/repository/attributes.go b/services/repository/attributes.go new file mode 100644 index 0000000000000..3c7f293a9db50 --- /dev/null +++ b/services/repository/attributes.go @@ -0,0 +1,51 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repository + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "code.gitea.io/gitea/modules/git" +) + +// SyncGitAttributes copies the content of the .gitattributes file from the default branch into repo.git/info/attributes. +func SyncGitAttributes(gitRepo *git.Repository, sourceBranch string) error { + commit, err := gitRepo.GetBranchCommit(sourceBranch) + if err != nil { + return err + } + + attributesBlob, err := commit.GetBlobByPath("/.gitattributes") + if err != nil { + if git.IsErrNotExist(err) { + return nil + } + return err + } + + infoPath := filepath.Join(gitRepo.Path, "info") + if err := os.MkdirAll(infoPath, 0700); err != nil { + return fmt.Errorf("Error creating directory [%s]: %v", infoPath, err) + } + attributesPath := filepath.Join(infoPath, "attributes") + + attributesFile, err := os.OpenFile(attributesPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("Error creating file [%s]: %v", attributesPath, err) + } + defer attributesFile.Close() + + blobReader, err := attributesBlob.DataAsync() + if err != nil { + return err + } + defer blobReader.Close() + + _, err = io.Copy(attributesFile, blobReader) + return err +} diff --git a/services/repository/push.go b/services/repository/push.go index f031073b2e0e0..3cad6e43f4602 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -207,6 +207,12 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) } + if branch == repo.DefaultBranch { + if err := SyncGitAttributes(gitRepo, repo.DefaultBranch); err != nil { + log.Error("SyncGitAttributes for %s failed: %v", repo.ID, err) + } + } + // Cache for big repository if err := repo_module.CacheRef(graceful.GetManager().HammerContext(), repo, gitRepo, opts.RefFullName); err != nil { log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err) From eacaa3d0c09b283f5dfbdec56c6241ddafc6399a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 25 Jun 2021 17:06:37 +0000 Subject: [PATCH 2/2] Exclude files from diff. --- modules/git/attributes.go | 100 +++++++++++++++++++++++++++ modules/git/attributes_test.go | 116 ++++++++++++++++++++++++++++++++ modules/git/repo.go | 17 +++++ options/locale/locale_en-US.ini | 1 + services/gitdiff/gitdiff.go | 15 +++++ templates/repo/diff/box.tmpl | 9 ++- 6 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 modules/git/attributes.go create mode 100644 modules/git/attributes_test.go diff --git a/modules/git/attributes.go b/modules/git/attributes.go new file mode 100644 index 0000000000000..b675c0a10e94d --- /dev/null +++ b/modules/git/attributes.go @@ -0,0 +1,100 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "bufio" + "io" + "path" + "regexp" + "strings" + + "github.com/gobwas/glob" +) + +type attributePattern struct { + pattern glob.Glob + attributes map[string]interface{} +} + +// Attributes represents all attributes from a .gitattribute file +type Attributes []attributePattern + +// ForFile returns the git attributes for the given path. +func (a Attributes) ForFile(filepath string) map[string]interface{} { + filepath = path.Join("/", filepath) + + for _, pattern := range a { + if pattern.pattern.Match(filepath) { + return pattern.attributes + } + } + + return map[string]interface{}{} +} + +var whitespaceSplit = regexp.MustCompile(`\s+`) + +// ParseAttributes parses git attributes from the provided reader. +func ParseAttributes(reader io.Reader) (Attributes, error) { + patterns := []attributePattern{} + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + + // Skip empty lines and comments + if len(line) == 0 || line[0] == '#' { + continue + } + + splitted := whitespaceSplit.Split(line, 2) + + pattern := path.Join("/", splitted[0]) + + attributes := map[string]interface{}{} + if len(splitted) == 2 { + attributes = parseAttributes(splitted[1]) + } + + if g, err := glob.Compile(pattern, '/'); err == nil { + patterns = append(patterns, attributePattern{ + g, + attributes, + }) + } + } + + for i, j := 0, len(patterns)-1; i < j; i, j = i+1, j-1 { + patterns[i], patterns[j] = patterns[j], patterns[i] + } + + return Attributes(patterns), scanner.Err() +} + +// parseAttributes parses an attribute string. Attributes can have the following formats: +// foo => foo = true +// -foo => foo = false +// foo=bar => foo = bar +func parseAttributes(attributes string) map[string]interface{} { + values := make(map[string]interface{}) + + for _, chunk := range whitespaceSplit.Split(attributes, -1) { + if chunk == "=" { // "foo = bar" is treated as "foo" and "bar" + continue + } + + if strings.HasPrefix(chunk, "-") { // "-foo" + values[chunk[1:]] = false + } else if strings.Contains(chunk, "=") { // "foo=bar" + splitted := strings.SplitN(chunk, "=", 2) + values[splitted[0]] = splitted[1] + } else { // "foo" + values[chunk] = true + } + } + + return values +} diff --git a/modules/git/attributes_test.go b/modules/git/attributes_test.go new file mode 100644 index 0000000000000..4c3f656308775 --- /dev/null +++ b/modules/git/attributes_test.go @@ -0,0 +1,116 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package git + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseAttributes(t *testing.T) { + attributes, err := ParseAttributes(strings.NewReader("* foo -bar foo2 = bar2 foobar=test")) + assert.NoError(t, err) + assert.Len(t, attributes, 1) + assert.Len(t, attributes[0].attributes, 5) + assert.Contains(t, attributes[0].attributes, "foo") + assert.True(t, attributes[0].attributes["foo"].(bool)) + assert.Contains(t, attributes[0].attributes, "bar") + assert.False(t, attributes[0].attributes["bar"].(bool)) + assert.Contains(t, attributes[0].attributes, "foo2") + assert.True(t, attributes[0].attributes["foo2"].(bool)) + assert.Contains(t, attributes[0].attributes, "bar2") + assert.True(t, attributes[0].attributes["bar2"].(bool)) + assert.Contains(t, attributes[0].attributes, "foobar") + assert.Equal(t, "test", attributes[0].attributes["foobar"].(string)) +} + +func TestForFile(t *testing.T) { + input := `* text=auto eol=lf +/vendor/** -text -eol linguist-vendored +/public/vendor/** -text -eol linguist-vendored +/templates/**/*.tmpl linguist-language=Handlebars +/.eslintrc linguist-language=YAML +/.stylelintrc linguist-language=YAML` + + attributes, err := ParseAttributes(strings.NewReader(input)) + assert.NoError(t, err) + assert.Len(t, attributes, 6) + + cases := []struct { + filepath string + expectedKey string + expectedValue interface{} + }{ + // case 0 + { + "test.txt", + "text", + "auto", + }, + // case 1 + { + "test.txt", + "eol", + "lf", + }, + // case 2 + { + "/vendor/test.txt", + "text", + false, + }, + // case 3 + { + "/vendor/test.txt", + "eol", + false, + }, + // case 4 + { + "vendor/test.txt", + "linguist-vendored", + true, + }, + // case 5 + { + "/vendor/dir/dir/dir/test.txt", + "linguist-vendored", + true, + }, + // case 6 + { + ".eslintrc", + "linguist-language", + "YAML", + }, + // case 7 + { + "/.eslintrc", + "linguist-language", + "YAML", + }, + } + + for n, c := range cases { + fa := attributes.ForFile(c.filepath) + assert.Contains(t, fa, c.expectedKey, "case %d", n) + assert.Equal(t, c.expectedValue, fa[c.expectedKey], "case %d", n) + } +} + +func TestForFileSecondWins(t *testing.T) { + input := `*.txt foo +*.txt bar` + + attributes, err := ParseAttributes(strings.NewReader(input)) + assert.NoError(t, err) + assert.Len(t, attributes, 2) + + fa := attributes.ForFile("test.txt") + assert.Contains(t, fa, "bar") + assert.NotContains(t, fa, "foo") +} diff --git a/modules/git/repo.go b/modules/git/repo.go index 43f329f4487d6..881e8bf7c0eb0 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -12,6 +12,7 @@ import ( "fmt" "os" "path" + "path/filepath" "strconv" "strings" "time" @@ -396,3 +397,19 @@ func GetDivergingCommits(repoPath string, baseBranch string, targetBranch string return DivergeObject{ahead, behind}, nil } + +// GetGitAttributes returns the parsed git attributes from repo.git/info/attributes or nil if not present. +func (repo *Repository) GetGitAttributes() (Attributes, error) { + attributesPath := filepath.Join(repo.Path, "info", "attributes") + + attributesFile, err := os.Open(attributesPath) + if err != nil { + if os.IsNotExist(err) { + return Attributes{}, nil + } + return nil, err + } + defer attributesFile.Close() + + return ParseAttributes(attributesFile) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 59ee6e48ea7f8..bfed3f2a69770 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1956,6 +1956,7 @@ diff.stats_desc = %d changed files with %d additions

@@ -111,6 +112,7 @@
{{if $showFileViewToggle}}
+ {{if $isRenderable}} {{if $isImage}} {{template "repo/diff/image_diff" dict "file" . "root" $ "blobBase" $blobBase "blobHead" $blobHead}} @@ -118,6 +120,11 @@ {{template "repo/diff/csv_diff" dict "file" . "root" $}} {{end}}
+ {{else}} +
+ {{$.i18n.Tr "repo.diff.generated_not_shown"}} +
+ {{end}}
{{end}}