mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-24 11:59:58 +00:00
472947468d
This just says that we want to use git to checkout OpenBSD without any special/complex features. Update #712
367 lines
9.4 KiB
Go
367 lines
9.4 KiB
Go
// Copyright 2017 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 (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/mail"
|
|
"os"
|
|
"os/exec"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/syzkaller/pkg/osutil"
|
|
)
|
|
|
|
type git struct {
|
|
dir string
|
|
}
|
|
|
|
func newGit(dir string) *git {
|
|
return &git{
|
|
dir: dir,
|
|
}
|
|
}
|
|
|
|
func (git *git) Poll(repo, branch string) (*Commit, error) {
|
|
dir := git.dir
|
|
runSandboxed(dir, "git", "bisect", "reset")
|
|
runSandboxed(dir, "git", "reset", "--hard")
|
|
origin, err := runSandboxed(dir, "git", "remote", "get-url", "origin")
|
|
if err != nil || strings.TrimSpace(string(origin)) != repo {
|
|
// The repo is here, but it has wrong origin (e.g. repo in config has changed), re-clone.
|
|
if err := git.clone(repo, branch); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
// Use origin/branch for the case the branch was force-pushed,
|
|
// in such case branch is not the same is origin/branch and we will
|
|
// stuck with the local version forever (git checkout won't fail).
|
|
if _, err := runSandboxed(dir, "git", "checkout", "origin/"+branch); err != nil {
|
|
// No such branch (e.g. branch in config has changed), re-clone.
|
|
if err := git.clone(repo, branch); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if _, err := runSandboxed(dir, "git", "fetch", "--no-tags"); err != nil {
|
|
// Something else is wrong, re-clone.
|
|
if err := git.clone(repo, branch); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if _, err := runSandboxed(dir, "git", "checkout", "origin/"+branch); err != nil {
|
|
return nil, err
|
|
}
|
|
return git.HeadCommit()
|
|
}
|
|
|
|
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 {
|
|
return nil, err
|
|
}
|
|
}
|
|
_, err := runSandboxed(dir, "git", "fetch", repo, branch)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := runSandboxed(dir, "git", "checkout", "FETCH_HEAD"); err != nil {
|
|
return nil, err
|
|
}
|
|
return git.HeadCommit()
|
|
}
|
|
|
|
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 {
|
|
return nil, err
|
|
}
|
|
}
|
|
_, err := runSandboxed(dir, "git", "fetch", repo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return git.SwitchCommit(commit)
|
|
}
|
|
|
|
func (git *git) SwitchCommit(commit string) (*Commit, error) {
|
|
dir := git.dir
|
|
if _, err := runSandboxed(dir, "git", "checkout", commit); err != nil {
|
|
return nil, err
|
|
}
|
|
return git.HeadCommit()
|
|
}
|
|
|
|
func (git *git) clone(repo, branch string) error {
|
|
if err := git.initRepo(); err != nil {
|
|
return err
|
|
}
|
|
if _, err := runSandboxed(git.dir, "git", "remote", "add", "origin", repo); err != nil {
|
|
return err
|
|
}
|
|
if _, err := runSandboxed(git.dir, "git", "fetch", "origin", branch); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (git *git) initRepo() error {
|
|
if err := os.RemoveAll(git.dir); err != nil {
|
|
return fmt.Errorf("failed to remove repo dir: %v", err)
|
|
}
|
|
if err := osutil.MkdirAll(git.dir); err != nil {
|
|
return fmt.Errorf("failed to create repo dir: %v", err)
|
|
}
|
|
if err := osutil.SandboxChown(git.dir); err != nil {
|
|
return err
|
|
}
|
|
if _, err := runSandboxed(git.dir, "git", "init"); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (git *git) HeadCommit() (*Commit, error) {
|
|
return git.getCommit("HEAD")
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return gitParseCommit(output)
|
|
}
|
|
|
|
func gitParseCommit(output []byte) (*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]))
|
|
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
|
|
for _, line := range lines[4:] {
|
|
for _, re := range ccRes {
|
|
matches := re.FindSubmatchIndex(line)
|
|
if matches == nil {
|
|
continue
|
|
}
|
|
addr, err := mail.ParseAddress(string(line[matches[2]:matches[3]]))
|
|
if err != nil {
|
|
break
|
|
}
|
|
cc[strings.ToLower(addr.Address)] = true
|
|
break
|
|
}
|
|
}
|
|
sortedCC := make([]string, 0, len(cc))
|
|
for addr := range cc {
|
|
sortedCC = append(sortedCC, addr)
|
|
}
|
|
sort.Strings(sortedCC)
|
|
com := &Commit{
|
|
Hash: string(lines[0]),
|
|
Title: string(lines[1]),
|
|
Author: string(lines[2]),
|
|
CC: sortedCC,
|
|
Date: date,
|
|
}
|
|
return com, nil
|
|
}
|
|
|
|
func (git *git) ListRecentCommits(baseCommit string) ([]string, error) {
|
|
// On upstream kernel this produces ~11MB of output.
|
|
// Somewhat inefficient to collect whole output in a slice
|
|
// and then convert to string, but should be bearable.
|
|
output, err := runSandboxed(git.dir, "git", "log",
|
|
"--pretty=format:%s", "--no-merges", "-n", "200000", baseCommit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return strings.Split(string(output), "\n"), nil
|
|
}
|
|
|
|
func (git *git) ExtractFixTagsFromCommits(baseCommit, email string) ([]FixCommit, error) {
|
|
since := time.Now().Add(-time.Hour * 24 * 365).Format("01-02-2006")
|
|
cmd := exec.Command("git", "log", "--no-merges", "--since", since, baseCommit)
|
|
cmd.Dir = git.dir
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := cmd.Start(); err != nil {
|
|
return nil, err
|
|
}
|
|
defer cmd.Wait()
|
|
defer cmd.Process.Kill()
|
|
return gitExtractFixTags(stdout, email)
|
|
}
|
|
|
|
func gitExtractFixTags(r io.Reader, email string) ([]FixCommit, error) {
|
|
user, domain, err := splitEmail(email)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse email %q: %v", email, err)
|
|
}
|
|
var (
|
|
s = bufio.NewScanner(r)
|
|
commits []FixCommit
|
|
commitTitle = ""
|
|
commitStart = []byte("commit ")
|
|
bodyPrefix = []byte(" ")
|
|
userBytes = []byte(user + "+")
|
|
domainBytes = []byte(domain)
|
|
)
|
|
for s.Scan() {
|
|
ln := s.Bytes()
|
|
if bytes.HasPrefix(ln, commitStart) {
|
|
commitTitle = ""
|
|
continue
|
|
}
|
|
if !bytes.HasPrefix(ln, bodyPrefix) {
|
|
continue
|
|
}
|
|
ln = ln[len(bodyPrefix):]
|
|
if len(ln) == 0 {
|
|
continue
|
|
}
|
|
if commitTitle == "" {
|
|
commitTitle = string(ln)
|
|
continue
|
|
}
|
|
userPos := bytes.Index(ln, userBytes)
|
|
if userPos == -1 {
|
|
continue
|
|
}
|
|
domainPos := bytes.Index(ln[userPos+len(userBytes)+1:], domainBytes)
|
|
if domainPos == -1 {
|
|
continue
|
|
}
|
|
startPos := userPos + len(userBytes)
|
|
endPos := userPos + len(userBytes) + domainPos + 1
|
|
tag := string(ln[startPos:endPos])
|
|
commits = append(commits, FixCommit{tag, commitTitle})
|
|
}
|
|
return commits, s.Err()
|
|
}
|
|
|
|
func splitEmail(email string) (user, domain string, err error) {
|
|
addr, err := mail.ParseAddress(email)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
at := strings.IndexByte(addr.Address, '@')
|
|
if at == -1 {
|
|
return "", "", fmt.Errorf("no @ in email address")
|
|
}
|
|
user = addr.Address[:at]
|
|
domain = addr.Address[at:]
|
|
if plus := strings.IndexByte(user, '+'); plus != -1 {
|
|
user = user[:plus]
|
|
}
|
|
return
|
|
}
|
|
|
|
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")
|
|
firstBad, err := git.getCommit(bad)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
output, err := runSandboxed(dir, "git", "bisect", "start", bad, good)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer runSandboxed(dir, "git", "bisect", "reset")
|
|
fmt.Fprintf(trace, "# git bisect start %v %v\n%s", bad, good, output)
|
|
current, err := git.HeadCommit()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var bisectTerms = [...]string{
|
|
BisectBad: "bad",
|
|
BisectGood: "good",
|
|
BisectSkip: "skip",
|
|
}
|
|
for {
|
|
res, err := pred()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if res == BisectBad {
|
|
firstBad = current
|
|
}
|
|
output, err = runSandboxed(dir, "git", "bisect", bisectTerms[res])
|
|
if err != nil {
|
|
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
|
|
}
|
|
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)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
}
|
|
return v1*1e6 + v2*1e3 + v3
|
|
}
|