diff options
author | KN4CK3R <admin@oldschoolhack.me> | 2023-09-16 19:42:34 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-16 19:42:34 +0200 |
commit | ed64f1c2b835bf9332bf8347be9675ef29c8274b (patch) | |
tree | d549801f35652496808d7307403f86e2f3f1ff39 /modules/git | |
parent | Add `RemoteAddress` to mirrors (#26952) (diff) | |
download | forgejo-ed64f1c2b835bf9332bf8347be9675ef29c8274b.tar.xz forgejo-ed64f1c2b835bf9332bf8347be9675ef29c8274b.zip |
Support `.git-blame-ignore-revs` file (#26395)
Closes #26329
This PR adds the ability to ignore revisions specified in the
`.git-blame-ignore-revs` file in the root of the repository.
![grafik](https://github.com/go-gitea/gitea/assets/1666336/9e91be0c-6e9c-431c-bbe9-5f80154251c8)
The banner is displayed in this case. I intentionally did not add a UI
way to bypass the ignore file (same behaviour as Github) but you can add
`?bypass-blame-ignore=true` to the url manually.
---------
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Diffstat (limited to 'modules/git')
14 files changed, 184 insertions, 30 deletions
diff --git a/modules/git/blame.go b/modules/git/blame.go index 4bd13dc32d..6728a6bed8 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -13,6 +13,7 @@ import ( "regexp" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) // BlamePart represents block of blame - continuous lines with one sha @@ -23,12 +24,16 @@ type BlamePart struct { // BlameReader returns part of file blame one by one type BlameReader struct { - cmd *Command output io.WriteCloser reader io.ReadCloser bufferedReader *bufio.Reader done chan error lastSha *string + ignoreRevsFile *string +} + +func (r *BlameReader) UsesIgnoreRevs() bool { + return r.ignoreRevsFile != nil } var shaLineRegex = regexp.MustCompile("^([a-z0-9]{40})") @@ -101,28 +106,44 @@ func (r *BlameReader) Close() error { r.bufferedReader = nil _ = r.reader.Close() _ = r.output.Close() + if r.ignoreRevsFile != nil { + _ = util.Remove(*r.ignoreRevsFile) + } return err } // CreateBlameReader creates reader for given repository, commit and file -func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*BlameReader, error) { - cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain"). - AddDynamicArguments(commitID). +func CreateBlameReader(ctx context.Context, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { + var ignoreRevsFile *string + if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { + ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) + } + + cmd := NewCommandContextNoGlobals(ctx, "blame", "--porcelain") + if ignoreRevsFile != nil { + // Possible improvement: use --ignore-revs-file /dev/stdin on unix + // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. + cmd.AddOptionValues("--ignore-revs-file", *ignoreRevsFile) + } + cmd.AddDynamicArguments(commit.ID.String()). AddDashesAndList(file). SetDescription(fmt.Sprintf("GetBlame [repo_path: %s]", repoPath)) reader, stdout, err := os.Pipe() if err != nil { + if ignoreRevsFile != nil { + _ = util.Remove(*ignoreRevsFile) + } return nil, err } done := make(chan error, 1) - go func(cmd *Command, dir string, stdout io.WriteCloser, done chan error) { + go func() { stderr := bytes.Buffer{} // TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close" err := cmd.Run(&RunOpts{ UseContextTimeout: true, - Dir: dir, + Dir: repoPath, Stdout: stdout, Stderr: &stderr, }) @@ -131,15 +152,42 @@ func CreateBlameReader(ctx context.Context, repoPath, commitID, file string) (*B if err != nil { log.Error("Error running git blame (dir: %v): %v, stderr: %v", repoPath, err, stderr.String()) } - }(cmd, repoPath, stdout, done) + }() bufferedReader := bufio.NewReader(reader) return &BlameReader{ - cmd: cmd, output: stdout, reader: reader, bufferedReader: bufferedReader, done: done, + ignoreRevsFile: ignoreRevsFile, }, nil } + +func tryCreateBlameIgnoreRevsFile(commit *Commit) *string { + entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs") + if err != nil { + return nil + } + + r, err := entry.Blob().DataAsync() + if err != nil { + return nil + } + defer r.Close() + + f, err := os.CreateTemp("", "gitea_git-blame-ignore-revs") + if err != nil { + return nil + } + + _, err = io.Copy(f, r) + _ = f.Close() + if err != nil { + _ = util.Remove(f.Name()) + return nil + } + + return util.ToPointer(f.Name()) +} diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 1c0cd5c4aa..013350ac2f 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -14,27 +14,127 @@ func TestReadingBlameOutput(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo5_pulls", "f32b0a9dfd09a60f616f29158f772cedd89942d2", "README.md") - assert.NoError(t, err) - defer blameReader.Close() - - parts := []*BlamePart{ - { - "72866af952e98d02a73003501836074b286a78f6", - []string{ - "# test_repo", - "Test repository for testing migration from github to gitea", - }, - }, - { - "f32b0a9dfd09a60f616f29158f772cedd89942d2", - []string{"", "Do not make any changes to this repo it is used for unit testing"}, - }, - } - - for _, part := range parts { - actualPart, err := blameReader.NextPart() + t.Run("Without .git-blame-ignore-revs", func(t *testing.T) { + repo, err := OpenRepository(ctx, "./tests/repos/repo5_pulls") assert.NoError(t, err) - assert.Equal(t, part, actualPart) - } + defer repo.Close() + + commit, err := repo.GetCommit("f32b0a9dfd09a60f616f29158f772cedd89942d2") + assert.NoError(t, err) + + parts := []*BlamePart{ + { + "72866af952e98d02a73003501836074b286a78f6", + []string{ + "# test_repo", + "Test repository for testing migration from github to gitea", + }, + }, + { + "f32b0a9dfd09a60f616f29158f772cedd89942d2", + []string{"", "Do not make any changes to this repo it is used for unit testing"}, + }, + } + + for _, bypass := range []bool{false, true} { + blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo5_pulls", commit, "README.md", bypass) + assert.NoError(t, err) + assert.NotNil(t, blameReader) + defer blameReader.Close() + + assert.False(t, blameReader.UsesIgnoreRevs()) + + for _, part := range parts { + actualPart, err := blameReader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part, actualPart) + } + + // make sure all parts have been read + actualPart, err := blameReader.NextPart() + assert.Nil(t, actualPart) + assert.NoError(t, err) + } + }) + + t.Run("With .git-blame-ignore-revs", func(t *testing.T) { + repo, err := OpenRepository(ctx, "./tests/repos/repo6_blame") + assert.NoError(t, err) + defer repo.Close() + + full := []*BlamePart{ + { + "af7486bd54cfc39eea97207ca666aa69c9d6df93", + []string{"line", "line"}, + }, + { + "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + []string{"changed line"}, + }, + { + "af7486bd54cfc39eea97207ca666aa69c9d6df93", + []string{"line", "line", ""}, + }, + } + + cases := []struct { + CommitID string + UsesIgnoreRevs bool + Bypass bool + Parts []*BlamePart + }{ + { + CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", + UsesIgnoreRevs: true, + Bypass: false, + Parts: []*BlamePart{ + { + "af7486bd54cfc39eea97207ca666aa69c9d6df93", + []string{"line", "line", "changed line", "line", "line", ""}, + }, + }, + }, + { + CommitID: "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", + UsesIgnoreRevs: false, + Bypass: true, + Parts: full, + }, + { + CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + UsesIgnoreRevs: false, + Bypass: false, + Parts: full, + }, + { + CommitID: "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", + UsesIgnoreRevs: false, + Bypass: false, + Parts: full, + }, + } + + for _, c := range cases { + commit, err := repo.GetCommit(c.CommitID) + assert.NoError(t, err) + + blameReader, err := CreateBlameReader(ctx, "./tests/repos/repo6_blame", commit, "blame.txt", c.Bypass) + assert.NoError(t, err) + assert.NotNil(t, blameReader) + defer blameReader.Close() + + assert.Equal(t, c.UsesIgnoreRevs, blameReader.UsesIgnoreRevs()) + + for _, part := range c.Parts { + actualPart, err := blameReader.NextPart() + assert.NoError(t, err) + assert.Equal(t, part, actualPart) + } + + // make sure all parts have been read + actualPart, err := blameReader.NextPart() + assert.Nil(t, actualPart) + assert.NoError(t, err) + } + }) } diff --git a/modules/git/tests/repos/repo6_blame/HEAD b/modules/git/tests/repos/repo6_blame/HEAD new file mode 100644 index 0000000000..cb089cd89a --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/modules/git/tests/repos/repo6_blame/config b/modules/git/tests/repos/repo6_blame/config new file mode 100644 index 0000000000..07d359d07c --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true diff --git a/modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643c b/modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643c Binary files differnew file mode 100644 index 0000000000..6cde9108e7 --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643c diff --git a/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1 b/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1 Binary files differnew file mode 100644 index 0000000000..b8db01dc35 --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1 diff --git a/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376 b/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376 Binary files differnew file mode 100644 index 0000000000..6c0ae4723f --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376 diff --git a/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9 b/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9 Binary files differnew file mode 100644 index 0000000000..5c2b5641cf --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9 diff --git a/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7 b/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7 Binary files differnew file mode 100644 index 0000000000..3c6471864c --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7 diff --git a/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044 b/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044 Binary files differnew file mode 100644 index 0000000000..847b7bc305 --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044 diff --git a/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93 b/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93 Binary files differnew file mode 100644 index 0000000000..206ef1efb7 --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93 diff --git a/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421 b/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421 Binary files differnew file mode 100644 index 0000000000..bb26889ed3 --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421 diff --git a/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8 b/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8 Binary files differnew file mode 100644 index 0000000000..1653ed9544 --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8 diff --git a/modules/git/tests/repos/repo6_blame/refs/heads/master b/modules/git/tests/repos/repo6_blame/refs/heads/master new file mode 100644 index 0000000000..01c9922c5f --- /dev/null +++ b/modules/git/tests/repos/repo6_blame/refs/heads/master @@ -0,0 +1 @@ +544d8f7a3b15927cddf2299b4b562d6ebd71b6a7 |