mirror of https://github.com/harness/drone.git
feat: [PIPE-22535]: Include previous commit in blame (#2812)
parent
ac8eb9ff37
commit
5cbd33bd5d
|
@ -27,6 +27,8 @@ import (
|
|||
"github.com/harness/gitness/errors"
|
||||
"github.com/harness/gitness/git/command"
|
||||
"github.com/harness/gitness/git/sha"
|
||||
|
||||
"github.com/gotidy/ptr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -37,8 +39,14 @@ var (
|
|||
)
|
||||
|
||||
type BlamePart struct {
|
||||
Commit *Commit `json:"commit"`
|
||||
Lines []string `json:"lines"`
|
||||
Commit *Commit `json:"commit"`
|
||||
Lines []string `json:"lines"`
|
||||
Previous *BlamePartPrevious `json:"previous,omitempty"`
|
||||
}
|
||||
|
||||
type BlamePartPrevious struct {
|
||||
CommitSHA sha.SHA `json:"commit_sha"`
|
||||
FileName string `json:"file_name"`
|
||||
}
|
||||
|
||||
type BlameNextReader interface {
|
||||
|
@ -92,17 +100,22 @@ func (g *Git) Blame(
|
|||
}()
|
||||
|
||||
return &BlameReader{
|
||||
scanner: bufio.NewScanner(pipeRead),
|
||||
commitCache: make(map[string]*Commit),
|
||||
errReader: stderr, // Any stderr output will cause the BlameReader to fail.
|
||||
scanner: bufio.NewScanner(pipeRead),
|
||||
cache: make(map[string]blameReaderCacheItem),
|
||||
errReader: stderr, // Any stderr output will cause the BlameReader to fail.
|
||||
}
|
||||
}
|
||||
|
||||
type blameReaderCacheItem struct {
|
||||
commit *Commit
|
||||
previous *BlamePartPrevious
|
||||
}
|
||||
|
||||
type BlameReader struct {
|
||||
scanner *bufio.Scanner
|
||||
lastLine string
|
||||
commitCache map[string]*Commit
|
||||
errReader io.Reader
|
||||
scanner *bufio.Scanner
|
||||
lastLine string
|
||||
cache map[string]blameReaderCacheItem
|
||||
errReader io.Reader
|
||||
}
|
||||
|
||||
func (r *BlameReader) nextLine() (string, error) {
|
||||
|
@ -132,6 +145,7 @@ func (r *BlameReader) unreadLine(line string) {
|
|||
//nolint:complexity,gocognit,nestif // it's ok
|
||||
func (r *BlameReader) NextPart() (*BlamePart, error) {
|
||||
var commit *Commit
|
||||
var previous *BlamePartPrevious
|
||||
var lines []string
|
||||
var err error
|
||||
|
||||
|
@ -146,8 +160,10 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
|
|||
commitSHA := sha.Must(matches[1])
|
||||
|
||||
if commit == nil {
|
||||
commit = r.commitCache[commitSHA.String()]
|
||||
if commit == nil {
|
||||
if cacheItem, ok := r.cache[commitSHA.String()]; ok {
|
||||
commit = cacheItem.commit
|
||||
previous = cacheItem.previous
|
||||
} else {
|
||||
commit = &Commit{SHA: commitSHA}
|
||||
}
|
||||
|
||||
|
@ -164,11 +180,15 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
|
|||
|
||||
if !commit.SHA.Equal(commitSHA) {
|
||||
r.unreadLine(line)
|
||||
r.commitCache[commit.SHA.String()] = commit
|
||||
r.cache[commit.SHA.String()] = blameReaderCacheItem{
|
||||
commit: commit,
|
||||
previous: previous,
|
||||
}
|
||||
|
||||
return &BlamePart{
|
||||
Commit: commit,
|
||||
Lines: lines,
|
||||
Commit: commit,
|
||||
Lines: lines,
|
||||
Previous: previous,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -187,7 +207,7 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
parseBlameHeaders(line, commit)
|
||||
parseBlameHeaders(line, commit, &previous)
|
||||
}
|
||||
|
||||
// Check if there's something in the error buffer... If yes, that's the error!
|
||||
|
@ -223,15 +243,16 @@ func (r *BlameReader) NextPart() (*BlamePart, error) {
|
|||
|
||||
if commit != nil && len(lines) > 0 {
|
||||
part = &BlamePart{
|
||||
Commit: commit,
|
||||
Lines: lines,
|
||||
Commit: commit,
|
||||
Lines: lines,
|
||||
Previous: previous,
|
||||
}
|
||||
}
|
||||
|
||||
return part, err
|
||||
}
|
||||
|
||||
func parseBlameHeaders(line string, commit *Commit) {
|
||||
func parseBlameHeaders(line string, commit *Commit, previous **BlamePartPrevious) {
|
||||
// This is the list of git blame headers that we process. Other headers we ignore.
|
||||
const (
|
||||
headerSummary = "summary "
|
||||
|
@ -241,6 +262,7 @@ func parseBlameHeaders(line string, commit *Commit) {
|
|||
headerCommitterName = "committer "
|
||||
headerCommitterMail = "committer-mail "
|
||||
headerCommitterTime = "committer-time "
|
||||
headerPrevious = "previous "
|
||||
)
|
||||
|
||||
switch {
|
||||
|
@ -258,6 +280,8 @@ func parseBlameHeaders(line string, commit *Commit) {
|
|||
commit.Committer.Identity.Email = extractEmail(line[len(headerCommitterMail):])
|
||||
case strings.HasPrefix(line, headerCommitterTime):
|
||||
commit.Committer.When = extractTime(line[len(headerCommitterTime):])
|
||||
case strings.HasPrefix(line, headerPrevious):
|
||||
*previous = ptr.Of(extractPrevious(line[len(headerPrevious):]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,6 +289,19 @@ func extractName(s string) string {
|
|||
return s
|
||||
}
|
||||
|
||||
// extractPrevious extracts the sha and filename of the previous commit.
|
||||
// example: previous 999d2ed306a916423d18e022abe258e92419ab9a README.md
|
||||
func extractPrevious(s string) BlamePartPrevious {
|
||||
rawSHA, fileName, _ := strings.Cut(s, " ")
|
||||
if len(fileName) > 0 && fileName[0] == '"' {
|
||||
fileName, _ = strconv.Unquote(fileName)
|
||||
}
|
||||
return BlamePartPrevious{
|
||||
CommitSHA: sha.Must(rawSHA),
|
||||
FileName: fileName,
|
||||
}
|
||||
}
|
||||
|
||||
// extractEmail extracts email from git blame output.
|
||||
// The email address is wrapped between "<" and ">" characters.
|
||||
// If "<" or ">" are not in place it returns the string as it.
|
||||
|
|
|
@ -41,6 +41,7 @@ committer-mail <noreply@harness.io>
|
|||
committer-time 1669812989
|
||||
committer-tz +0100
|
||||
summary Pull request 1
|
||||
previous ec84ae5018520efdead481c81a31950b82196ec6 "\"\\n\\123\342\210\206'ex' \\\\t.\\ttxt\""
|
||||
filename file_name_before_rename.go
|
||||
Line 10
|
||||
16f267ad4f731af1b2e36f42e170ed8921377398 12 11 1
|
||||
|
@ -55,7 +56,7 @@ committer-mail <noreply@harness.io>
|
|||
committer-time 1673952128
|
||||
committer-tz +0100
|
||||
summary Pull request 2
|
||||
previous 6561a7b86e1a5e74ea0e4e73ccdfc18b486a2826 file_name.go
|
||||
previous 6561a7b86e1a5e74ea0e4e73ccdfc18b486a2826 file_name.go
|
||||
filename file_name.go
|
||||
Line 12
|
||||
16f267ad4f731af1b2e36f42e170ed8921377398 13 13 2
|
||||
|
@ -86,6 +87,10 @@ filename file_name.go
|
|||
When: time.Unix(1669812989, 0),
|
||||
},
|
||||
}
|
||||
previous1 := &BlamePartPrevious{
|
||||
CommitSHA: sha.Must("ec84ae5018520efdead481c81a31950b82196ec6"),
|
||||
FileName: `"\n\123∆'ex' \\t.\ttxt"`,
|
||||
}
|
||||
|
||||
commit2 := &Commit{
|
||||
SHA: sha.Must("dcb4b6b63e86f06ed4e4c52fbc825545dc0b6200"),
|
||||
|
@ -100,26 +105,33 @@ filename file_name.go
|
|||
When: time.Unix(1673952128, 0),
|
||||
},
|
||||
}
|
||||
previous2 := &BlamePartPrevious{
|
||||
CommitSHA: sha.Must("6561a7b86e1a5e74ea0e4e73ccdfc18b486a2826"),
|
||||
FileName: " file_name.go ",
|
||||
}
|
||||
|
||||
want := []*BlamePart{
|
||||
{
|
||||
Commit: commit1,
|
||||
Lines: []string{"Line 10", "Line 11"},
|
||||
Commit: commit1,
|
||||
Lines: []string{"Line 10", "Line 11"},
|
||||
Previous: previous1,
|
||||
},
|
||||
{
|
||||
Commit: commit2,
|
||||
Lines: []string{"Line 12"},
|
||||
Commit: commit2,
|
||||
Lines: []string{"Line 12"},
|
||||
Previous: previous2,
|
||||
},
|
||||
{
|
||||
Commit: commit1,
|
||||
Lines: []string{"Line 13", "Line 14"},
|
||||
Commit: commit1,
|
||||
Lines: []string{"Line 13", "Line 14"},
|
||||
Previous: previous1,
|
||||
},
|
||||
}
|
||||
|
||||
reader := BlameReader{
|
||||
scanner: bufio.NewScanner(strings.NewReader(blameOut)),
|
||||
commitCache: make(map[string]*Commit),
|
||||
errReader: strings.NewReader(""),
|
||||
scanner: bufio.NewScanner(strings.NewReader(blameOut)),
|
||||
cache: make(map[string]blameReaderCacheItem),
|
||||
errReader: strings.NewReader(""),
|
||||
}
|
||||
|
||||
var got []*BlamePart
|
||||
|
@ -144,9 +156,9 @@ filename file_name.go
|
|||
|
||||
func TestBlameReader_NextPart_UserError(t *testing.T) {
|
||||
reader := BlameReader{
|
||||
scanner: bufio.NewScanner(strings.NewReader("")),
|
||||
commitCache: make(map[string]*Commit),
|
||||
errReader: strings.NewReader("fatal: no such path\n"),
|
||||
scanner: bufio.NewScanner(strings.NewReader("")),
|
||||
cache: make(map[string]blameReaderCacheItem),
|
||||
errReader: strings.NewReader("fatal: no such path\n"),
|
||||
}
|
||||
|
||||
_, err := reader.NextPart()
|
||||
|
@ -157,9 +169,9 @@ func TestBlameReader_NextPart_UserError(t *testing.T) {
|
|||
|
||||
func TestBlameReader_NextPart_CmdError(t *testing.T) {
|
||||
reader := BlameReader{
|
||||
scanner: bufio.NewScanner(iotest.ErrReader(errors.New("dummy error"))),
|
||||
commitCache: make(map[string]*Commit),
|
||||
errReader: strings.NewReader(""),
|
||||
scanner: bufio.NewScanner(iotest.ErrReader(errors.New("dummy error"))),
|
||||
cache: make(map[string]blameReaderCacheItem),
|
||||
errReader: strings.NewReader(""),
|
||||
}
|
||||
|
||||
_, err := reader.NextPart()
|
||||
|
|
25
git/blame.go
25
git/blame.go
|
@ -20,6 +20,7 @@ import (
|
|||
"io"
|
||||
|
||||
"github.com/harness/gitness/errors"
|
||||
"github.com/harness/gitness/git/sha"
|
||||
)
|
||||
|
||||
type BlameParams struct {
|
||||
|
@ -65,8 +66,14 @@ func (params *BlameParams) Validate() error {
|
|||
}
|
||||
|
||||
type BlamePart struct {
|
||||
Commit *Commit `json:"commit"`
|
||||
Lines []string `json:"lines"`
|
||||
Commit *Commit `json:"commit"`
|
||||
Lines []string `json:"lines"`
|
||||
Previous *BlamePartPrevious `json:"previous,omitempty"`
|
||||
}
|
||||
|
||||
type BlamePartPrevious struct {
|
||||
CommitSHA sha.SHA `json:"commit_sha"`
|
||||
FileName string `json:"file_name"`
|
||||
}
|
||||
|
||||
// Blame processes and streams the git blame output data.
|
||||
|
@ -94,7 +101,6 @@ func (s *Service) Blame(ctx context.Context, params *BlameParams) (<-chan *Blame
|
|||
|
||||
for {
|
||||
part, errRead := reader.NextPart()
|
||||
|
||||
if part == nil {
|
||||
return
|
||||
}
|
||||
|
@ -108,7 +114,18 @@ func (s *Service) Blame(ctx context.Context, params *BlameParams) (<-chan *Blame
|
|||
lines := make([]string, len(part.Lines))
|
||||
copy(lines, part.Lines)
|
||||
|
||||
ch <- &BlamePart{Commit: commit, Lines: lines}
|
||||
next := &BlamePart{
|
||||
Commit: commit,
|
||||
Lines: lines,
|
||||
}
|
||||
if part.Previous != nil {
|
||||
next.Previous = &BlamePartPrevious{
|
||||
CommitSHA: part.Previous.CommitSHA,
|
||||
FileName: part.Previous.FileName,
|
||||
}
|
||||
}
|
||||
|
||||
ch <- next
|
||||
|
||||
if errRead != nil && errors.Is(errRead, io.EOF) {
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue