summaryrefslogtreecommitdiffstats
path: root/modules/git
diff options
context:
space:
mode:
authorKN4CK3R <admin@oldschoolhack.me>2023-09-16 19:42:34 +0200
committerGitHub <noreply@github.com>2023-09-16 19:42:34 +0200
commited64f1c2b835bf9332bf8347be9675ef29c8274b (patch)
treed549801f35652496808d7307403f86e2f3f1ff39 /modules/git
parentAdd `RemoteAddress` to mirrors (#26952) (diff)
downloadforgejo-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')
-rw-r--r--modules/git/blame.go64
-rw-r--r--modules/git/blame_test.go144
-rw-r--r--modules/git/tests/repos/repo6_blame/HEAD1
-rw-r--r--modules/git/tests/repos/repo6_blame/config4
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643cbin0 -> 98 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1bin0 -> 35 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376bin0 -> 167 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9bin0 -> 24 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7bin0 -> 175 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044bin0 -> 57 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93bin0 -> 134 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421bin0 -> 54 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8bin0 -> 54 bytes
-rw-r--r--modules/git/tests/repos/repo6_blame/refs/heads/master1
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
new file mode 100644
index 0000000000..6cde9108e7
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/31/bb4b42cecf0a98fc9a32fc5aaeaf53ec52643c
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1 b/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1
new file mode 100644
index 0000000000..b8db01dc35
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/3b/0f66d8b065f8adbf2fef7d986528c655b98cb1
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376 b/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376
new file mode 100644
index 0000000000..6c0ae4723f
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/45/fb6cbc12f970b04eacd5cd4165edd11c8d7376
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9 b/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9
new file mode 100644
index 0000000000..5c2b5641cf
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/49/7701e5bb8676e419b93875d8f0808c7b31aed9
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7 b/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7
new file mode 100644
index 0000000000..3c6471864c
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/54/4d8f7a3b15927cddf2299b4b562d6ebd71b6a7
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044 b/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044
new file mode 100644
index 0000000000..847b7bc305
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/a8/9199e8dea077e4a8ba0bc01bc155275cfdd044
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93 b/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93
new file mode 100644
index 0000000000..206ef1efb7
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/af/7486bd54cfc39eea97207ca666aa69c9d6df93
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421 b/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421
new file mode 100644
index 0000000000..bb26889ed3
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/b8/d1ba1ccb58ee3744b3d1434aae7d26ce2d9421
Binary files differ
diff --git a/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8 b/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8
new file mode 100644
index 0000000000..1653ed9544
--- /dev/null
+++ b/modules/git/tests/repos/repo6_blame/objects/ca/411a3b842c3caec045772da42de16b3ffdafe8
Binary files differ
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