Created
July 17, 2025 07:09
-
-
Save bschne/f0c015bf3ad30f3b6e690ece9c34f8c1 to your computer and use it in GitHub Desktop.
Putting this in your path gives you a git command, "git todo", that shows TODOs added between the current state of your working directory and a base branch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Usage: git todo [base-branch] [--allcase] [--strict] | |
| # Default base branch = develop. | |
| base=${1:-develop} | |
| shift || true | |
| match_case=0 # 0 = case-sensitive (ignore "todos"), 1 = case-insensitive | |
| strict=0 | |
| for arg in "$@"; do | |
| case "$arg" in | |
| --allcase) match_case=1 ;; | |
| --strict) strict=1 ;; | |
| esac | |
| done | |
| # Regex (POSIX BRE, portable for BSD awk) | |
| if (( strict )); then | |
| # Require a comment introducer before TODO (heuristic) | |
| re='\(//\|#\|;\|%\|--\|/\*\|<!--\)[^A-Za-z0-9]*TODO\([[:space:][:punct:]]\|$\)' | |
| else | |
| re='TODO\([[:space:][:punct:]]\|$\)' | |
| fi | |
| # Resolve merge base | |
| if ! mb=$(git merge-base "$base" HEAD 2>/dev/null); then | |
| echo "Error: could not find merge-base with '$base'." >&2 | |
| exit 1 | |
| fi | |
| # Portable AWK script: | |
| # - Track current file from "+++ b/..." line (handles renames, etc.) | |
| # - Parse new hunk start from "@@ -old +new @@" | |
| # - Emit file:line:text for added lines beginning with '+' | |
| awk_script=' | |
| BEGIN{file="";ln=0;} | |
| /^\+\+\+ /{ | |
| # example: +++ b/path/to/file | |
| file=$0; | |
| sub(/^\+\+\+ b\//,"",file); | |
| next; | |
| } | |
| /^@@ /{ | |
| # Extract +newstart | |
| # Format: @@ -oldstart,oldlen +newstart,newlen @@ ... | |
| # Strategy: find first "+" then read digits | |
| start=index($0,"+"); | |
| if(start>0){ | |
| rest=substr($0,start+1); | |
| # grab leading digits | |
| match(rest,/^[0-9]+/); | |
| if(RLENGTH>0){ln=substr(rest,RSTART,RLENGTH)+0;} else {ln=0;} | |
| } else {ln=0;} | |
| next; | |
| } | |
| { | |
| c=substr($0,1,1); | |
| if(c=="+"){ | |
| if(substr($0,1,3)=="+++") next; # skip file header | |
| text=substr($0,2); # added line text | |
| t=text; | |
| if(mc){ | |
| # case-insensitive: map to upper and test upper(RE) | |
| t=toupper(t); | |
| if(t ~ upre){print file ":" ln ":" text;} | |
| } else { | |
| if(text ~ re){print file ":" ln ":" text;} | |
| } | |
| ln++; | |
| } else if(c=="-"){ | |
| # removed line; do not increment ln | |
| next; | |
| } else { | |
| # context line | |
| ln++; | |
| } | |
| } | |
| ' | |
| # For case-insensitive mode we upper() both text and pattern. | |
| # Build uppercase version of the pattern (crude but works for ASCII TODO). | |
| upre=$(printf '%s\n' "$re" | tr '[:lower:]' '[:upper:]') | |
| # Force raw, parseable diff (ignore user diff.external / tool / textconv) | |
| DIFF_BASE_OPTS=(-c diff.external= -c diff.tool= diff --no-ext-diff --no-textconv --no-color -U0) | |
| run_scan () { | |
| local label=$1; shift | |
| echo "=== $label ===" | |
| if ! git "${DIFF_BASE_OPTS[@]}" "$@" | awk -vre="$re" -vupre="$upre" -vmc="$match_case" "$awk_script"; then | |
| echo "(error running diff)" >&2 | |
| fi | |
| echo | |
| } | |
| run_scan "Working tree (staged + unstaged vs HEAD)" HEAD | |
| run_scan "Commits since $base (merge-base $mb)" "$mb"...HEAD |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment