Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarmigan Casebolt committed Sep 2, 2016
0 parents commit 0c67f1a
Show file tree
Hide file tree
Showing 5 changed files with 362 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language: go
go:
- 1.4
- 1.6
- tip
env:
- GOOS=linux
- GOOS=windows GOARCH=386
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

# prepare-commit-msg-jira

[![GoDoc](https://godoc.org/github.com/tarm/prepare-commit-msg-jira?status.svg)](http://godoc.org/github.com/tarm/prepare-commit-msg-jira)

[![Build Status](https://travis-ci.org/tarm/prepare-commit-msg-jira.svg?branch=master)](https://travis-ci.org/tarm/prepare-commit-msg-jira)

Prepares a git commit message from a Jira issue by fetching the
summary and description and formatting in a way that is appropiate for
a git commit message. It identifies the Jira ticket number from the
name of the git branch that you are on.

You should install it in your .git/hooks/ directory as 'prepare-commit-msg'.

It uses the following git configuration items:


- jira.url - this is the base url of the jira service. It should
have a trailing '/'

- jira.regexp - this is a regular expression that extracts the issue
name from the git branch name.

- jira.jsessionid - this is the value of the JSESSIONID cookie that
jira sets after you login. Get this from your
browser.

You should set these git configuration items like this:


git config --add jira.url <a href="https://jira.atlassian.com">https://jira.atlassian.com</a>

If it cannot extract the ticket number from the branch or it cannot
find the ticket number in Jira, then the default git commit message
will be used.

It only prepares a commit message for "normal" commits. It leaves the
git default commit message for ammend, --C, merge, and -m"msg" style
commits.

It applies the following format for the git commit message:


TICKET-12345: The summary line

Descriptive text is wrapped at 76 characters. This is a really long
line to get the point across.

Text that is within the {noformat} directive is included without wrapping but is indented by 4 spaces

Patches (with unit tests) are welcome to improve authentication,
enhance the formatting, etc.








- - -
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
56 changes: 56 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Documentation: http://godoc.org/github.com/tarm/prepare-commit-msg-jira
Travis Status: https://travis-ci.org/tarm/prepare-commit-msg-jira
Prepares a git commit message from a Jira issue by fetching the
summary and description and formatting in a way that is appropiate for
a git commit message. It identifies the Jira ticket number from the
name of the git branch that you are on.
You should install it in your .git/hooks/ directory as 'prepare-commit-msg'.
It uses the following git configuration items:
- jira.url - this is the base url of the jira service. It should
have a trailing '/'
- jira.regexp - this is a regular expression that extracts the issue
name from the git branch name.
- jira.jsessionid - this is the value of the JSESSIONID cookie that
jira sets after you login. Get this from your
browser.
You should set these git configuration items like this:
git config --add jira.url https://jira.atlassian.com
If it cannot extract the ticket number from the branch or it cannot
find the ticket number in Jira, then the default git commit message
will be used.
It only prepares a commit message for "normal" commits. It leaves the
git default commit message for ammend, --C, merge, and -m"msg" style
commits.
It applies the following format for the git commit message:
TICKET-12345: The summary line
Descriptive text is wrapped at 76 characters. This is a really long
line to get the point across.
Text that is within the {noformat} directive is included without wrapping but is indented by 4 spaces
Patches (with unit tests) are welcome to improve authentication,
enhance the formatting, etc.
*/
package main

//go:generate godoc2md -o README.md github.com/tarm/prepare-commit-msg-jira

/////////////////////////////////////////
// Fixup the readme to add the badges: //
/////////////////////////////////////////
//go:generate sed -i "s|Documentation: .*|[![GoDoc](https://godoc.org/github.com/tarm/prepare-commit-msg-jira?status.svg)](http://godoc.org/github.com/tarm/prepare-commit-msg-jira)|" README.md
//go:generate sed -i "s|Travis Status: .*|[![Build Status](https://travis-ci.org/tarm/prepare-commit-msg-jira.svg?branch=master)](https://travis-ci.org/tarm/prepare-commit-msg-jira)|" README.md
60 changes: 60 additions & 0 deletions format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"encoding/hex"
"testing"

jira "github.com/andygrunwald/go-jira"
)

const expFormat = `EXAM-1234: This is an example summary
Here is the description. It has some lines that are seperated by only a single \n. They will
be combined together and wrapped.
A double newline should be it's own paragraph
A noformat section should be intented
`

func TestFormat(t *testing.T) {
issue := &jira.Issue{
Key: "EXAM-1234",
Fields: &jira.IssueFields{
Summary: "This is an example summary",
Description: `Here is the description.
It has some lines that are seperated by only a single \n. They will be combined together and wrapped.
A double newline should be it's own paragraph
{noformat}
A noformat section should be intented
{noformat}
`,
},
}

out := formatForGit(issue)

if string(out) != expFormat {
t.Logf("%s\n", out)
t.Fatalf("Bad formatting. Expected:\n\n%v\nGot\n\n%v", hex.Dump([]byte(expFormat)), hex.Dump(out))
}
}

func TestFetch(t *testing.T) {
issue, err := fetchIssue("JRA-808", "9D8B6D83F4E00F3A83EC0A76304DE343.node1", "https://jira.atlassian.com")
if err != nil {
t.Fatal(err)
}
if issue == nil {
t.Fatal("Should not have nil issue")
}
if issue.Fields.Summary != "jsessionid trouble" {
t.Fatalf("Unexpected summary %v", issue.Fields.Summary)
}
if len(issue.Fields.Description) != 1230 {
t.Fatalf("Got bad description length %v", len(issue.Fields.Description))
}
}
176 changes: 176 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package main

import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"os/exec"
"regexp"
"strings"

jira "github.com/andygrunwald/go-jira"
)

func main() {
// Don't modify if a pre-existing message exists
if len(os.Args) > 2 {
return
}

jsessionid, err := getGitConfig("jira.jsessionid")
if err != nil {
log.Fatal("Please set jira.jsessionid in you git config")
}
jurl, err := getGitConfig("jira.url")
if err != nil {
log.Fatal("Please set jira.url in your git config")
}
jregex, err := getGitConfig("jira.regexp")
if err != nil {
log.Fatal("Please set jira.regexp in your git config")
}
re, err := regexp.Compile(jregex)
if err != nil {
log.Fatal(err)
}

// Get the name from the branch
out, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").CombinedOutput()
if err != nil {
log.Fatal("Could not get branch name", err)
}
name := string(re.Find(out))
if name == "" {
// We still want to allow regular committing
fmt.Println("No matching branch found")
return
}

issue, err := fetchIssue(name, jsessionid, jurl)
if err != nil {
// We still want to allow regular committing
fmt.Println(err)
return
}

err = ioutil.WriteFile(os.Args[1], formatForGit(issue), 0644)
if err != nil {
log.Fatal(err)
}
}

func getGitConfig(key string) (string, error) {
out, err := exec.Command("git", "config", "--get", key).CombinedOutput()
return string(bytes.TrimSpace(out)), err
}

const lineLen = 76

// 1) Puts the summary at the top
// 2) wraps long lines at 76 characters
// 3) prettifies some jira formatting
// - {noformat} gets indented by 4 spaces
// - TODO more things like tables, quotes, and numbered lists
func formatForGit(i *jira.Issue) []byte {
var buf bytes.Buffer

// Summary
fmt.Fprintf(&buf, "%v: %v\n\n", i.Key, i.Fields.Summary)

// Wrap some lines
str := strings.Replace(i.Fields.Description, "\r", "", -1)
pars := strings.Split(str, "\n")
count := 0
noformat := false
for i := range pars {
if noformat {
if pars[i] == "{noformat}" {
noformat = false
} else {
fmt.Fprintf(&buf, " %v\n", pars[i])
}
continue
}
if pars[i] == "{noformat}" {
if count != 0 {
buf.WriteString("\n\n")
count = 0
}
noformat = true
continue
}

if len(pars[i]) == 0 {
// Double newline
if count != 0 {
buf.WriteString("\n")
count = 0
}
buf.WriteString("\n")
continue
}
firstChar := pars[i][0]
switch {
case firstChar >= 'a' && firstChar <= 'z':
case firstChar >= 'A' && firstChar <= 'Z':
case firstChar >= '0' && firstChar <= '9':
default:
if count != 0 {
buf.WriteString("\n")
}
buf.WriteString(pars[i])
buf.WriteString("\n")
count = 0
continue
}
words := strings.Fields(pars[i])
for _, word := range words {
if count+len(word) > lineLen {
buf.WriteString("\n")
count = 0
}
fmt.Fprintf(&buf, "%v ", word)
count += len(word)
}
}

return buf.Bytes()
}

func fetchIssue(name, jsessionid, jurl string) (*jira.Issue, error) {
jar, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
jurlp, err := url.Parse(jurl)
if err != nil {
return nil, err
}
jar.SetCookies(jurlp, []*http.Cookie{{
Name: "JSESSIONID",
Value: jsessionid,
}})
cl := &http.Client{
Jar: jar,
}

c, err := jira.NewClient(cl, jurl)
if err != nil {
return nil, err
}
issue, resp, err := c.Issue.Get(name)
if err != nil {
if resp != nil {
body, _ := ioutil.ReadAll(resp.Body)
err = fmt.Errorf("%v %v", err, string(body))
}
return nil, err
}

return issue, nil
}

0 comments on commit 0c67f1a

Please sign in to comment.