├── 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 | --------------------------------------------------------------------------------