aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/vcs
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2019-03-12 12:22:13 +0100
committerDmitry Vyukov <dvyukov@google.com>2019-03-17 18:06:44 +0100
commit5ed211ca96c6e6ab96fad7b5a495d81178ee98eb (patch)
tree352711e48e64791cda1615ca2bd6611588a23209 /pkg/vcs
parent5958caeafcf3d467009ae984a9be8302bf852a50 (diff)
pkg/vcs: refactor bisection support
In preparation for syz-ci bisection: - move bisection function into a separate interface they look out of place in vcs.Repo because most OSes don't implement it and most users don't case - extract author name and more CC emails for commits - move linux-specific PreviousReleaseTags into linux.go - fix inconclusive bisection (more than 1 potential commits) - add tests fr bisection - add maintainers returned from get_maintainers.pl for commits that don't have enough emails (e.g. only author email) Update #501
Diffstat (limited to 'pkg/vcs')
-rw-r--r--pkg/vcs/akaros.go18
-rw-r--r--pkg/vcs/freebsd.go2
-rw-r--r--pkg/vcs/fuchsia.go11
-rw-r--r--pkg/vcs/git.go120
-rw-r--r--pkg/vcs/git_repo_test.go147
-rw-r--r--pkg/vcs/git_test.go19
-rw-r--r--pkg/vcs/linux.go154
-rw-r--r--pkg/vcs/netbsd.go19
-rw-r--r--pkg/vcs/openbsd.go19
-rw-r--r--pkg/vcs/vcs.go40
10 files changed, 404 insertions, 145 deletions
diff --git a/pkg/vcs/akaros.go b/pkg/vcs/akaros.go
index 8d8d640e6..0d3e701fb 100644
--- a/pkg/vcs/akaros.go
+++ b/pkg/vcs/akaros.go
@@ -4,8 +4,6 @@
package vcs
import (
- "fmt"
- "io"
"path/filepath"
)
@@ -16,8 +14,8 @@ type akaros struct {
func newAkaros(vm, dir string) *akaros {
return &akaros{
- git: newGit(dir),
- dropbear: newGit(filepath.Join(dir, "dropbear")),
+ git: newGit(dir, nil),
+ dropbear: newGit(filepath.Join(dir, "dropbear"), nil),
}
}
@@ -27,15 +25,3 @@ func (ctx *akaros) Poll(repo, branch string) (*Commit, error) {
}
return ctx.git.Poll(repo, branch)
}
-
-func (ctx *akaros) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) {
- return ctx.git.ExtractFixTagsFromCommits(baseCommit, email)
-}
-
-func (ctx *akaros) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) {
- return nil, fmt.Errorf("not implemented for akaros")
-}
-
-func (ctx *akaros) PreviousReleaseTags(commit string) ([]string, error) {
- return nil, fmt.Errorf("not implemented for akaros")
-}
diff --git a/pkg/vcs/freebsd.go b/pkg/vcs/freebsd.go
index 6ef7858ce..f007ab69f 100644
--- a/pkg/vcs/freebsd.go
+++ b/pkg/vcs/freebsd.go
@@ -14,7 +14,7 @@ type freebsd struct {
func newFreeBSD(vm, dir string) *freebsd {
return &freebsd{
- git: newGit(dir),
+ git: newGit(dir, nil),
}
}
diff --git a/pkg/vcs/fuchsia.go b/pkg/vcs/fuchsia.go
index 07626cd5e..c04404416 100644
--- a/pkg/vcs/fuchsia.go
+++ b/pkg/vcs/fuchsia.go
@@ -22,7 +22,7 @@ func newFuchsia(vm, dir string) *fuchsia {
return &fuchsia{
vm: vm,
dir: dir,
- zircon: newGit(filepath.Join(dir, "zircon")),
+ zircon: newGit(filepath.Join(dir, "zircon"), nil),
}
}
@@ -91,10 +91,7 @@ func (ctx *fuchsia) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Comm
return ctx.zircon.ExtractFixTagsFromCommits(baseCommit, email)
}
-func (ctx *fuchsia) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) {
- return nil, fmt.Errorf("not implemented for fuchsia")
-}
-
-func (ctx *fuchsia) PreviousReleaseTags(commit string) ([]string, error) {
- return nil, fmt.Errorf("not implemented for fuchsia")
+func (ctx *fuchsia) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, interface{}, error)) (
+ *Commit, interface{}, error) {
+ return nil, nil, fmt.Errorf("not implemented for fuchsia")
}
diff --git a/pkg/vcs/git.go b/pkg/vcs/git.go
index 4f23bb16c..210c5b154 100644
--- a/pkg/vcs/git.go
+++ b/pkg/vcs/git.go
@@ -11,22 +11,25 @@ import (
"net/mail"
"os"
"os/exec"
+ "regexp"
"sort"
- "strconv"
"strings"
"time"
"github.com/google/syzkaller/pkg/hash"
+ "github.com/google/syzkaller/pkg/log"
"github.com/google/syzkaller/pkg/osutil"
)
type git struct {
- dir string
+ dir string
+ ignoreCC map[string]bool
}
-func newGit(dir string) *git {
+func newGit(dir string, ignoreCC map[string]bool) *git {
return &git{
- dir: dir,
+ dir: dir,
+ ignoreCC: ignoreCC,
}
}
@@ -66,7 +69,7 @@ func (git *git) CheckoutBranch(repo, branch string) (*Commit, error) {
dir := git.dir
runSandboxed(dir, "git", "bisect", "reset")
if _, err := runSandboxed(dir, "git", "reset", "--hard"); err != nil {
- if err := git.initRepo(); err != nil {
+ if err := git.initRepo(err); err != nil {
return nil, err
}
}
@@ -84,7 +87,7 @@ func (git *git) CheckoutCommit(repo, commit string) (*Commit, error) {
dir := git.dir
runSandboxed(dir, "git", "bisect", "reset")
if _, err := runSandboxed(dir, "git", "reset", "--hard"); err != nil {
- if err := git.initRepo(); err != nil {
+ if err := git.initRepo(err); err != nil {
return nil, err
}
}
@@ -111,7 +114,7 @@ func (git *git) SwitchCommit(commit string) (*Commit, error) {
}
func (git *git) clone(repo, branch string) error {
- if err := git.initRepo(); err != nil {
+ if err := git.initRepo(nil); err != nil {
return err
}
if _, err := runSandboxed(git.dir, "git", "remote", "add", "origin", repo); err != nil {
@@ -123,7 +126,10 @@ func (git *git) clone(repo, branch string) error {
return nil
}
-func (git *git) initRepo() error {
+func (git *git) initRepo(reason error) error {
+ if reason != nil {
+ log.Logf(1, "git: initializing repo at %v: %v", git.dir, reason)
+ }
if err := os.RemoveAll(git.dir); err != nil {
return fmt.Errorf("failed to remove repo dir: %v", err)
}
@@ -144,27 +150,27 @@ func (git *git) HeadCommit() (*Commit, error) {
}
func (git *git) getCommit(commit string) (*Commit, error) {
- output, err := runSandboxed(git.dir, "git", "log", "--format=%H%n%s%n%ae%n%ad%n%b", "-n", "1", commit)
+ output, err := runSandboxed(git.dir, "git", "log", "--format=%H%n%s%n%ae%n%an%n%ad%n%b", "-n", "1", commit)
if err != nil {
return nil, err
}
- return gitParseCommit(output, nil, nil)
+ return gitParseCommit(output, nil, nil, git.ignoreCC)
}
-func gitParseCommit(output, user, domain []byte) (*Commit, error) {
+func gitParseCommit(output, user, domain []byte, ignoreCC map[string]bool) (*Commit, error) {
lines := bytes.Split(output, []byte{'\n'})
if len(lines) < 4 || len(lines[0]) != 40 {
return nil, fmt.Errorf("unexpected git log output: %q", output)
}
const dateFormat = "Mon Jan 2 15:04:05 2006 -0700"
- date, err := time.Parse(dateFormat, string(lines[3]))
+ date, err := time.Parse(dateFormat, string(lines[4]))
if err != nil {
return nil, fmt.Errorf("failed to parse date in git log output: %v\n%q", err, output)
}
cc := make(map[string]bool)
cc[strings.ToLower(string(lines[2]))] = true
var tags []string
- for _, line := range lines[4:] {
+ for _, line := range lines[5:] {
if user != nil {
userPos := bytes.Index(line, user)
if userPos != -1 {
@@ -195,7 +201,11 @@ func gitParseCommit(output, user, domain []byte) (*Commit, error) {
if err != nil {
break
}
- cc[strings.ToLower(addr.Address)] = true
+ email := strings.ToLower(addr.Address)
+ if ignoreCC[email] {
+ continue
+ }
+ cc[email] = true
break
}
}
@@ -205,12 +215,13 @@ func gitParseCommit(output, user, domain []byte) (*Commit, error) {
}
sort.Strings(sortedCC)
com := &Commit{
- Hash: string(lines[0]),
- Title: string(lines[1]),
- Author: string(lines[2]),
- CC: sortedCC,
- Tags: tags,
- Date: date,
+ Hash: string(lines[0]),
+ Title: string(lines[1]),
+ Author: string(lines[2]),
+ AuthorName: string(lines[3]),
+ CC: sortedCC,
+ Tags: tags,
+ Date: date,
}
return com, nil
}
@@ -276,7 +287,7 @@ func (git *git) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit,
func (git *git) fetchCommits(since, base, user, domain string, greps []string, fixedStrings bool) ([]*Commit, error) {
const commitSeparator = "---===syzkaller-commit-separator===---"
- args := []string{"log", "--since", since, "--format=%H%n%s%n%ae%n%ad%n%b%n" + commitSeparator}
+ args := []string{"log", "--since", since, "--format=%H%n%s%n%ae%n%an%n%ad%n%b%n" + commitSeparator}
if fixedStrings {
args = append(args, "--fixed-strings")
}
@@ -314,7 +325,7 @@ func (git *git) fetchCommits(since, base, user, domain string, greps []string, f
buf.WriteByte('\n')
continue
}
- com, err := gitParseCommit(buf.Bytes(), userBytes, domainBytes)
+ com, err := gitParseCommit(buf.Bytes(), userBytes, domainBytes, git.ignoreCC)
if err != nil {
return nil, err
}
@@ -343,7 +354,7 @@ func splitEmail(email string) (user, domain string, err error) {
return
}
-func (git *git) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) {
+func (git *git) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) ([]*Commit, error) {
dir := git.dir
runSandboxed(dir, "git", "bisect", "reset")
runSandboxed(dir, "git", "reset", "--hard")
@@ -375,59 +386,44 @@ func (git *git) Bisect(bad, good string, trace io.Writer, pred func() (BisectRes
firstBad = current
}
output, err = runSandboxed(dir, "git", "bisect", bisectTerms[res])
+ fmt.Fprintf(trace, "# git bisect %v %v\n%s", bisectTerms[res], current.Hash, output)
if err != nil {
+ if bytes.Contains(output, []byte("There are only 'skip'ped commits left to test")) {
+ return git.bisectInconclusive(output)
+ }
return nil, err
}
- fmt.Fprintf(trace, "# git bisect %v %v\n%s", bisectTerms[res], current.Hash, output)
next, err := git.HeadCommit()
if err != nil {
return nil, err
}
if current.Hash == next.Hash {
- return firstBad, nil
+ return []*Commit{firstBad}, nil
}
current = next
}
}
-// Note: linux-specific.
-func (git *git) PreviousReleaseTags(commit string) ([]string, error) {
- output, err := runSandboxed(git.dir, "git", "tag", "--no-contains", commit, "--merged", commit, "v*.*")
- if err != nil {
- return nil, err
- }
- return gitParseReleaseTags(output)
-}
-
-func gitParseReleaseTags(output []byte) ([]string, error) {
- var tags []string
- for _, tag := range bytes.Split(output, []byte{'\n'}) {
- if releaseTagRe.Match(tag) && gitReleaseTagToInt(string(tag)) != 0 {
- tags = append(tags, string(tag))
- }
- }
- sort.Slice(tags, func(i, j int) bool {
- return gitReleaseTagToInt(tags[i]) > gitReleaseTagToInt(tags[j])
- })
- return tags, nil
-}
-
-func gitReleaseTagToInt(tag string) uint64 {
- matches := releaseTagRe.FindStringSubmatchIndex(tag)
- v1, err := strconv.ParseUint(tag[matches[2]:matches[3]], 10, 64)
- if err != nil {
- return 0
- }
- v2, err := strconv.ParseUint(tag[matches[4]:matches[5]], 10, 64)
- if err != nil {
- return 0
- }
- var v3 uint64
- if matches[6] != -1 {
- v3, err = strconv.ParseUint(tag[matches[6]:matches[7]], 10, 64)
+func (git *git) bisectInconclusive(output []byte) ([]*Commit, error) {
+ // For inconclusive bisection git prints the following message:
+ //
+ // There are only 'skip'ped commits left to test.
+ // The first bad commit could be any of:
+ // 1f43f400a2cbb02f3d34de8fe30075c070254816
+ // 4d96e13ee9cd1f7f801e8c7f4b12f09d1da4a5d8
+ // 5cd856a5ef9aa189df757c322be34ad735a5b17f
+ // We cannot bisect more!
+ //
+ // For conclusive bisection:
+ //
+ // 7c3850adbcccc2c6c9e7ab23a7dcbc4926ee5b96 is the first bad commit
+ var commits []*Commit
+ for _, hash := range regexp.MustCompile("[a-f0-9]{40}").FindAll(output, -1) {
+ com, err := git.getCommit(string(hash))
if err != nil {
- return 0
+ return nil, err
}
+ commits = append(commits, com)
}
- return v1*1e6 + v2*1e3 + v3
+ return commits, nil
}
diff --git a/pkg/vcs/git_repo_test.go b/pkg/vcs/git_repo_test.go
index f60bfc737..6e8018dfc 100644
--- a/pkg/vcs/git_repo_test.go
+++ b/pkg/vcs/git_repo_test.go
@@ -8,6 +8,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "sort"
"testing"
"time"
@@ -22,6 +23,7 @@ func init() {
const (
userEmail = `test@syzkaller.com`
+ userName = `Test Syzkaller`
extractFixTagsEmail = `"syzbot" <syzbot@my.mail.com>`
)
@@ -34,7 +36,7 @@ func TestGitRepo(t *testing.T) {
defer os.RemoveAll(baseDir)
repo1 := createTestRepo(t, baseDir, "repo1")
repo2 := createTestRepo(t, baseDir, "repo2")
- repo := newGit(filepath.Join(baseDir, "repo"))
+ repo := newGit(filepath.Join(baseDir, "repo"), nil)
{
com, err := repo.Poll(repo1.dir, "master")
if err != nil {
@@ -165,6 +167,9 @@ func checkCommit(t *testing.T, idx int, test testCommit, com *Commit, checkTags
if test.author != com.Author {
t.Errorf("#%v: want author %q, got %q", idx, test.author, com.Author)
}
+ if userName != com.AuthorName {
+ t.Errorf("#%v: want author name %q, got %q", idx, userName, com.Author)
+ }
if diff := cmp.Diff(test.cc, com.CC); diff != "" {
t.Logf("%#v", com.CC)
t.Error(diff)
@@ -273,11 +278,142 @@ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
`,
title: "USB: fix usbmon BUG trigger",
author: userEmail,
- cc: []string{userEmail},
+ cc: []string{"gregkh@linuxfoundation.org", userEmail, "zaitcev@redhat.com"},
tags: []string{"f9831b881b3e849829fc"},
},
}
+func TestBisect(t *testing.T) {
+ t.Parallel()
+ repoDir, err := ioutil.TempDir("", "syz-git-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(repoDir)
+ repo := makeTestRepo(t, repoDir)
+ var commits []string
+ for i := 0; i < 5; i++ {
+ repo.commitChange(fmt.Sprintf("commit %v", i))
+ com, err := repo.repo.HeadCommit()
+ if err != nil {
+ t.Fatal(err)
+ }
+ commits = append(commits, com.Hash)
+ t.Logf("%v %v", com.Hash, com.Title)
+ }
+ type Test struct {
+ pred func() (BisectResult, error)
+ result []string
+ }
+ tests := []Test{
+ {
+ // All are bad.
+ func() (BisectResult, error) {
+ return BisectBad, nil
+ },
+ []string{commits[1]},
+ },
+ {
+ // All are good.
+ func() (BisectResult, error) {
+ return BisectGood, nil
+ },
+ []string{commits[4]},
+ },
+ {
+ // All are skipped.
+ func() (BisectResult, error) {
+ return BisectSkip, nil
+ },
+ []string{commits[1], commits[2], commits[3], commits[4]},
+ },
+ {
+ // Some are skipped.
+ func() (BisectResult, error) {
+ current, err := repo.repo.HeadCommit()
+ if err != nil {
+ t.Fatal(err)
+ }
+ switch current.Hash {
+ case commits[1]:
+ return BisectSkip, nil
+ case commits[2]:
+ return BisectSkip, nil
+ case commits[3]:
+ return BisectGood, nil
+ default:
+ return 0, fmt.Errorf("unknown commit %v", current.Hash)
+ }
+ },
+ []string{commits[4]},
+ },
+ {
+ // Some are skipped.
+ func() (BisectResult, error) {
+ current, err := repo.repo.HeadCommit()
+ if err != nil {
+ t.Fatal(err)
+ }
+ switch current.Hash {
+ case commits[1]:
+ return BisectGood, nil
+ case commits[2]:
+ return BisectSkip, nil
+ case commits[3]:
+ return BisectBad, nil
+ default:
+ return 0, fmt.Errorf("unknown commit %v", current.Hash)
+ }
+ },
+ []string{commits[2], commits[3]},
+ },
+ {
+ // Some are skipped.
+ func() (BisectResult, error) {
+ current, err := repo.repo.HeadCommit()
+ if err != nil {
+ t.Fatal(err)
+ }
+ switch current.Hash {
+ case commits[1]:
+ return BisectSkip, nil
+ case commits[2]:
+ return BisectSkip, nil
+ case commits[3]:
+ return BisectGood, nil
+ default:
+ return 0, fmt.Errorf("unknown commit %v", current.Hash)
+ }
+ },
+ []string{commits[4]},
+ },
+ }
+ for i, test := range tests {
+ t.Logf("TEST %v", i)
+ result, err := repo.repo.Bisect(commits[4], commits[0], (*testWriter)(t), test.pred)
+ if err != nil {
+ t.Fatal(err)
+ }
+ var got []string
+ for _, com := range result {
+ got = append(got, com.Hash)
+ }
+ sort.Strings(got) // git result order is non-deterministic (wat)
+ sort.Strings(test.result)
+ if diff := cmp.Diff(test.result, got); diff != "" {
+ t.Logf("result: %+v", got)
+ t.Fatal(diff)
+ }
+ }
+}
+
+type testWriter testing.T
+
+func (t *testWriter) Write(data []byte) (int, error) {
+ (*testing.T)(t).Log(string(data))
+ return len(data), nil
+}
+
func createTestRepo(t *testing.T, baseDir, name string) *testRepo {
repo := makeTestRepo(t, filepath.Join(baseDir, name))
repo.git("checkout", "-b", "master")
@@ -304,16 +440,19 @@ func makeTestRepo(t *testing.T, dir string) *testRepo {
if err := osutil.MkdirAll(dir); err != nil {
t.Fatal(err)
}
+ ignoreCC := map[string]bool{
+ "stable@vger.kernel.org": true,
+ }
repo := &testRepo{
t: t,
dir: dir,
name: filepath.Base(dir),
commits: make(map[string]map[string]*Commit),
- repo: newGit(dir),
+ repo: newGit(dir, ignoreCC),
}
repo.git("init")
repo.git("config", "--add", "user.email", userEmail)
- repo.git("config", "--add", "user.name", "Test Syzkaller")
+ repo.git("config", "--add", "user.name", userName)
return repo
}
diff --git a/pkg/vcs/git_test.go b/pkg/vcs/git_test.go
index a22835a69..b2ad32b6b 100644
--- a/pkg/vcs/git_test.go
+++ b/pkg/vcs/git_test.go
@@ -7,6 +7,8 @@ import (
"reflect"
"testing"
"time"
+
+ "github.com/google/go-cmp/cmp"
)
func TestGitParseCommit(t *testing.T) {
@@ -14,6 +16,7 @@ func TestGitParseCommit(t *testing.T) {
`2075b16e32c26e4031b9fd3cbe26c54676a8fcb5
rbtree: include rcu.h
foobar@foobar.de
+Foo Bar
Fri May 11 16:02:14 2018 -0700
Since commit c1adf20052d8 ("Introduce rb_replace_node_rcu()")
rbtree_augmented.h uses RCU related data structures but does not include
@@ -30,14 +33,18 @@ Reported-and-Tested-by: Name-name <name@name.com>
Tested-by: Must be correct <mustbe@correct.com>
Signed-off-by: Linux Master <linux@linux-foundation.org>
`: {
- Hash: "2075b16e32c26e4031b9fd3cbe26c54676a8fcb5",
- Title: "rbtree: include rcu.h",
- Author: "foobar@foobar.de",
+ Hash: "2075b16e32c26e4031b9fd3cbe26c54676a8fcb5",
+ Title: "rbtree: include rcu.h",
+ Author: "foobar@foobar.de",
+ AuthorName: "Foo Bar",
CC: []string{
"and@me.com",
+ "another@email.de",
"foobar@foobar.de",
+ "linux@linux-foundation.org",
"mustbe@correct.com",
"name@name.com",
+ "somewhere@email.com",
"subsystem@reviewer.com",
"yetanother@email.org",
},
@@ -45,7 +52,7 @@ Signed-off-by: Linux Master <linux@linux-foundation.org>
},
}
for input, com := range tests {
- res, err := gitParseCommit([]byte(input), nil, nil)
+ res, err := gitParseCommit([]byte(input), nil, nil, nil)
if err != nil && com != nil {
t.Fatalf("want %+v, got error: %v", com, err)
}
@@ -64,8 +71,8 @@ Signed-off-by: Linux Master <linux@linux-foundation.org>
if com.Author != res.Author {
t.Fatalf("want author %q, got %q", com.Author, res.Author)
}
- if !reflect.DeepEqual(com.CC, res.CC) {
- t.Fatalf("want CC %q, got %q", com.CC, res.CC)
+ if diff := cmp.Diff(com.CC, res.CC); diff != "" {
+ t.Fatalf("bad CC: %v", diff)
}
if !com.Date.Equal(res.Date) {
t.Fatalf("want date %v, got %v", com.Date, res.Date)
diff --git a/pkg/vcs/linux.go b/pkg/vcs/linux.go
new file mode 100644
index 000000000..f8d97d60e
--- /dev/null
+++ b/pkg/vcs/linux.go
@@ -0,0 +1,154 @@
+// Copyright 2019 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package vcs
+
+import (
+ "bytes"
+ "io"
+ "net/mail"
+ "path/filepath"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/google/syzkaller/pkg/email"
+ "github.com/google/syzkaller/pkg/osutil"
+)
+
+type linux struct {
+ *git
+}
+
+func newLinux(dir string) *linux {
+ ignoreCC := map[string]bool{
+ "stable@vger.kernel.org": true,
+ }
+ return &linux{
+ git: newGit(dir, ignoreCC),
+ }
+}
+
+func (ctx *linux) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) ([]*Commit, error) {
+ commits, err := ctx.git.Bisect(bad, good, trace, pred)
+ if len(commits) == 1 {
+ ctx.addMaintainers(commits[0])
+ }
+ return commits, err
+}
+
+func (ctx *linux) addMaintainers(com *Commit) {
+ if len(com.CC) > 3 {
+ return
+ }
+ list := ctx.getMaintainers(com.Hash, false)
+ if len(list) < 3 {
+ list = ctx.getMaintainers(com.Hash, true)
+ }
+ com.CC = email.MergeEmailLists(com.CC, list)
+}
+
+func (ctx *linux) getMaintainers(hash string, blame bool) []string {
+ args := "git show " + hash + " | " +
+ filepath.FromSlash("scripts/get_maintainer.pl") + " --no-n --no-rolestats"
+ if blame {
+ args += " --git-blame"
+ }
+ output, err := osutil.RunCmd(time.Minute, ctx.git.dir, "bash", "-c", args)
+ if err != nil {
+ return nil
+ }
+ var list []string
+ for _, line := range strings.Split(string(output), "\n") {
+ addr, err := mail.ParseAddress(line)
+ if err != nil {
+ continue
+ }
+ list = append(list, strings.ToLower(addr.Address))
+ }
+ return list
+}
+
+func (ctx *linux) PreviousReleaseTags(commit string) ([]string, error) {
+ output, err := runSandboxed(ctx.dir, "git", "tag", "--no-contains", commit, "--merged", commit, "v*.*")
+ if err != nil {
+ return nil, err
+ }
+ tags, err := gitParseReleaseTags(output)
+ if err != nil {
+ return nil, err
+ }
+ for i, tag := range tags {
+ if tag == "v3.8" {
+ // v3.8 does not work with modern perl, and as we go further in history
+ // make stops to work, then binutils, glibc, etc. So we stop at v3.8.
+ // Up to that point we only need an ancient gcc.
+ tags = tags[:i]
+ break
+ }
+ }
+ return tags, nil
+}
+
+func gitParseReleaseTags(output []byte) ([]string, error) {
+ var tags []string
+ for _, tag := range bytes.Split(output, []byte{'\n'}) {
+ if releaseTagRe.Match(tag) && gitReleaseTagToInt(string(tag)) != 0 {
+ tags = append(tags, string(tag))
+ }
+ }
+ sort.Slice(tags, func(i, j int) bool {
+ return gitReleaseTagToInt(tags[i]) > gitReleaseTagToInt(tags[j])
+ })
+ return tags, nil
+}
+
+func gitReleaseTagToInt(tag string) uint64 {
+ matches := releaseTagRe.FindStringSubmatchIndex(tag)
+ v1, err := strconv.ParseUint(tag[matches[2]:matches[3]], 10, 64)
+ if err != nil {
+ return 0
+ }
+ v2, err := strconv.ParseUint(tag[matches[4]:matches[5]], 10, 64)
+ if err != nil {
+ return 0
+ }
+ var v3 uint64
+ if matches[6] != -1 {
+ v3, err = strconv.ParseUint(tag[matches[6]:matches[7]], 10, 64)
+ if err != nil {
+ return 0
+ }
+ }
+ return v1*1e6 + v2*1e3 + v3
+}
+
+func (ctx *linux) EnvForCommit(commit string, kernelConfig []byte) (*BisectEnv, error) {
+ tagList, err := ctx.PreviousReleaseTags(commit)
+ if err != nil {
+ return nil, err
+ }
+ tags := make(map[string]bool)
+ for _, tag := range tagList {
+ tags[tag] = true
+ }
+ env := &BisectEnv{
+ Compiler: "gcc-" + linuxCompilerVersion(tags),
+ KernelConfig: kernelConfig,
+ }
+ return env, nil
+}
+
+func linuxCompilerVersion(tags map[string]bool) string {
+ switch {
+ case tags["v4.12"]:
+ return "8.1.0"
+ case tags["v4.11"]:
+ return "7.3.0"
+ case tags["v3.19"]:
+ return "5.5.0"
+ default:
+ return "4.9.4"
+ }
+}
diff --git a/pkg/vcs/netbsd.go b/pkg/vcs/netbsd.go
index b43523756..6b710f4ca 100644
--- a/pkg/vcs/netbsd.go
+++ b/pkg/vcs/netbsd.go
@@ -3,29 +3,12 @@
package vcs
-import (
- "fmt"
- "io"
-)
-
type netbsd struct {
*git
}
func newNetBSD(vm, dir string) *netbsd {
return &netbsd{
- git: newGit(dir),
+ git: newGit(dir, nil),
}
}
-
-func (ctx *netbsd) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) {
- return ctx.git.ExtractFixTagsFromCommits(baseCommit, email)
-}
-
-func (ctx *netbsd) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) {
- return nil, fmt.Errorf("not implemented for netbsd")
-}
-
-func (ctx *netbsd) PreviousReleaseTags(commit string) ([]string, error) {
- return nil, fmt.Errorf("not implemented for netbsd")
-}
diff --git a/pkg/vcs/openbsd.go b/pkg/vcs/openbsd.go
index 3998ce58a..f97290f99 100644
--- a/pkg/vcs/openbsd.go
+++ b/pkg/vcs/openbsd.go
@@ -3,29 +3,12 @@
package vcs
-import (
- "fmt"
- "io"
-)
-
type openbsd struct {
*git
}
func newOpenBSD(vm, dir string) *openbsd {
return &openbsd{
- git: newGit(dir),
+ git: newGit(dir, nil),
}
}
-
-func (ctx *openbsd) ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error) {
- return ctx.git.ExtractFixTagsFromCommits(baseCommit, email)
-}
-
-func (ctx *openbsd) Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error) {
- return nil, fmt.Errorf("not implemented for openbsd")
-}
-
-func (ctx *openbsd) PreviousReleaseTags(commit string) ([]string, error) {
- return nil, fmt.Errorf("not implemented for openbsd")
-}
diff --git a/pkg/vcs/vcs.go b/pkg/vcs/vcs.go
index 9d779410f..8026bbfd5 100644
--- a/pkg/vcs/vcs.go
+++ b/pkg/vcs/vcs.go
@@ -49,25 +49,32 @@ type Repo interface {
// Given email = "user@domain.com", it searches for tags of the form "user+tag@domain.com"
// and returns commits with these tags.
ExtractFixTagsFromCommits(baseCommit, email string) ([]*Commit, error)
+}
- // PreviousReleaseTags returns list of preceding release tags that are reachable from the given commit.
- PreviousReleaseTags(commit string) ([]string, error)
-
+// Bisecter may be optionally implemented by Repo.
+type Bisecter interface {
// Bisect bisects good..bad commit range against the provided predicate (wrapper around git bisect).
// The predicate should return an error only if there is no way to proceed
// (it will abort the process), if possible it should prefer to return BisectSkip.
// Progress of the process is streamed to the provided trace.
- // Returns the first commit on which the predicate returns BisectBad.
- Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) (*Commit, error)
+ // Returns the first commit on which the predicate returns BisectBad,
+ // or multiple commits if bisection is inconclusive due to BisectSkip.
+ Bisect(bad, good string, trace io.Writer, pred func() (BisectResult, error)) ([]*Commit, error)
+
+ // PreviousReleaseTags returns list of preceding release tags that are reachable from the given commit.
+ PreviousReleaseTags(commit string) ([]string, error)
+
+ EnvForCommit(commit string, kernelConfig []byte) (*BisectEnv, error)
}
type Commit struct {
- Hash string
- Title string
- Author string
- CC []string
- Tags []string
- Date time.Time
+ Hash string
+ Title string
+ Author string
+ AuthorName string
+ CC []string
+ Tags []string
+ Date time.Time
}
type BisectResult int
@@ -78,10 +85,15 @@ const (
BisectSkip
)
+type BisectEnv struct {
+ Compiler string
+ KernelConfig []byte
+}
+
func NewRepo(os, vm, dir string) (Repo, error) {
switch os {
case "linux":
- return newGit(dir), nil
+ return newLinux(dir), nil
case "akaros":
return newAkaros(vm, dir), nil
case "fuchsia":
@@ -97,7 +109,7 @@ func NewRepo(os, vm, dir string) (Repo, error) {
}
func NewSyzkallerRepo(dir string) Repo {
- return newGit(dir)
+ return newGit(dir, nil)
}
func Patch(dir string, patch []byte) error {
@@ -171,6 +183,8 @@ var (
regexp.MustCompile(`^[A-Za-z-]+\-and\-[Aa]cked\-.*: (.*)$`),
regexp.MustCompile(`^Tested\-.*: (.*)$`),
regexp.MustCompile(`^[A-Za-z-]+\-and\-[Tt]ested\-.*: (.*)$`),
+ regexp.MustCompile(`^Signed-off-by: (.*)$`),
+ regexp.MustCompile(`^C[Cc]: (.*)$`),
}
)