Skip to content

Commit d311333

Browse files
committed
Add pre-commit support
1 parent f409734 commit d311333

File tree

5 files changed

+150
-157
lines changed

5 files changed

+150
-157
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
This file was deleted.

.pre-commit-hooks.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- id: auto-smart-commit
2+
name: Auto Jira smart commit
3+
description: Automatically transform your Git commit messages into Jira smart commits
4+
entry: auto-smart-commit.py
5+
language: script
6+
always_run: true

README.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
1-
## Automated Jira smart commits
1+
## Auto Jira smart commit
22

3-
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).
3+
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).
44

5-
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:
5+
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:
66

77
| Command | Log entry |
88
| ------- | --------- |
9-
| 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. |
10-
| 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. |
11-
| 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$`. |
9+
| 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. |
10+
| 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. |
11+
12+
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.
1213

1314
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:
1415

1516
1. Separate subject from body with a blank line
1617
2. Limit the subject line to 50 characters
17-
3. Capitalize the subject line
18-
4. Do not end the subject line with a period
18+
3. Capitalize the subject line (automated)
19+
4. Do not end the subject line with a period (automated)
1920
5. Use the imperative mood in the subject line
2021
6. Wrap the body at 72 characters
2122
7. Use the body to explain what and why vs. how
2223

2324
## Installation
2425

25-
To install the git hooks in the directory `githooks`, run the following command from the root of your repository:
26+
### Installation with pre-commit
27+
28+
Add the following to your `.pre-commit-config.yaml` file:
29+
30+
```yaml
31+
repos:
32+
- repo: https://github.com/radix-ai/auto-smart-commit
33+
rev: v1.0.0
34+
hooks:
35+
- id: auto-smart-commit
36+
```
37+
38+
### Manual installation
39+
40+
Copy `auto-smart-commit.py` to a `githooks` directory in your repository, then run the following command from the root of your repository:
41+
2642
```bash
2743
git config --local core.hooksPath githooks
2844
```

auto-smart-commit.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#!/usr/bin/env python
2+
3+
import re
4+
import sys
5+
from datetime import datetime
6+
from math import floor
7+
from subprocess import check_output
8+
from typing import NoReturn, Optional
9+
10+
11+
def run_command(command: str) -> str:
12+
stdout: str = check_output(command.split()).decode("utf-8").strip()
13+
return stdout
14+
15+
16+
def current_git_branch_name() -> str:
17+
return run_command("git symbolic-ref --short HEAD")
18+
19+
20+
def extract_jira_issue_key(message: str) -> Optional[str]:
21+
project_key, issue_number = r"[A-Z]{2,}", r"[0-9]+"
22+
match = re.search(f"{project_key}-{issue_number}", message)
23+
if match:
24+
return match.group(0)
25+
return None
26+
27+
28+
def last_commit_datetime() -> datetime:
29+
# https://git-scm.com/docs/git-log#_pretty_formats
30+
git_log = "git log -1 --branches --format=%aI"
31+
author = run_command("git config user.email")
32+
last_author_datetime = run_command(f"{git_log} --author={author}") or run_command(git_log)
33+
if "+" in last_author_datetime:
34+
return datetime.strptime(last_author_datetime.split("+")[0], "%Y-%m-%dT%H:%M:%S")
35+
return datetime.now()
36+
37+
38+
def num_lunches(start: datetime, end: datetime) -> int:
39+
n = (end.date() - start.date()).days - 1
40+
if start < start.replace(hour=12, minute=0, second=0):
41+
n += 1
42+
if end > end.replace(hour=12, minute=45, second=0):
43+
n += 1
44+
return max(n, 0)
45+
46+
47+
def num_nights(start: datetime, end: datetime) -> int:
48+
n = (end.date() - start.date()).days - 1
49+
if start < start.replace(hour=1, minute=0, second=0):
50+
n += 1
51+
if end > end.replace(hour=5, minute=0, second=0):
52+
n += 1
53+
return max(n, 0)
54+
55+
56+
def time_worked_on_commit() -> Optional[str]:
57+
now = datetime.now()
58+
last = last_commit_datetime()
59+
# Determine the number of minutes worked on this commit as the number of
60+
# minutes since the last commit minus the lunch breaks and nights.
61+
working_hours_per_day = 8
62+
working_days_per_week = 5
63+
minutes = max(
64+
round((now - last).total_seconds() / 60)
65+
- num_nights(last, now) * (24 - working_hours_per_day) * 60
66+
- num_lunches(last, now) * 45,
67+
0,
68+
)
69+
# Convert the number of minutes worked to working weeks, days, hours,
70+
# minutes.
71+
if minutes > 0:
72+
hours = floor(minutes / 60)
73+
minutes -= hours * 60
74+
days = floor(hours / working_hours_per_day)
75+
hours -= days * working_hours_per_day
76+
weeks = floor(days / working_days_per_week)
77+
days -= weeks * working_days_per_week
78+
return f"{weeks}w {days}d {hours}h {minutes}m"
79+
return None
80+
81+
82+
def main() -> NoReturn:
83+
# https://confluence.atlassian.com/fisheye/using-smart-commits-960155400.html
84+
# Exit if the branch name does not contain a Jira issue key.
85+
git_branch_name = current_git_branch_name()
86+
jira_issue_key = extract_jira_issue_key(git_branch_name)
87+
if not jira_issue_key:
88+
sys.exit(0)
89+
# Read the commit message.
90+
commit_msg_filepath = sys.argv[1]
91+
with open(commit_msg_filepath, "r") as f:
92+
commit_msg = f.read()
93+
# Split the commit into a subject and body and apply some light formatting.
94+
commit_elements = commit_msg.split("\n", maxsplit=1)
95+
commit_subject = commit_elements[0].strip()
96+
commit_subject = f"{commit_subject[:1].upper()}{commit_subject[1:]}"
97+
commit_subject = re.sub(r"\.+$", "", commit_subject)
98+
commit_body = None if len(commit_elements) == 1 else commit_elements[1].strip()
99+
# Build the new commit message:
100+
# 1. If there is a body, turn it into a comment on the issue.
101+
if "#comment" not in commit_msg and commit_body:
102+
commit_body = f"{jira_issue_key} #comment {commit_body}"
103+
# 2. Add the time worked to the Work Log in the commit body.
104+
work_time = time_worked_on_commit()
105+
if "#time" not in commit_msg and work_time:
106+
work_log = f"{jira_issue_key} #time {work_time} {commit_subject}"
107+
commit_body = f"{commit_body}\n\n{work_log}" if commit_body else work_log
108+
# 3. Make sure the subject starts with a Jira issue key.
109+
if not extract_jira_issue_key(commit_subject):
110+
commit_subject = f"{jira_issue_key} {commit_subject}"
111+
# Override commit message.
112+
commit_msg = f"{commit_subject}\n\n{commit_body}" if commit_body else commit_subject
113+
with open(commit_msg_filepath, "w") as f:
114+
f.write(commit_msg)
115+
sys.exit(0)
116+
117+
118+
if __name__ == "__main__":
119+
main()

githooks/prepare-commit-msg

Lines changed: 0 additions & 147 deletions
This file was deleted.

0 commit comments

Comments
 (0)