Adding a Stopwatch Feature to My Bash Module: A Developer’s Journey Through Branches, Tests, and Mild Chaos

A new feature joins my Bash module: a stopwatch that measures how long each shell command takes. In this post I walk through the whole process—planning, branching, coding, testing, updating the installer, and preparing a pull request—all with a healthy dose of fun.

Adding a Stopwatch Feature to My Bash Module: A Developer’s Journey Through Branches, Tests, and Mild Chaos

If you’ve been following the saga of my little Bash module — a tiny script that started as a joke, escaped into the wild, and now pretends to be a real piece of software — you know that we already published it on GitHub, gave it proper documentation, added tests, built a shiny installer, and even created an uninstaller (because apparently people like reversible actions).

But every hero’s journey needs a second act.
And every Bash module, no matter how small, eventually wakes up one morning and whispers:

“Hey… what if I had a new feature?”

So today, dear traveler, we’re adding one.

Grab your towel. Things are about to get nerdy.

1. Why Add a Stopwatch?

I wanted something fun, something useful, and something not too annoying.
(Three things that rarely appear together in Bash.)

The idea struck me:
A stopwatch that measures how long each shell command takes.

Every time you run something — ls, docker run, sudo rm -rf / (please don’t) — the module quietly records how long it took. Then, right before the next prompt appears, it prints a small timing note.

Something like:

⏱  Completed in: 143ms
$ _

Is it necessary?
No.
Is it cool?
Absolutely.
Is it a perfect excuse to teach feature development on GitHub?
You bet.

2. Planning the Feature (Yes, We Pretend to Be Professional)

Before writing anything, let's outline what has to be done:

✔ Add new stopwatch logic

This means:

  • A function that records the start time before each command.
  • A function that calculates the duration after each command.
  • A display function that prints the result before the prompt.

Thanks to bash-preexec.sh, which already handles the “before” and “after” hooks for shell commands, integrating this feature is much easier.

✔ Add configuration options

Users must be able to turn the stopwatch on or off:

STOPWATCH_ENABLED="true"

Maybe later:

  • Color output
  • Precision control
  • Display format options (“seconds”, “ms”, “galactic years”?)

But for now — on/off is enough.

✔ Add tests

We need:

  • Unit tests for the stopwatch functions
  • Tests verifying the timing appears in the console
  • Tests for config on/off
  • Tests for installer behavior (since dependencies are now required)

✔ Update the installer

The installer must check that:

  • date supports nanoseconds (many shells do)
  • bash-preexec.sh is installed
  • configs are patched correctly

In other words:
It should confirm the user’s computer isn’t secretly a potato.

✔ Write documentation

No feature exists until it is documented.
Otherwise it’s just a rumor.

✔ Create a feature branch

We don’t dump code into main.
That way lies madness and broken terminals.

✔ Open a pull request

Where a future version of me — or you — can review it.

3. Creating a Feature Branch (The Official Developer Ritual)

We start with the classic Git spell:

git checkout -b feature/timers

And just like that, a new parallel universe exists.
In this universe:

  • You can break the code
  • Fix the code
  • Break it again
  • Add debugging printouts
  • Cry
  • Fix it again

…and none of it affects the main project.

This is the power of branches.

4. Writing the Stopwatch Logic (Now the Module Gets Smarter)

Thanks to bash-preexec.sh, adding timing hooks is easy.

Before the command runs:

hhgttg_preexec() {
    if [[ "$STOPWATCH_ENABLED" == "true" ]]; then
        COMMAND_START_TIME=$(date +%s%N)
    fi
}

After the command finishes:

hhgttg_precmd() {
    if [[ "$STOPWATCH_ENABLED" == "true" && -n "$COMMAND_START_TIME" ]]; then
        local end_time=$(date +%s%N)
        local elapsed=$(( (end_time - COMMAND_START_TIME) / 1000000 ))
        printf "⏱  Completed in: %dms\n" "$elapsed"
    fi
}

Boom.
Timing is now built into the module.

Will it work perfectly?
Probably not the first time.
But that’s why we have tests!

5. Adding a Config Switch (Power to the Users)

In hhgttg.config.sh:

# Enables or disables the stopwatch
STOPWATCH_ENABLED="true"

Users can toggle it:

STOPWATCH_ENABLED="false"

No need to uninstall the module just to stop the timer from judging your slow commands.

6. Updating the Installer (Because No Feature Stands Alone)

Now the installer needs to ensure timing is possible.

What might go wrong?

  • The system has an outdated date command without nanoseconds
  • bash-preexec.sh didn’t download correctly
  • The config file is missing the new variable
  • Someone is running the script on… macOS (brace for sed differences)

So we add:

command -v date >/dev/null 2>&1 || die "date command missing!"

And maybe:

if ! date +%s%N >/dev/null 2>&1; then
    echo "Warning: nanosecond timing not supported. Stopwatch disabled."
fi

Installer updated.
Module smarter.
User slightly more prepared.

7. Writing Tests (Where Features Become Real)

Inside test/test_stopwatch.bats:

@test "stopwatch prints timing" {
    export STOPWATCH_ENABLED="true"
    run bash -c 'source hhgttg.sh; sleep 0.1'
    assert_output --partial "Completed in"
}

Configuration test:

@test "stopwatch disabled" {
    export STOPWATCH_ENABLED="false"
    run bash -c 'source hhgttg.sh; ls'
    refute_output --partial "Completed in"
}

Installer test:

@test "installer adds config entry" {
    run ./install.sh
    grep -q "STOPWATCH_ENABLED" "$HOME/.hhgttg/hhgttg.config.sh"
}

Now we break everything until every test passes.
Then celebrate.

8. Committing the Progress (One Small Commit for Bash, One Giant Leap for My Module)

git add .
git commit -m "Add stopwatch feature with config + tests + installer updates"

If tests fail, repeat the cycle:
break → cry → fix → commit.

9. Opening a Pull Request (The Moment of Truth)

Back on GitHub:

  • Open Pull Requests
  • Select feature/stopwatchmain
  • Add title:
    “Add stopwatch feature”
  • Add description:
    What was added, why, how it works
  • Add checklist
  • Add screenshots
  • Add code snippets

If this were a team project, now someone else would review it.

But since I am the team — reviewer, developer, QA, designer, and motivational speaker — I simply review it myself, pretend I’m very strict, approve it, then merge.

10. Merge and Celebrate (Preferably With Coffee)

Once merged:

  • The feature becomes part of the main code
  • The documentation is live
  • The tests validate everything
  • The installer supports the new functionality

You now have a shiny new stopwatch in your shell.

And your Bash prompt is officially more useful (and more judgmental) than before.

Conclusion

Adding a new feature to a Bash module is not just typing random characters into your .sh file until something stops breaking (although sometimes it feels like that).

The real workflow is:

  1. Think through the feature
  2. Create a branch
  3. Write the feature
  4. Add configuration
  5. Update documentation
  6. Write tests
  7. Update the installer
  8. Commit in small steps
  9. Open a pull request
  10. Merge when ready

By following this flow, even small projects grow in a clean, professional, maintainable way — and you get a chance to learn real development practices while building something fun.

Read next

Git Hooks: Automating Git Tasks with Custom Scripts

Git hooks are a powerful feature that allows you to automate tasks during the lifecycle of a Git repository. Hooks are scripts that are triggered by specific Git events, such as making a commit or pushing code. They enable you to enforce coding standards, run tests, and other automated task

Exploring Commits, Blobs, Trees, and Tags in Git

Git is a powerful version control system built on four core objects: commits, blobs, trees, and tags. These objects are fundamental to Git’s storage model, and understanding them provides valuable insights into how Git operates.

Step-by-Step with Git: Advanced Commit Strategies

Let’s use a practical example to guide us through this process. By the end of this tutorial, you'll understand how to commit changes selectively, review modifications, and manage staged and unstaged changes.