Skip to content

Instantly share code, notes, and snippets.

@elmart
Created May 9, 2014 18:53
Show Gist options
  • Save elmart/8d6dc48ebdc8d04ae5cb to your computer and use it in GitHub Desktop.
Save elmart/8d6dc48ebdc8d04ae5cb to your computer and use it in GitHub Desktop.
Git prepare-commit-msg hook to automatically add task context headings
#!/bin/bash
#
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# This hook automatically inserts Task/Subtask headings into commit messages.
# Each line in .git/task file gets added as a level.
# This is a prepare-commit time hook, so headings are already there
# when the commit message is presented to the user after issuing 'git commit'.
# That means headings (full message content in fact) can still be edited
# if desired before really committing, which will occur when saving and quitting.
# Quitting with an empty message will abort the commit, as usual.
#
# For example, if contents of .git/task file is
# Simplify memory management
# Use safe functions
# Then, when you issue 'git commit', the message presented to you
# will already include:
# Simplify memory management: Use safe functions:.
# So that you only have to fill up the last (Step) part.
# As you can see above, it also adds a dot at the end, so that you can
# quickly type '$i', fill up the Step, and be sure there's a dot at the end
# withouth actually having to type it.
#
# You can manually edit .git/task file if you want.
# But, to populate it more comfortably, you can add this alias to git config:
# [alias]
# task = !vi .git/task
# That way, issuing 'git task' at the command line will open an editor in which you
# can change current task/subtasks.
# If you remove .git/task file, or leave it empty, this does nothing.
#
# This also works when the message was already populated. For example, after
# issuing a git rebase -i that chose to rename some commit.
# In that case, it checks due headings are there, and adds them before the
# existing message if not.
# If the existing message starts with "LOCAL", then it doesn't add the headings.
# That allows you having some local commits, that you never publish, but that
# you systematically rebase on top of your branches for something to work
# different locally just for you.
# All these changes to existing message apply only to the first line. Other existing
# lines are untouched.
#
# As a last thing, it can optionally add the project's name before the headings.
# This is disabled by default.
#
# @author elmart<[email protected]>
hook_dir="$(dirname "$0")"
project_dir="$(cd "${hook_dir}/../.." && pwd)"
project_name="$(basename "$project_dir")"
project_name_separator=" - "
add_project_name=0
task_file="$hook_dir/../task"
task_separator=": "
temp_file=`mktemp /tmp/gitpreparecommithook.XXXXXX`
awk -v project_name="$project_name" \
-v project_name_separator="$project_name_separator" \
-v add_project_name="$add_project_name" \
-v task_file="$task_file" \
-v task_separator="$task_separator" '
function ltrim(s) { sub(/^[ \t\r\n]+/, "", s); return s }
function rtrim(s) { sub(/[ \t\r\n]+$/, "", s); return s }
function trim(s) { return rtrim(ltrim(s)); }
BEGIN {
current_field = 1
message_prefix = ""
message = ""
}
NR == 1 && /^LOCAL/ {
print
next
}
NR == 1 {
# Calculate message prefix from task file, if any
if (system("test -f " task_file) == 0) {
while ((getline task_file_line < task_file) > 0) {
message_prefix = message_prefix task_file_line task_separator
}
}
# Recognize project name and project name separator
if (tolower($current_field) == tolower(project_name) \
&& trim($(current_field + 1)) == trim(project_name_separator)) {
current_field += 2
}
# Get rest of message
while (current_field <= NF) {
message = message == "" ? $current_field : message " " $current_field
current_field++
}
# Remove message prefix from message if already present
i = index(message, message_prefix)
if (i > 0) {
message = substr(message, 1, i - 1) substr(message, i + length(message_prefix))
}
# Add message prefix to message
message = message_prefix message
# Add full-stop to end of message if not present
if (substr(message, length(message), 1) != ".") {
message = message "."
}
# Print resulting message
if (add_project_name) {
print project_name project_name_separator message
}
else {
print message
}
}
NR > 1 && !/^git-svn-id:/ {
print
}
' "$1" > "$temp_file"
if [ $? -eq 0 ]
then
cp "$temp_file" "$1"
else
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment