A Simple Bash Script to Find Your Forgotten TODOs

I admit it. I’m one of the worst offenders of the “never leave TODOs” rule. With the intention of quickly finishing a story, I’ll write a TODO comment and then promptly forget about it. Often, a teammate will bring it up again during a code review, and I can address it then. However, there are times when a TODO comment can slip into the codebase and never be seen again.

In order to combat this problem, I spent a few minutes writing a short shell function to find the TODO comments I’ve left behind. I’ve developed a habit to run it frequently and try and address some of the TODOs that I’ve left.

If you’re interested in running this yourself, I would recommend ripgrep and GNU Parallel. Both of these can be installed via Homebrew with brew install ripgrep parallel.

The script

# Function Style
todo() {
  TODOS=`rg -i TODO --vimgrep`
  BLAME_CMDS=`awk '{ split($1,arr,":"); print "\"git blame -f -n -L"  arr[2] "," arr[2], arr[1] "\""; }' <<< "$TODOS"`
  ALL_BLAMES=`xargs -n1 bash -c <<< $BLAME_CMDS`
  MY_BLAMES=`rg "'git config --global user.name'" <<< $ALL_BLAMES`
  echo "$MY_BLAMES"
}

# One-line
rg -i TODO --vimgrep | awk '{split($1,arr,":"); print "\"git blame -f -n -L"  arr[2] "," arr[2], arr[1] "\""}' | xargs -n1 bash -c | rg "`git config --global user.name`

For quicker execution (and another dependency), the xargs -n1 bash -c step can be replaced with GNU Parallel:

# Function Style
todo() {
  TODOS=`rg -i TODO --vimgrep`
  BLAME_CMDS=`awk '{ split($1,arr,":"); print "git blame -f -n -L"  arr[2] "," arr[2], arr[1]; }' <<< "$TODOS"`
  ALL_BLAMES=`parallel <<< $BLAME_CMDS`
  MY_BLAMES=`rg "'git config --global user.name'" <<< $ALL_BLAMES`
  echo "$MY_BLAMES"
}

# One-line
rg -i TODO --vimgrep | awk '{split($1,arr,":"); print "git blame -f -n -L"  arr[2] "," arr[2], arr[1] }' | parallel | rg "`git config --global user.name`

Note that this removes the need for escaped quotes in the awk print command.

The output of these commands looks like this:

8f2193719 server/services/supplier-commitment-record-service.ts 62 (Dan Kelch 2019-05-13 17:11:30 -0400 81)   // TODO: The distinct flag isn't working (these queries are getting data-loaded together), so I'm adding the uniq/uniqBy as a temporary measure. Don't copy this patten, we should be able to filter distinct values in SQL.

6e5c8aeead src/app/orders/reports/reducers.ts 11 (Dan Kelch 2018-10-31 10:25:17 -0400 11)  * DK TODO 2018-10-31 - I don't love this, I was hoping to have each reducer in

4a37c4f7bb test/acceptance/customer-orders/reconciliation-test.ts 95 (Dan Kelch 2017-10-12 12:18:21 -0400 96)   // TODO adding a supplier order to a customer order and seeing the data on the page update

As you can see, the blame output has a commit hash (from when it was last modified), file name, line number, author name, timestamp, and context where the TODO shows up.

To break down each step of the command:

  1. rg -i TODO --vimgrep"

    rg (ripgrep) is my favorite search tool. It’s blazing-fast and easy to use. The -i flag denotes a case-insensitive search. The --vimgrep flag changes the output of the search to look like this: path/to/file:line-number:colum-number matching string.

  2. awk '{split($1,arr,":"); print "git blame -f -n -L"  arr[2] "," arr[2], arr[1] }'"

    Awk is a really handy tool, and I’ve only barely scratched the surface of it. This piece is doing two things: splitting the first argument (denoted by the $1) by a semi-colon, and putting the results into an array named arr. arr[1] will be the filename, and arr[2] will be the line number.

    Then, it’s printing a string, starting with "git blame -f -n -L and inserting elements of arr into the output. The printed output will look like "git blame -f -n -L 123:123 path/to/file" (assuming the line number is 123).

    The git blame command is quoted to help with the next step when executing the command. The -f and -n flags indicate that the filename and line number should be part of the output. The -L 123:123 designates that git blame should only look at line 123 (the line where the found search term shows up).

  3. xargs -n1 bash -c"

    This runs bash -c for each line of the input. This will execute the "git blame -f -n -L 123:123 path/to/file" command that was generated from awk. As I mentioned before, this could be replaced with GNU Parallel, for quicker execution.

  4. rg "`git config --global user.name`"

    This first runs git config --global user.name to get the user’s name, and then searches the input for lines matching this. This narrows the scope down to TODOs written by me, and not by my teammates.

Here’s to getting those TODOs crossed off my list.