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