issues

Github, Jira, Gitlab issues from Plan 9's acme
Log | Files | Refs | README | LICENSE

commit dba81bbb8f3a41fae5355b537578aee32f4ffbb5
parent d2fa05283082197455aecfaa93287d441bb0c452
Author: Russ Cox <rsc@golang.org>
Date:   Sat, 25 Apr 2015 16:24:29 -0400

cl2issue: new gopherbot program

Posts "CL XXX mentions this issue" on GitHub when that's true and hasn't been said yet.

Should live somewhere else eventually.

Diffstat:
Acl2issue/main.go | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 137 insertions(+), 0 deletions(-)

diff --git a/cl2issue/main.go b/cl2issue/main.go @@ -0,0 +1,137 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Cl2issue scans Gerrit for pending CLs that mention GitHub issues +// and posts links to those CLs as GitHub issue comments. +// It expects to find golang.org/x/build/cmd/cl and rsc.io/github/issue +// in its $PATH, and it expects to have a GitHub personal access token +// in $HOME/.github-cl2issue-token for use with the issue program. +package main + +import ( + "bytes" + "encoding/json" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" +) + +const mentionsTemplate = "CL https://golang.org/cl/%v mentions this issue." + +var ( + editFlag = flag.String("edit-for-cl", "", "act as $EDITOR for issue, mentioning CL `cl`") + flagN = flag.Bool("n", false, "print operations but do not execute them") +) + +type CL struct { + Number int + Issues []int +} + +type Issue struct { + Comments []*Comment +} + +type Comment struct { + Text string +} + +func main() { + log.SetFlags(0) + log.SetPrefix("cl2issue: ") + flag.Parse() + + if *editFlag != "" { + runEditor() + return + } + + args := []string{"-json"} + args = append(args, flag.Args()...) + data, err := exec.Command("cl", args...).CombinedOutput() + if err != nil { + log.Fatal("fetching CLs: %v\n%s", err, data) + } + + var cls []*CL + if err := json.Unmarshal(data, &cls); err != nil { + log.Fatal("parsing CLs: %v", err) + } + + tokenFile := os.Getenv("HOME") + "/.github-cl2issue-token" + for _, cl := range cls { + mentions := fmt.Sprintf(mentionsTemplate, cl.Number) + Issues: + for _, issueNumber := range cl.Issues { + data, err := exec.Command("issue", "-token", tokenFile, "-json", fmt.Sprint(issueNumber)).CombinedOutput() + if err != nil { + log.Printf("reading #%d: %v\n%s", issueNumber, err, data) + continue + } + var issue Issue + if err := json.Unmarshal(data, &issue); err != nil { + log.Printf("parsing #%d: %v", issueNumber, err) + continue + } + for _, com := range issue.Comments { + if strings.Contains(com.Text, mentions) { + continue Issues + } + } + fmt.Printf("post to #%d about CL %d\n", issueNumber, cl.Number) + if *flagN { + continue + } + cmd := exec.Command("issue", "-token", tokenFile, "-e", fmt.Sprint(issueNumber)) + cmd.Env = editorEnv(cl.Number) + data, err = cmd.CombinedOutput() + if err != nil { + log.Printf("updating #%d: %v\n%s", issueNumber, err, data) + continue + } + } + } +} + +func runEditor() { + if flag.NArg() != 1 { + flag.Usage() + os.Exit(2) + } + + file := flag.Arg(0) + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatal(err) + } + + i := bytes.Index(data, []byte("\nReported by ")) + if i < 0 { + log.Fatal("unexpected issue template") + } + + newdata := append(data[:i:i], []byte(fmt.Sprintf("\n\n"+mentionsTemplate+"\n\n", *editFlag))...) + newdata = append(newdata, data[i:]...) + + if err := ioutil.WriteFile(file, newdata, 0666); err != nil { + log.Fatal(err) + } + os.Exit(0) +} + +func editorEnv(cl int) []string { + var env []string + for _, kv := range os.Environ() { + if strings.HasPrefix(kv, "EDITOR=") || strings.HasPrefix(kv, "VISUAL=") { + continue + } + env = append(env, kv) + } + env = append(env, "EDITOR=cl2issue -edit-for-cl "+fmt.Sprint(cl)) + return env +}