forgejo/modules/git/repo_blame.go
BtbN 6298ee4d3a
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
fix: pull request review comment position (#9914)
## Checklist

This PR contains both #9889 and #9912, since it depends on the one, and the other provides a test for it.
The exact reasoning behind its logic is described here: https://codeberg.org/forgejo/forgejo/issues/9473#issuecomment-7976186

This PR should return the behaviour back to how it was before a PR to Gitea changed it.
Only the resulting Database-Entry will reference the line blamed commit, now also with the correct adjusted line.
While the context diff view is pulled from the commit the commenter actually commented on.

### Tests

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [x] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9914
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: BtbN <btbn@btbn.de>
Co-committed-by: BtbN <btbn@btbn.de>
2025-10-31 16:17:23 +01:00

67 lines
1.8 KiB
Go

// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
var (
ErrBlameFileDoesNotExist = errors.New("the blamed file does not exist")
ErrBlameFileNotEnoughLines = errors.New("the blamed file has not enough lines")
notEnoughLinesRe = regexp.MustCompile(`^fatal: file .+ has only \d+ lines?\n$`)
)
// LineBlame returns the latest commit at the given line
func (repo *Repository) LineBlame(revision, file string, line uint64) (*Commit, uint64, error) {
res, _, gitErr := NewCommand(repo.Ctx, "blame").
AddOptionFormat("-L %d,%d", line, line).
AddOptionValues("-p", revision).
AddDashesAndList(file).RunStdString(&RunOpts{Dir: repo.Path})
if gitErr != nil {
stdErr := gitErr.Stderr()
if stdErr == fmt.Sprintf("fatal: no such path %s in %s\n", file, revision) {
return nil, 0, ErrBlameFileDoesNotExist
}
if notEnoughLinesRe.MatchString(stdErr) {
return nil, 0, ErrBlameFileNotEnoughLines
}
return nil, 0, gitErr
}
objectFormat, err := repo.GetObjectFormat()
if err != nil {
return nil, 0, err
}
objectIDLen := objectFormat.FullLength()
if len(res) < objectIDLen+1 {
return nil, 0, fmt.Errorf("output of blame is invalid, cannot contain commit ID: %s", res)
}
commit, err := repo.GetCommit(res[:objectIDLen])
if err != nil {
return nil, 0, fmt.Errorf("GetCommit: %w", err)
}
endIdxOriginalLineNo := strings.IndexRune(res[objectIDLen+1:], ' ')
if endIdxOriginalLineNo == -1 {
return nil, 0, fmt.Errorf("output of blame is invalid, cannot contain original line number: %s", res)
}
originalLineNo, err := strconv.ParseUint(res[objectIDLen+1:objectIDLen+1+endIdxOriginalLineNo], 10, 64)
if err != nil {
return nil, 0, fmt.Errorf("strconv.ParseUint: %w", err)
}
return commit, originalLineNo, nil
}