-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[MER-3309] Add an extra merge commit detection based on commit messages
When a PR gets closed by "Close #123", usually GitHub marks the PR to be closed by certain commit. However, this mechanism is not reliable, and sometimes GitHub doesn't mark it so and just close it. This breaks the merge commit detection since av-cli doesn't have a way to know it from GitHub API. By using git-log, we can figure out whether there's actually a commit that closes the PRs with this comment. This adds that extra detection method. In order to make this work, it moves the timing of git-fetch. This is needed in order for a local repository to have commits in the upstream to check the commit messages.
- Loading branch information
Showing
3 changed files
with
186 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package git | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"io" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
var ( | ||
closeCommitPattern = regexp.MustCompile(`(?i)\b(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)\W+#(\d+)\b`) | ||
) | ||
|
||
type LogOpts struct { | ||
// RevisionRange is the range of the commits specified by the format described in | ||
// git-log(1). | ||
RevisionRange string | ||
} | ||
|
||
// Log returns a list of commits specified by the range. | ||
func (r *Repo) Log(opts LogOpts) ([]*CommitInfo, error) { | ||
res, err := r.Run(&RunOpts{ | ||
Args: []string{"log", "--format=%H%x00%h%x00%s%x00%b%x00", opts.RevisionRange}, | ||
ExitError: true, | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
logrus.WithFields(logrus.Fields{"range": opts.RevisionRange}).Debug("got git-log") | ||
|
||
rd := bufio.NewReader(bytes.NewBuffer(res.Stdout)) | ||
var ret []*CommitInfo | ||
for { | ||
ci, err := readLogEntry(rd) | ||
if err != nil { | ||
if err == io.EOF { | ||
break | ||
} | ||
return nil, err | ||
} | ||
ret = append(ret, ci) | ||
} | ||
return ret, nil | ||
} | ||
|
||
func readLogEntry(rd *bufio.Reader) (*CommitInfo, error) { | ||
commitHash, err := rd.ReadString('\x00') | ||
if err != nil { | ||
return nil, err | ||
} | ||
abbrevHash, err := rd.ReadString('\x00') | ||
if err != nil { | ||
return nil, err | ||
} | ||
subject, err := rd.ReadString('\x00') | ||
if err != nil { | ||
return nil, err | ||
} | ||
body, err := rd.ReadString('\x00') | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &CommitInfo{ | ||
Hash: strings.TrimSpace(trimNUL(commitHash)), | ||
ShortHash: strings.TrimSpace(trimNUL(abbrevHash)), | ||
Subject: trimNUL(subject), | ||
Body: trimNUL(body), | ||
}, nil | ||
} | ||
|
||
func trimNUL(s string) string { | ||
return strings.Trim(s, "\x00") | ||
} | ||
|
||
// FindClosedPRs finds the "closes #123" instructions from the commit messages. This returns a PR | ||
// number to commit hash mapping. | ||
// | ||
// See https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword | ||
func FindClosedPRs(cis []*CommitInfo) map[int64]string { | ||
ret := map[int64]string{} | ||
for _, ci := range cis { | ||
matches := closeCommitPattern.FindAllStringSubmatch(ci.Body, -1) | ||
for _, match := range matches { | ||
prNum, _ := strconv.ParseInt(match[1], 10, 64) | ||
ret[prNum] = ci.Hash | ||
} | ||
} | ||
return ret | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package git_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/aviator-co/av/internal/git" | ||
"github.com/aviator-co/av/internal/git/gittest" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestRepo_Log(t *testing.T) { | ||
repo := gittest.NewTempRepo(t) | ||
c1 := gittest.CommitFile(t, repo, "file", []byte("first commit\n"), gittest.WithMessage("commit 1\n\ncommit 1 body")) | ||
c2 := gittest.CommitFile(t, repo, "file", []byte("first commit\nsecond commit\n"), gittest.WithMessage("commit 2\n\ncommit 2 body")) | ||
|
||
cis, err := repo.Log(git.LogOpts{RevisionRange: c1 + "^1.." + c2}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, []*git.CommitInfo{ | ||
{ | ||
Hash: c2, | ||
ShortHash: c2[:7], | ||
Subject: "commit 2", | ||
Body: "commit 2 body\n", | ||
}, | ||
{ | ||
Hash: c1, | ||
ShortHash: c1[:7], | ||
Subject: "commit 1", | ||
Body: "commit 1 body\n", | ||
}, | ||
}, cis) | ||
} | ||
|
||
func TestFindClosedPRs(t *testing.T) { | ||
cis := []*git.CommitInfo{ | ||
{ | ||
Hash: "fake_1", | ||
Body: "some comments. close #123. fixed #433", | ||
}, | ||
{ | ||
Hash: "fake_2", | ||
Body: "some other comments.\nfix #234", | ||
}, | ||
} | ||
|
||
assert.Equal(t, map[int64]string{ | ||
123: "fake_1", | ||
234: "fake_2", | ||
433: "fake_1", | ||
}, git.FindClosedPRs(cis)) | ||
} |