Skip to content

Commit

Permalink
Add pre-commit support
Browse files Browse the repository at this point in the history
  • Loading branch information
lsorber committed Jan 1, 2020
1 parent f409734 commit d311333
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 157 deletions.
1 change: 0 additions & 1 deletion .gitignore

This file was deleted.

6 changes: 6 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- id: auto-smart-commit
name: Auto Jira smart commit
description: Automatically transform your Git commit messages into Jira smart commits
entry: auto-smart-commit.py
language: script
always_run: true
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
## Automated Jira smart commits
## Auto Jira smart commit

This [`prepare-commit-msg`](https://git-scm.com/docs/githooks#_prepare_commit_msg) Git hook transforms your Git commit messages into [Jira smart commits](https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html).
This [pre-commit](https://pre-commit.com/) hook transforms your Git commit messages into [Jira smart commits](https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html).

After naming your branch after a [Jira issue key](https://confluence.atlassian.com/adminjiraserver073/changing-the-project-key-format-861253229.html) such as `ML-42`, the hook will automatically format your commit message into a Jira smart commit:
If your branch name contains a [Jira issue key](https://confluence.atlassian.com/adminjiraserver073/changing-the-project-key-format-861253229.html) such as `ABC-123`, the hook will automatically format your commit message into a Jira smart commit:

| Command | Log entry |
| ------- | --------- |
| git commit -m "open the pod bay doors." | ML-42 Open the pod bay doors<br><br>Jira #time 0w 0d 2h 8m Open the pod bay doors<br><br>_Effect:_ Logs the time since your last commit on any branch in the Work Log tab. |
| git commit -m "Open the pod bay doors<br><br>I should get back inside, so I must open the pod bay doors." | ML-42 Open the pod bay doors<br><br>Jira #comment I should get back inside, so I must open the pod bay doors.<br><br>Jira #time 0w 0d 2h 8m Open the pod bay doors<br><br>_Effect:_ Posts a comment to the Jira issue and logs the time since your last commit in the Work Log tab. |
| git commit | ML-42 d$:<If applied, this commit will "Open the pod bay doors"><br><br>Jira #comment d$:<What does this commit do, and why?><br><br>Jira #time 0w 0d 2h 8m Open the pod bay doors<br><br>_Effect:_ Edit the smart commit with your favourite editor before publishing it. Since the default is usually Vim, we remind the user how to delete a line starting from the cursor with `d$`. |
| git commit -m "release the kraken." | ABC-123 Release the kraken<br><br>ABC-123 #time 0w 0d 2h 8m Release the kraken<br><br>_Effect:_ Logs the time since your last commit on any branch in the Work Log tab. |
| git commit -m "Release the kraken<br><br>A kraken lives in dark depths, usually a sunken rift or a cavern filled with detritus, treasure, and wrecked ships." | ABC-123 Release the kraken<br><br>ABC-123 #comment A kraken lives in dark depths, usually a sunken rift or a cavern filled with detritus, treasure, and wrecked ships.<br><br>ABC-123 #time 0w 0d 2h 8m Release the kraken<br><br>_Effect:_ Posts a comment to the Jira issue and logs the time since your last commit in the Work Log tab. |

If the branch name does not contain a Jira issue key, the commit message is not modified. The time logged takes into account non-working hours such as lunch breaks and nights.

See [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) for an explanation of the seven rules of a great Git commit message:

1. Separate subject from body with a blank line
2. Limit the subject line to 50 characters
3. Capitalize the subject line
4. Do not end the subject line with a period
3. Capitalize the subject line (automated)
4. Do not end the subject line with a period (automated)
5. Use the imperative mood in the subject line
6. Wrap the body at 72 characters
7. Use the body to explain what and why vs. how

## Installation

To install the git hooks in the directory `githooks`, run the following command from the root of your repository:
### Installation with pre-commit

Add the following to your `.pre-commit-config.yaml` file:

```yaml
repos:
- repo: https://github.com/radix-ai/auto-smart-commit
rev: v1.0.0
hooks:
- id: auto-smart-commit
```
### Manual installation
Copy `auto-smart-commit.py` to a `githooks` directory in your repository, then run the following command from the root of your repository:

```bash
git config --local core.hooksPath githooks
```
119 changes: 119 additions & 0 deletions auto-smart-commit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env python

import re
import sys
from datetime import datetime
from math import floor
from subprocess import check_output
from typing import NoReturn, Optional


def run_command(command: str) -> str:
stdout: str = check_output(command.split()).decode("utf-8").strip()
return stdout


def current_git_branch_name() -> str:
return run_command("git symbolic-ref --short HEAD")


def extract_jira_issue_key(message: str) -> Optional[str]:
project_key, issue_number = r"[A-Z]{2,}", r"[0-9]+"
match = re.search(f"{project_key}-{issue_number}", message)
if match:
return match.group(0)
return None


def last_commit_datetime() -> datetime:
# https://git-scm.com/docs/git-log#_pretty_formats
git_log = "git log -1 --branches --format=%aI"
author = run_command("git config user.email")
last_author_datetime = run_command(f"{git_log} --author={author}") or run_command(git_log)
if "+" in last_author_datetime:
return datetime.strptime(last_author_datetime.split("+")[0], "%Y-%m-%dT%H:%M:%S")
return datetime.now()


def num_lunches(start: datetime, end: datetime) -> int:
n = (end.date() - start.date()).days - 1
if start < start.replace(hour=12, minute=0, second=0):
n += 1
if end > end.replace(hour=12, minute=45, second=0):
n += 1
return max(n, 0)


def num_nights(start: datetime, end: datetime) -> int:
n = (end.date() - start.date()).days - 1
if start < start.replace(hour=1, minute=0, second=0):
n += 1
if end > end.replace(hour=5, minute=0, second=0):
n += 1
return max(n, 0)


def time_worked_on_commit() -> Optional[str]:
now = datetime.now()
last = last_commit_datetime()
# Determine the number of minutes worked on this commit as the number of
# minutes since the last commit minus the lunch breaks and nights.
working_hours_per_day = 8
working_days_per_week = 5
minutes = max(
round((now - last).total_seconds() / 60)
- num_nights(last, now) * (24 - working_hours_per_day) * 60
- num_lunches(last, now) * 45,
0,
)
# Convert the number of minutes worked to working weeks, days, hours,
# minutes.
if minutes > 0:
hours = floor(minutes / 60)
minutes -= hours * 60
days = floor(hours / working_hours_per_day)
hours -= days * working_hours_per_day
weeks = floor(days / working_days_per_week)
days -= weeks * working_days_per_week
return f"{weeks}w {days}d {hours}h {minutes}m"
return None


def main() -> NoReturn:
# https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html
# Exit if the branch name does not contain a Jira issue key.
git_branch_name = current_git_branch_name()
jira_issue_key = extract_jira_issue_key(git_branch_name)
if not jira_issue_key:
sys.exit(0)
# Read the commit message.
commit_msg_filepath = sys.argv[1]
with open(commit_msg_filepath, "r") as f:
commit_msg = f.read()
# Split the commit into a subject and body and apply some light formatting.
commit_elements = commit_msg.split("\n", maxsplit=1)
commit_subject = commit_elements[0].strip()
commit_subject = f"{commit_subject[:1].upper()}{commit_subject[1:]}"
commit_subject = re.sub(r"\.+$", "", commit_subject)
commit_body = None if len(commit_elements) == 1 else commit_elements[1].strip()
# Build the new commit message:
# 1. If there is a body, turn it into a comment on the issue.
if "#comment" not in commit_msg and commit_body:
commit_body = f"{jira_issue_key} #comment {commit_body}"
# 2. Add the time worked to the Work Log in the commit body.
work_time = time_worked_on_commit()
if "#time" not in commit_msg and work_time:
work_log = f"{jira_issue_key} #time {work_time} {commit_subject}"
commit_body = f"{commit_body}\n\n{work_log}" if commit_body else work_log
# 3. Make sure the subject starts with a Jira issue key.
if not extract_jira_issue_key(commit_subject):
commit_subject = f"{jira_issue_key} {commit_subject}"
# Override commit message.
commit_msg = f"{commit_subject}\n\n{commit_body}" if commit_body else commit_subject
with open(commit_msg_filepath, "w") as f:
f.write(commit_msg)
sys.exit(0)


if __name__ == "__main__":
main()
147 changes: 0 additions & 147 deletions githooks/prepare-commit-msg

This file was deleted.

0 comments on commit d311333

Please sign in to comment.