Skip to content

Instantly share code, notes, and snippets.

@Earendil95
Last active August 8, 2019 17:12
Show Gist options
  • Save Earendil95/b52955051fb2db5762a47708d4be4c6d to your computer and use it in GitHub Desktop.
Save Earendil95/b52955051fb2db5762a47708d4be4c6d to your computer and use it in GitHub Desktop.
Git hooks for automatic reference issues in commit

Description

This hooks will remind you to reference task in your commit, and remember your task ref for branch. Your commit messages will have style "[reference] message"

Usage

  1. Create two files in your repo - e.g. [PROJECT_ROOT]/hooks/prepare-commit-msg.rb and [PROJECT_ROOT]/hooks/post-checkout.rb
  2. Copy to first file (here will assume that this is a [PROJECT_ROOT]/hooks/prepare-commit-msg.rb):
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'fileutils'

MESSAGE_WITH_ISSUE_PATTERN = /^\[(#\d+)\]/ # Regexp that matches messages with specified issue
                                         # Remember that actual issue link will be taken from 
                                         # message.match(MESSAGE_WITH_ISSUE_PATTERN)[1]
                                         # so currrent example is for github issues references
ISSUE_PATTERN = /^#\d+$/ # Regexp that matches issue reference
                         # e.g. if you want to reference github issues it will be /^#\d+$/
                         # or may be you want to reference trello -- /^https:\/\/trello.com/.+/

# Have no idea why STDIN.reopen is not working, so this
def read_stdin
  open '/dev/tty' do |f|
    input = f.gets.chomp
    yield input
  end
end

def branch
  branch = File.read('.git/HEAD').match /^ref: refs\/heads\/(.+)/
  branch[1] if branch
end

def save_issue(number)
  return unless branch

  issue_number = number =~ /^TN-\d+$/ ? number : "TN-#{number}"
  File.write '.issues/ISSUE', issue_number
  issue_folder = ".issues/"
  subfolder = branch.split('/').to_a
  issue_folder += subfolder[0..(subfolder.size - 2)].join('/') if subfolder.size >= 2
  FileUtils.mkdir_p issue_folder
  File.write ".issues/#{branch}", issue_number
end

def cache_issue(number, force: false)
  if !force && File.exist?('.issues/ISSUE')
    return if number.to_s == File.read('.issues/ISSUE')

    puts "Do you want to update issue in branch? y(es)/anything else"

    read_stdin do |input|
      save_issue number if input.downcase.strip =~ /^y(?:es)?$/
    end
  else
    save_issue number
  end
end

def check_issue(file)
  msg = File.read(file)

  issue_match = msg.match MESSAGE_WITH_ISSUE_PATTERN
  unless issue_match.nil?
    cache_issue issue_match[1]
    exit 0
  end

  issue = File.read('.issues/ISSUE') if File.exist? '.issues/ISSUE'
  # after rebase we lose .issues/ISSUE
  if !branch.nil? && File.exist?(".issues/#{branch}") && (issue.nil? || issue.empty?)
    issue = File.read(".issues/#{branch}")
  end

  File.write(file, "[#{issue}] #{msg}") && exit(0) if !issue.nil? && !issue.empty?

  puts "Do you want to specify issue? Enter issue number (without TN- prefix) if yes " \
       "or anything else if no."

  read_stdin do |input|
    if input =~ ISSUE_PATTERN
      File.write file, "[TN-#{input}] #{msg}"
      cache_issue input, force: true
    end
  end
end

check_issue ARGV[0]

exit 0
  1. Specify your own regexp in MESSAGE_WITH_ISSUE_PATTERN and ISSUE_PATTERN which will satisfy your needs
  2. Copy to second file (assume that this is [PROJECT_ROOT]/hooks/post-checkout.rb):
#!/usr/bin/env ruby

issues = '.issues/'

branch = File.read('.git/HEAD').match /^ref: refs\/heads\/(.+)/

if !branch.nil? && File.exist?(issues + branch[1])
  issue = File.read issues + branch[1]
  puts "Current issue is #{issue}"
  File.write '.issues/ISSUE', issue
else
  puts "Current issue is not specified yet"
  File.write '.issues/ISSUE', ''
end

exit 0
  1. Create links:
ln -s ../../hooks/prepare-commit-msg.rb .git/hooks/prepare-commit-msg
ln -s ../../hooks/post-checkout.rb .git/hooks/post-checkout
  1. Add permissions:
chmod u+x .git/hooks/prepare-commit-msg
chmod u+x .git/hooks/post-checkout
  1. When you will run git commit -m "My awesome commit!" you will be asked for issue. For next time, your commit in same branch will marked automatically.
  2. If you want to redefine issue in branch, you just need to ref new issue. E.g. with default values (reference to GitHub) git commit -m "[#5] One more awesome commit". You will be asked if you want to update issue ref permanently or just for this commit.
@palkan
Copy link

palkan commented Sep 28, 2018

Fix for #save_issue to take into account branch names like "fix/bla/bla/bla":

if branch
  FileUtils.mkdir_p ".git/refs/issues/#{branch[1].gsub(/\/[^\/]+$/, '')}"
  File.write ".git/refs/issues/#{branch[1]}", number
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment