Skip to content

Commit 890713d

Browse files
committed
Add Support for Current Ticket in Branch
- Add parsing of the current git branch (fails gracefully) to extract a prefixed ticket ID from the branch name - Add output to the CLI to make user aware of unfinished TODOs for their current branch
1 parent 6c890b8 commit 890713d

File tree

7 files changed

+872
-62
lines changed

7 files changed

+872
-62
lines changed

.devcontainer/CLAUDE.md

381 Bytes
Binary file not shown.

README.md

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,34 @@ repos:
3030
args: ['-j', 'MYJIRA,PROJECT,TEAM']
3131
```
3232
33+
### Recommended Configurations
34+
35+
#### For Maximum TODO Visibility (Recommended)
36+
Get both violations AND branch-specific TODOs, check all files:
37+
38+
```yaml
39+
repos:
40+
- repo: https://github.com/lasp/prevent-dangling-todos
41+
hooks:
42+
- id: prevent-dangling-todos
43+
args: ['-j', 'MYJIRA', '-v']
44+
always_run: true # Check all files for branch-specific TODOs
45+
verbose: true # Always show output
46+
```
47+
48+
#### For Non-Blocking Alerts with Branch Tracking
49+
Alert about TODOs without blocking commits:
50+
51+
```yaml
52+
repos:
53+
- repo: https://github.com/lasp/prevent-dangling-todos
54+
hooks:
55+
- id: prevent-dangling-todos
56+
args: ['-j', 'MYJIRA', '--succeed-always', '-v']
57+
always_run: true # Check all files for branch-specific TODOs
58+
verbose: true # Always show output even when hook succeeds
59+
```
60+
3361
### Custom Comment Types
3462
3563
Check only specific comment types:
@@ -64,20 +92,60 @@ repos:
6492
args: ['-j', 'MYJIRA', '-q']
6593
```
6694
67-
**Verbose Mode**: Shows configuration, violations, file status summary, and help text
95+
**Verbose Mode**: Shows configuration, violations, file status summary, help text, and branch-specific TODOs
96+
```yaml
97+
repos:
98+
- repo: https://github.com/lasp/prevent-dangling-todos
99+
hooks:
100+
- id: prevent-dangling-todos
101+
args: ['-j', 'MYJIRA', '-v']
102+
```
103+
104+
### Branch-Specific TODO Tracking
105+
106+
The tool automatically detects your current git branch and extracts ticket IDs that match your configured Jira prefixes. When TODOs reference the current branch's ticket, they are displayed separately as informational warnings (yellow ⚠️) rather than violations (red ❌).
107+
108+
#### Checking All Files (Including Untouched)
109+
110+
By default, pre-commit only runs on modified files. To catch branch-specific TODOs in files that haven't been changed, configure the hook to **always run**:
111+
112+
#### Complete Branch Tracking Configuration
113+
114+
For full branch-specific TODO tracking, use both verbose and always_run:
115+
68116
```yaml
69117
repos:
70118
- repo: https://github.com/lasp/prevent-dangling-todos
71119
hooks:
72120
- id: prevent-dangling-todos
73121
args: ['-j', 'MYJIRA', '-v']
122+
always_run: true
123+
verbose: true # Ensure output is always shown
74124
```
75125
126+
**Example branch detection:**
127+
- Branch: `feature/MYJIRA-123-user-auth`
128+
- Detected ticket: `MYJIRA-123`
129+
- TODOs with `MYJIRA-123` will be shown as warnings, not violations
130+
131+
**Note:** Branch detection messages are only shown in verbose mode or when there are issues detecting the branch.
132+
76133
### Alert Without Blocking
77134

78135
Alert developers to dangling TODOs without blocking commits. When using `--succeed-always`, the hook will always return exit code 0 (success), but by default pre-commit hides output from successful hooks. To ensure TODO violations are still visible, you should configure verbose output:
79136

80-
**Recommended: Always show output (persistent configuration)**
137+
**Recommended: Always show output with branch tracking**
138+
```yaml
139+
repos:
140+
- repo: https://github.com/lasp/prevent-dangling-todos
141+
hooks:
142+
- id: prevent-dangling-todos
143+
args: ['-j', 'MYJIRA', '--succeed-always', '-v']
144+
always_run: true # Check all files for branch-specific TODOs
145+
verbose: true # Always show output, even when hook succeeds
146+
```
147+
148+
**Alternative: Basic alert without branch tracking**
81149
```yaml
82150
repos:
83151
- repo: https://github.com/lasp/prevent-dangling-todos
@@ -87,7 +155,7 @@ repos:
87155
verbose: true # Always show output, even when hook succeeds
88156
```
89157

90-
**Alternative: Show output only when needed (runtime flag)**
158+
**Runtime flag approach (minimal configuration)**
91159
```yaml
92160
repos:
93161
- repo: https://github.com/lasp/prevent-dangling-todos
@@ -167,6 +235,11 @@ TODO, FIXME, XXX, HACK, BUG, REVIEW, OPTIMIZE, REFACTOR
167235
```
168236
❌ file.py:15: # TODO: Missing Jira reference
169237
❌ file.py:23: # FIXME: Another comment without ticket
238+
239+
240+
⚠️ Unresolved TODOs for current branch ticket MYJIRA-123:
241+
⚠️ auth.py:10: # TODO MYJIRA-123: Complete OAuth implementation
242+
⚠️ auth.py:25: # FIXME MYJIRA-123: Handle token refresh edge case
170243
```
171244
172245
**Quiet Mode**: No output (silent)
@@ -178,12 +251,18 @@ TODO, FIXME, XXX, HACK, BUG, REVIEW, OPTIMIZE, REFACTOR
178251
❌ file.py:15: # TODO: Missing Jira reference
179252
❌ file.py:23: # FIXME: Another comment without ticket
180253

254+
⚠️ Unresolved TODOs for current branch ticket MYJIRA-123:
255+
⚠️ auth.py:10: # TODO MYJIRA-123: Complete OAuth implementation
256+
⚠️ auth.py:25: # FIXME MYJIRA-123: Handle token refresh edge case
257+
181258
✅ clean_file.py
182259
❌ file.py
183260

184261
💡 Please add Jira issue references to work comments like:
185262
// TODO MYJIRA-123: Implement user authentication
186263
# FIXME MYJIRA-124: Handle edge case for empty input
264+
265+
Note: No ticket ID detected in current branch 'main'
187266
```
188267
189268
## Troubleshooting
@@ -202,7 +281,17 @@ TODO, FIXME, XXX, HACK, BUG, REVIEW, OPTIMIZE, REFACTOR
202281
- Check that your comment format matches: `# TODO JIRA-123: Description`
203282
- Ensure the Jira prefix matches your configuration
204283
205-
4. **False positives**
284+
4. **Branch-specific TODOs not appearing**
285+
- **Solution**: Use `verbose: true` in your config
286+
- Use `always_run: true` to check all files, not just modified ones
287+
- Ensure your branch name contains a valid ticket ID (e.g., `feature/MYJIRA-123-description`)
288+
289+
5. **Branch detection not working**
290+
- Ensure you're in a git repository with a valid branch
291+
- Check that your branch name includes a ticket ID matching your Jira prefixes
292+
- Branch detection messages appear in verbose mode or when there are detection issues
293+
294+
6. **False positives**
206295
- Use `-c/--comment-prefix` to check only specific comment types
207296
- Consider using `-q/--quiet` mode for silent operation in CI/CD pipelines
208297
- Use `-v/--verbose` mode when you need detailed information about what files are being checked

prevent_dangling_todos/cli.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import argparse
44
import os
5+
import re
6+
import subprocess
57
import sys
6-
from typing import List, Optional
8+
from typing import List, Optional, Tuple
79

810
from prevent_dangling_todos.prevent_todos import TodoChecker
911

@@ -134,6 +136,62 @@ def _parse_comma_separated(value: Optional[str]) -> Optional[List[str]]:
134136
return parsed if parsed else None
135137

136138

139+
def _get_current_git_branch() -> Tuple[Optional[str], Optional[str]]:
140+
"""
141+
Get the current git branch name.
142+
143+
Returns
144+
-------
145+
tuple of (Optional[str], Optional[str])
146+
(branch_name, error_message) - Returns (None, error_msg) if detection fails
147+
"""
148+
try:
149+
result = subprocess.run(
150+
["git", "rev-parse", "--abbrev-ref", "HEAD"],
151+
capture_output=True,
152+
text=True,
153+
timeout=5,
154+
)
155+
if result.returncode == 0:
156+
branch = result.stdout.strip()
157+
if branch:
158+
return branch, None
159+
return None, "Unable to detect current git branch"
160+
return None, "Unable to detect current git branch"
161+
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
162+
return None, "Unable to detect current git branch"
163+
164+
165+
def _extract_ticket_id(branch_name: str, jira_prefixes: List[str]) -> Optional[str]:
166+
"""
167+
Extract a ticket ID from a branch name if it matches any of the Jira prefixes.
168+
169+
Parameters
170+
----------
171+
branch_name : str
172+
The git branch name
173+
jira_prefixes : list of str
174+
List of valid Jira project prefixes
175+
176+
Returns
177+
-------
178+
str or None
179+
The ticket ID (e.g., "LIBSDC-123") if found, otherwise None
180+
"""
181+
if not branch_name or not jira_prefixes:
182+
return None
183+
184+
# Build a pattern to match any of the Jira prefixes followed by a dash and numbers
185+
# This will match patterns like LIBSDC-123 anywhere in the branch name
186+
for prefix in jira_prefixes:
187+
pattern = rf"{re.escape(prefix)}-\d+"
188+
match = re.search(pattern, branch_name)
189+
if match:
190+
return match.group(0)
191+
192+
return None
193+
194+
137195
def main(argv: Optional[List[str]] = None) -> None:
138196
"""
139197
Main entry point for the script.
@@ -198,17 +256,35 @@ def main(argv: Optional[List[str]] = None) -> None:
198256
file=sys.stderr,
199257
)
200258

259+
# Detect current git branch and extract ticket ID
260+
branch_name, branch_error = _get_current_git_branch()
261+
current_ticket_id = None
262+
branch_detection_msg = None
263+
264+
if branch_error:
265+
branch_detection_msg = f"Note: {branch_error}"
266+
elif branch_name:
267+
current_ticket_id = _extract_ticket_id(branch_name, final_jira_prefixes)
268+
if not current_ticket_id:
269+
branch_detection_msg = f"Note: No ticket ID detected in current branch '{branch_name}'"
270+
201271
# Initialize checker with configuration
202272
checker = TodoChecker(
203273
jira_prefixes=final_jira_prefixes,
204274
quiet=args.quiet,
205275
verbose=args.verbose,
206276
comment_prefixes=final_comment_prefixes,
207277
succeed_always=final_succeed_always,
278+
current_ticket_id=current_ticket_id,
208279
)
209280

210281
# Check files and exit with appropriate code
211282
exit_code = checker.check_files(args.files)
283+
284+
# Show branch detection message if needed (not in quiet mode)
285+
if branch_detection_msg and not args.quiet:
286+
print(f"\n{branch_detection_msg}")
287+
212288
sys.exit(exit_code)
213289

214290

prevent_dangling_todos/prevent_todos.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(
4545
quiet: bool = False,
4646
verbose: bool = False,
4747
succeed_always: bool = False,
48+
current_ticket_id: Optional[str] = None,
4849
):
4950
"""
5051
Initialize the TodoChecker.
@@ -61,6 +62,9 @@ def __init__(
6162
If True, show detailed output including config, violations, file status, and help. Default is False.
6263
succeed_always : bool, optional
6364
If True, always exit with code 0 even when violations are found. Default is False.
65+
current_ticket_id : str, optional
66+
The ticket ID for the current branch (e.g., "LIBSDC-123"). If provided, TODOs
67+
matching this ticket will be tracked separately for informational output.
6468
"""
6569
# Always store as list internally
6670
if isinstance(jira_prefixes, str):
@@ -72,6 +76,8 @@ def __init__(
7276
self.verbose = verbose
7377
self.succeed_always = succeed_always
7478
self.exit_code = 0
79+
self.current_ticket_id = current_ticket_id
80+
self.ticket_todos: list[tuple] = [] # Track TODOs for the current ticket
7581

7682
# Comment prefixes that should require Jira references
7783
if comment_prefixes is None:
@@ -140,6 +146,9 @@ def check_file(self, file_path: str) -> List[Tuple[int, str]]:
140146
# Check if it also contains a Jira reference
141147
if not self.jira_pattern.search(line):
142148
violations.append((line_num, line.rstrip()))
149+
# If we have a current ticket, check if this TODO is for it
150+
elif self.current_ticket_id and self.current_ticket_id in line:
151+
self.ticket_todos.append((file_path, line_num, line.rstrip()))
143152
except Exception as e:
144153
print(f"⚠️ Warning: Could not read {file_path}: {e}", file=sys.stderr)
145154

@@ -187,13 +196,20 @@ def check_files(self, file_paths: List[str]) -> int:
187196
for line_num, line_content in violations:
188197
print(f"❌ {file_path}:{line_num}: {line_content}")
189198

199+
# Show ticket-specific TODOs with yellow warning (not in quiet mode)
200+
if self.ticket_todos and not self.quiet:
201+
print("") # Blank line before ticket TODOs
202+
print(f"⚠️ Unresolved TODOs for current branch ticket {self.current_ticket_id}:")
203+
for file_path, line_num, line_content in self.ticket_todos:
204+
print(f"⚠️ {file_path}:{line_num}: {line_content}")
205+
190206
# Verbose mode: Show file status summary and help text
191207
if self.verbose:
192208
print("") # Blank line before summary
193209
for file_path, is_clean in file_statuses:
194210
status_icon = "✅" if is_clean else "❌"
195211
print(f"{status_icon} {file_path}")
196-
212+
197213
# Show help text only if violations were found
198214
if self.exit_code == 1:
199215
print("")

0 commit comments

Comments
 (0)