├── README.textile
├── git-jirabranch
├── git-jirafix
└── prepare-commit-msg
/README.textile:
--------------------------------------------------------------------------------
1 | If like Stef you're using a local branch per JIRA issue, you might want the following features:
2 |
3 | - Make all commits in that branch include the JIRA issue name and description.
4 | - The ability to start a branch for a JIRA issue which would mark the issue as In Progress.
5 | - The ability to merge a branch for a JIRA issue which would mark the issue as Fixed.
6 |
7 | This can all be automated and we describe how to set this up.
8 |
9 | h1. Set up JIRA to allow API access
10 |
11 | You need to Administer JIRA and allow it to "Accept remote API calls":http://confluence.atlassian.com/display/JIRA/Configuring+JIRA+Options.
12 |
13 | Then you will probably want to create a special user with very limited permissions, let's say "cli-api" (or "cli-api-stef" if you want one API user per user), in a new group called "api-users".
14 |
15 | Next, edit the "Permission Scheme" of the project you want to allow access to, and grant the following permissions to the "api-users"
16 | group:
17 |
18 | |_. Permission Name |_. What for|
19 | |Browse Projects | Required for everything|
20 | |Assign Issues | Required for starting and merging branches|
21 | |Resolve Issue | Required for starting and merging branches|
22 |
23 | On the JIRA side you're all set.
24 |
25 | h1. Download the JIRA CLI API
26 |
27 | "Download it":https://studio.plugins.atlassian.com/wiki/display/JCLI/JIRA+Command+Line+Interface (the "Download Binary" link at the bottom), and save it somewhere appropriate on your system.
28 |
29 | h1. Set up the git/JIRA integration
30 |
31 | You now need to tell our tools how to connect to JIRA:
32 |
33 |
# Run this INSIDE your project (or make it --global if you prefer)
34 | $ git config jira.cli ~/bin/jira-cli-2.4.0/jira.sh
35 | $ git config jira.user cli-api
36 | $ git config jira.password secret
37 | $ git config jira.server https://jira.lunatech.com/jira
38 |
39 |
40 | h1. Install the JIRA prepare-commit-msg hook
41 |
42 | "Download the custom hook":git-jira/raw/master/prepare-commit-msg and install it in your project at @.git/hooks/prepare-commit-msg@.
43 |
44 | h1. Install the custom git/JIRA commands
45 |
46 | Download "git-jirabranch":git-jira/raw/master/git-jirabranch and "git-jirafix":git-jira/raw/master/git-jirafix and put them somewhere on your path, so that git picks them up as git extensions.
47 |
48 | h1. Start having fun
49 |
50 | h2. Create an issue branch
51 |
52 | $ git jirabranch FOO-23
53 |
54 |
55 | This will do the following:
56 |
57 | # Create a branch called FOO-23
58 | # Switch to the new branch
59 | # Mark the FOO-23 issue as in progress
60 | # Add a comment to FOO-23 saying you started working on it
61 |
62 | h2. Work
63 |
64 | $ echo "new" > new-file
65 | $ git commit new-file
66 |
67 |
68 | Your message will start with those lines:
69 |
70 | # [FOO-23]: Do tons of fixes
71 |
72 | # [jira]
73 | # View this issue at https://jira.lunatech.com/jira/browse/FOO-23
74 | # ...The usual git commit message follows...
75 |
76 |
77 | Note: While it is slightly inconvenient to start the JIRA line with a comment, it is necessary because it allows you to cancel the commit by exiting without saving. If we had started the commit file with a non-commented line, git would still commit the file if you left the commit message unedited because it would contain a non-commented line. This is not what users expect, so you have to manually uncomment the JIRA line on every commit if you want to keep it in the commit message, but this is quite acceptable.
78 |
79 | h3. Customising the pre-filled commit message
80 |
81 | You can override the start line of the commit messages with the @jira.commit.template@ config:
82 |
83 | # Run this INSIDE your project (or make it --global if you prefer)
84 | $ git config jira.commit.template '%i: %t'
85 |
86 |
87 | The default template is @[%i]: %t@ but you can set it to anything, and @%i@ will be replaced with the issue key, and @%t@ with the issue title.
88 |
89 | h3. What happens if?
90 |
91 | If the hook fails to connect to JIRA, or the issue does not exist or if you forgot to configure the hook with @git config@, you will get a comment in the commit file warning you about it.
92 |
93 | If the branch name does not match the JIRA key grammar, we will not even try to look it up in JIRA, saving time for @master@ commits.
94 |
95 | If the JIRA issue can be resolved from JIRA, it will be cached in @.git/jira.cache@ so that future commits on this branch are faster.
96 |
97 | h2. Merge the branch back to master when you have fixed the issue
98 |
99 | Note: the branch name argument is optional, it will default to the current branch if missing.
100 |
101 | $ git jirafix FOO-23
102 |
103 |
104 | This will do the following:
105 |
106 | # Switch to the branch called FOO-23
107 | # Rebase the branch on master
108 | # Switch to the master branch
109 | # Merge the FOO-23 branch on master
110 | # Mark the FOO-23 issue as Fixed
111 | # Add a comment to FOO-23 saying you committed a fix
112 |
--------------------------------------------------------------------------------
/git-jirabranch:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | USAGE=''
4 | LONG_USAGE='git-jirabranch lets you create a new branch based on a
5 | JIRA issue and mark the issue as in progress.'
6 |
7 | . git-sh-setup
8 |
9 | CACHE_FILE=$GIT_DIR/jira.cache
10 |
11 | JIRA_SERVER=$(git config jira.server || true)
12 | JIRA_USER=$(git config jira.user || true)
13 | JIRA_PASSWORD=$(git config jira.password || true)
14 |
15 | JIRA_CLI=$(git config jira.cli || true)
16 |
17 | GIT_USER=$(git config user.name || echo $USER)
18 |
19 | JIRA_CMD="$JIRA_CLI --server $JIRA_SERVER --user $JIRA_USER --password $JIRA_PASSWORD"
20 |
21 | function get_cached_issue () {
22 | local ISSUE=$1
23 | test -f $CACHE_FILE || return 0
24 | local LINE=`grep ^$ISSUE: $CACHE_FILE`
25 | test -n "$LINE" || return 0
26 | ISSUE_DESCR=$(echo $LINE | sed -e "s/^$ISSUE: //")
27 | }
28 |
29 | function fail () {
30 | echo $@
31 | exit 1
32 | }
33 |
34 | # start to work
35 |
36 | require_work_tree
37 |
38 | BRANCH_NAME=$1
39 | test -n "$BRANCH_NAME" || usage
40 |
41 | test -n "$GIT_DIR" || fail "Missing GIT_DIR variable"
42 | test -n "$JIRA_SERVER" || fail "Missing git config variable jira.server (set it with 'git config jira.server ...')"
43 | test -n "$JIRA_CLI" || fail "Missing git config variable jira.cli (set it with 'git config jira.cli ...')"
44 | test -n "$JIRA_USER" || fail "Missing git config variable jira.user (set it with 'git config jira.user ...')"
45 | test -n "$JIRA_PASSWORD" || fail "Missing git config variable jira.password (set it with 'git config jira.password ...')"
46 |
47 | [[ $BRANCH_NAME =~ ^[A-Z]+-[0-9]+$ ]] || fail "Branch $BRANCH_NAME does not appear to be a JIRA issue"
48 |
49 | echo "Doing a branch for $BRANCH_NAME"
50 |
51 | $JIRA_CMD --action updateIssue --issue $BRANCH_NAME --assignee $JIRA_USER
52 | $JIRA_CMD --action progressIssue --issue $BRANCH_NAME --step "Start Progress"
53 | $JIRA_CMD --action addComment --issue $BRANCH_NAME --comment "Coding started by $GIT_USER"
54 |
55 | # Cache the jira issue title for later cheaper commits
56 | get_cached_issue $BRANCH_NAME
57 | if test -z "$ISSUE_DESCR"
58 | then
59 | ISSUE_DESCR=`$JIRA_CMD --action getFieldValue --issue $BRANCH_NAME --field Summary | sed -e '1d'`
60 | echo "$BRANCH_NAME: $ISSUE_DESCR" >> $CACHE_FILE
61 |
62 | fi
63 |
64 | git checkout -b $BRANCH_NAME
65 |
66 | echo "*** You are now on branch $BRANCH_NAME ***"
67 |
68 |
--------------------------------------------------------------------------------
/git-jirafix:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | USAGE=''
4 | LONG_USAGE='git-jirafix lets you merge your issue branch to master
5 | and mark the issue as fixed.'
6 |
7 | . git-sh-setup
8 |
9 | JIRA_SERVER=$(git config jira.server || true)
10 | JIRA_USER=$(git config jira.user || true)
11 | JIRA_PASSWORD=$(git config jira.password || true)
12 |
13 | JIRA_CLI=$(git config jira.cli || true)
14 |
15 | GIT_USER=$(git config user.name || echo $USER)
16 |
17 | JIRA_CMD="$JIRA_CLI --server $JIRA_SERVER --user $JIRA_USER --password $JIRA_PASSWORD"
18 |
19 | function fail () {
20 | echo $@
21 | exit 1
22 | }
23 |
24 | # start to work
25 |
26 | require_work_tree
27 |
28 | BRANCH_NAME=$1
29 | # default to current branch
30 | test -n "$BRANCH_NAME" || BRANCH_NAME=`git symbolic-ref HEAD | sed -e 's/refs\/heads\///'`
31 | test -n "$BRANCH_NAME" || fail "Unable to get branch name from current branch and no argument specified"
32 |
33 | test -n "$JIRA_SERVER" || fail "Missing git config variable jira.server (set it with 'git config jira.server ...')"
34 | test -n "$JIRA_CLI" || fail "Missing git config variable jira.cli (set it with 'git config jira.cli ...')"
35 | test -n "$JIRA_USER" || fail "Missing git config variable jira.user (set it with 'git config jira.user ...')"
36 | test -n "$JIRA_PASSWORD" || fail "Missing git config variable jira.password (set it with 'git config jira.password ...')"
37 |
38 | [[ $BRANCH_NAME =~ ^[A-Z]+-[0-9]+$ ]] || fail "Branch $BRANCH_NAME does not appear to be a JIRA issue"
39 |
40 | echo "Merging branch $BRANCH_NAME"
41 |
42 | git checkout $BRANCH_NAME
43 | git rebase master
44 | git checkout master
45 | git merge $BRANCH_NAME
46 |
47 | $JIRA_CMD --action progressIssue --issue $BRANCH_NAME --step "Resolve Issue"
48 | $JIRA_CMD --action updateIssue --issue $BRANCH_NAME --assignee -1
49 | $JIRA_CMD --action addComment --issue $BRANCH_NAME --comment "Fix committed by $GIT_USER"
50 |
51 | echo "*** You are now on branch master ***"
52 |
53 |
--------------------------------------------------------------------------------
/prepare-commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/bash -e
2 |
3 | COMMIT_FILE=$1
4 |
5 | . git-sh-setup
6 |
7 | CACHE_FILE=$GIT_DIR/jira.cache
8 |
9 | JIRA_SERVER=$(git config jira.server || true)
10 | JIRA_USER=$(git config jira.user || true)
11 | JIRA_PASSWORD=$(git config jira.password || true)
12 |
13 | JIRA_COMMIT_TEMPLATE=$(git config jira.commit.template || true)
14 |
15 | JIRA_CLI=$(git config jira.cli || true)
16 |
17 | JIRA_CMD="$JIRA_CLI --server $JIRA_SERVER --user $JIRA_USER --password $JIRA_PASSWORD"
18 |
19 | function prepend () {
20 | local MSG=$1
21 | # Give up if we already have a commit message (like when rebasing)
22 | awk 'NR==1 && /^$/ { exit 0 }; {exit 1} ' < $COMMIT_FILE || return 0
23 | echo -e $MSG | sed -i '' -e "1s/^/\
24 | # [jira]/" -e 'r /dev/stdin' -e '2s/^/# \
25 | /' $COMMIT_FILE
26 | }
27 |
28 | function prepend_ok () {
29 | local MSG=$1
30 | # Give up if we already have a commit message (like when rebasing)
31 | awk 'NR==1 && /^$/ { exit 0 }; {exit 1} ' < $COMMIT_FILE || return 0
32 | echo $MSG | sed -i '' -e 'r /dev/stdin' -e '1d' -e '2s/^/\
33 | /' $COMMIT_FILE
34 | }
35 |
36 | function get_cached_issue () {
37 | local ISSUE=$1
38 | test -f $CACHE_FILE || return 0
39 | local LINE=`grep ^$ISSUE: $CACHE_FILE`
40 | test -n "$LINE" || return 0
41 | ISSUE_DESCR=$(echo $LINE | sed -e "s/^$ISSUE: //")
42 | }
43 |
44 | function fail () {
45 | echo $@
46 | exit 1
47 | }
48 |
49 | # start to work
50 |
51 | require_work_tree
52 |
53 | test -n "$GIT_DIR" || fail "Missing GIT_DIR variable"
54 | test -n "$COMMIT_FILE" || fail "Missing commit file argument"
55 | test -n "$JIRA_SERVER" || fail "Missing git config variable jira.server (set it with 'git config jira.server ...')"
56 | test -n "$JIRA_CLI" || fail "Missing git config variable jira.cli (set it with 'git config jira.cli ...')"
57 | test -n "$JIRA_USER" || fail "Missing git config variable jira.user (set it with 'git config jira.user ...')"
58 | test -n "$JIRA_PASSWORD" || fail "Missing git config variable jira.password (set it with 'git config jira.password ...')"
59 | test -n "$JIRA_COMMIT_TEMPLATE" || JIRA_COMMIT_TEMPLATE="[%i]: %t"
60 |
61 |
62 | BRANCH_NAME=`git symbolic-ref HEAD | sed -e 's/refs\/heads\///'`
63 |
64 | if test -z "$BRANCH_NAME"
65 | then
66 | prepend "# Not committing from a branch"
67 | exit 0
68 | fi
69 |
70 | # is the branch a JIRA key?
71 | if [[ ! ( $BRANCH_NAME =~ ^[A-Z]+-[0-9]+$ ) ]]
72 | then
73 | prepend "# Branch does not appear to be a JIRA issue"
74 | exit 0
75 | fi
76 |
77 | get_cached_issue $BRANCH_NAME
78 |
79 | if test -z "$ISSUE_DESCR"
80 | then
81 | echo "Querying JIRA, this might take a while..."
82 | ISSUE_DESCR=`$JIRA_CMD --action getFieldValue --issue $BRANCH_NAME --field Summary | sed -e '1d'`
83 | SHOULD_CACHE=1
84 | fi
85 |
86 | if test -z "$ISSUE_DESCR"
87 | then
88 | prepend "# Branch appears to be a JIRA issue but we were unable to get its description"
89 | exit 0
90 | fi
91 |
92 | if test "$SHOULD_CACHE" = "1"
93 | then
94 | echo "$BRANCH_NAME: $ISSUE_DESCR" >> $CACHE_FILE
95 |
96 | fi
97 |
98 | prepend "# [$BRANCH_NAME]: $ISSUE_DESCR\n# View issue at $JIRA_SERVER/browse/$BRANCH_NAME"
99 |
100 | MSG=`echo $JIRA_COMMIT_TEMPLATE | awk -v i="$BRANCH_NAME" -v t="$ISSUE_DESCR" '{sub(/%i/,i); sub(/%t/,t); print}'`
101 | prepend_ok "#$MSG"
102 |
--------------------------------------------------------------------------------