diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2018-05-17 15:54:17 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2018-05-17 15:58:58 +0200 |
| commit | edbe39a26d6535516aeafd0dca705c1ee02ad5b9 (patch) | |
| tree | 6c085e5ec618ffc291dd207148f4e9f6e9390a02 /pkg/git | |
| parent | 4e1c0dbaeab92f02cef51524975f6cc447667b80 (diff) | |
pkg/git: extract relevant email addresses from commits
Update #501
Diffstat (limited to 'pkg/git')
| -rw-r--r-- | pkg/git/git.go | 55 | ||||
| -rw-r--r-- | pkg/git/git_test.go | 65 |
2 files changed, 111 insertions, 9 deletions
diff --git a/pkg/git/git.go b/pkg/git/git.go index 59715ed07..899930bff 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -131,9 +131,11 @@ func initRepo(dir string) error { } type Commit struct { - Hash string - Title string - Date time.Time + Hash string + Title string + Author string + CC []string + Date time.Time } // HeadCommit returns info about the HEAD commit of the current branch of git repository in dir. @@ -142,22 +144,49 @@ func HeadCommit(dir string) (*Commit, error) { } func GetCommit(dir, commit string) (*Commit, error) { - output, err := runSandboxed(dir, "git", "log", "--pretty=format:%H%n%s%n%ad", "-n", "1", commit) + output, err := runSandboxed(dir, "git", "log", "--format=%H%n%s%n%ae%n%ad%n%b", "-n", "1", commit) if err != nil { return nil, err } + return parseCommit(output) +} + +func parseCommit(output []byte) (*Commit, error) { lines := bytes.Split(output, []byte{'\n'}) - if len(lines) != 3 || len(lines[0]) != 40 { + if len(lines) < 4 || len(lines[0]) != 40 { return nil, fmt.Errorf("unexpected git log output: %q", output) } - date, err := time.Parse(DateFormat, string(lines[2])) + 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]), - Date: date, + Hash: string(lines[0]), + Title: string(lines[1]), + Author: string(lines[2]), + CC: sortedCC, + Date: date, } return com, nil } @@ -456,4 +485,12 @@ var ( gitBranchRe = regexp.MustCompile("^[a-zA-Z0-9-_/.]{2,200}$") gitHashRe = regexp.MustCompile("^[a-f0-9]+$") releaseTagRe = regexp.MustCompile(`^v([0-9]+).([0-9]+)(?:\.([0-9]+))?$`) + ccRes = []*regexp.Regexp{ + regexp.MustCompile(`^Reviewed\-.*: (.*)$`), + regexp.MustCompile(`^[A-Za-z-]+\-and\-[Rr]eviewed\-.*: (.*)$`), + regexp.MustCompile(`^Acked\-.*: (.*)$`), + regexp.MustCompile(`^[A-Za-z-]+\-and\-[Aa]cked\-.*: (.*)$`), + regexp.MustCompile(`^Tested\-.*: (.*)$`), + regexp.MustCompile(`^[A-Za-z-]+\-and\-[Tt]ested\-.*: (.*)$`), + } ) diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go index 66159107f..86f033acf 100644 --- a/pkg/git/git_test.go +++ b/pkg/git/git_test.go @@ -7,8 +7,73 @@ import ( "reflect" "strings" "testing" + "time" ) +func TestParseCommit(t *testing.T) { + tests := map[string]*Commit{ + `2075b16e32c26e4031b9fd3cbe26c54676a8fcb5 +rbtree: include rcu.h +foobar@foobar.de +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 +the header file. It works as long as it gets somehow included before +that and fails otherwise. + +Link: http://lkml.kernel.org/r/20180504103159.19938-1-bigeasy@linutronix.de +Signed-off-by: Foo Bad Baz <another@email.de> +Reviewed-by: <yetanother@email.org> +Cc: Unrelated Guy <somewhere@email.com> +Acked-by: Subsystem reviewer <Subsystem@reviewer.com> +Reported-and-tested-by: and@me.com +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> +`: &Commit{ + Hash: "2075b16e32c26e4031b9fd3cbe26c54676a8fcb5", + Title: "rbtree: include rcu.h", + Author: "foobar@foobar.de", + CC: []string{ + "and@me.com", + "foobar@foobar.de", + "mustbe@correct.com", + "name@name.com", + "subsystem@reviewer.com", + "yetanother@email.org", + }, + Date: time.Date(2018, 5, 11, 16, 02, 14, 0, time.FixedZone("", -7*60*60)), + }, + } + for input, com := range tests { + res, err := parseCommit([]byte(input)) + if err != nil && com != nil { + t.Fatalf("want %+v, got error: %v", com, err) + } + if err == nil && com == nil { + t.Fatalf("want error, got commit %+v", res) + } + if com == nil { + continue + } + if com.Hash != res.Hash { + t.Fatalf("want hash %q, got %q", com.Hash, res.Hash) + } + if com.Title != res.Title { + t.Fatalf("want title %q, got %q", com.Title, res.Title) + } + 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 !com.Date.Equal(res.Date) { + t.Fatalf("want date %v, got %v", com.Date, res.Date) + } + } +} + func TestCanonicalizeCommit(t *testing.T) { tests := map[string]string{ "foo bar": "foo bar", |
