Three suggestions for daily git workflow

In our quest for productivity, it’s easy to be lured by the allure of sophisticated tooling. However, complexity often comes at a cost, sometimes sidelining our actual projects for the sake of toolchain tweaks. That’s why I try to keep my setup minimalistic, aiming for the sweet spot where simplicity meets functionality. In this spirit, I want to share three adjustments I recently integrated into my workflow, offering a blend of improved practices without much overhead.

Start with the Commit Messages

Let’s face it, a bad commit message can be a real downer, especially when it’s your own! It’s crucial not just to document what was changed but to capture the why behind it. Despite the abundance of advice on crafting meaningful commit messages, many of us still fall short. Why? It often boils down to timing – we start writing the commit messages too late in the game.

What if I did the opposite:

  1. Draft Beforehand: Start by drafting your commit reason in a file (like CHANGELOG or .git_commit) using your favorite IDE, not a cramped text editor.
  2. Keep It Private: Add this file to your .gitignore to ensure it stays out of version control.
  3. Code With Purpose: With your intentions clearly outlined, proceed with your changes, then add your files.
  4. Commit With Clarity: Use git commit -F CHANGELOG to pull your polished message into the commit, enhancing both documentation and your focus on the task at hand.

This method not only improves your commit messages but also primes your mindset for the changes ahead.

Streamlining Commits with Git Alias

It is unlikely you can get your changes to be working in the first go. Your linting, testing etc will point out, your gaps. It is also unlikely, your coworkers will appreciate a review request with multiple of commit in it saying, “bug fixes”. And then if we forget squashing the commits before merging … there goes the project’s git history.

To simplify, consider git commit --amend. It has the useful --no-edit and -a flags to help tidy up your follow up edits beyond first commit. However, to keep your remote happy, you need to force push your changes. Summing it up, after every effort to fix your change is followed by

git commit -F CHANGELOG -a --amend --no-edit
git push origin BRANCH -f

This is where the git alias comes in. Run the following command

git config --global alias.cp '!f(){ \
    git commit -F CHANGELOG -a --amend --no-edit && \
    local branch=$(git rev-parse --abbrev-ref HEAD) && \
    git push --force-with-lease origin "$branch":"$branch"; \ 
}; f'

This gives us a new git command or alias – git cp, short for commit and push. A little reduction of friction between your iteration cycles that ensures your pull requests are tidy.
P.S. A pit fall to avoid – if you have added a new file that is outside your current working directory in the middle of your change, you’ll need to add it. But hopefully, your command prompt is configured to show the git branch and status, so that you catch that real quick.

➜  cryptogpt git:(main) ✗ git status
On branch main
Your branch is behind 'origin/main' by 13 commits, and can be fast-forwarded.
  (use "git pull" to update your local branch)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   requirements.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	Dockerfile

no changes added to commit (use "git add" and/or "git commit -a")
➜  cryptogpt git:(main) ✗ 

The ✗ tells me that I have un-commited changes. ohmyz.sh will do this right out of the box for you.

Git pre-commit hooks

And lastly, we have everyone’s favorite pre-commit hooks. Pre-commit hooks allows easy management of your commit hooks, has great documentation and all the hooks to satisfy the needs of a polyglot code base. It is great way to minimize noise in code reviews and standardize the whole team into best practices. Sometimes you have needs that are custom to your projects. And pre-commit-hooks makes it really easy to introduce and distribute such custom hooks.

Custom hook example – Ansible Inventory generator

Ansible inventories provide a great way of abstracting your implementations in a way that is similar to dependency injection. It lets us organize configuration data from higher level groups of servers, down to then more host specific configurations. This eliminates unnecessary when conditions (equivalent to if-else checks) and encourages abstractions for resiliency to changing business needs.
However, this results in the variable values to be distributed across multiple files all, group_vars/ , host_vars/ making it difficult to get a complete view of configuration values for each server until runtime. If you are deploying to a big fleet, incorrect values could get very costly. A shift-left solution here is to use the ansible-inventory command to generate all the values for each server in your inventory. Here is an example command that dumps the values to a file that can be easily reviewed whenever you are refactoring the inventory variables.

### contents of bin/generate_inventory.sh 
ansible-inventory -i inventories/dev --list -y 2>&1 > sed 1d dev_inventory

### example output
all:
  children:
    americas:
      children:
        nyc:
          hosts:
            server-01:
              variable: &id001
              - a
              - b
            server-02: *id001
	       ...

Checking the file into the git repo helps set a base level which you can compare anytime the variables are moved around. This is where we bring in a custom pre-commit hook.

To call the script at a pre-commit stage, add a section to your.pre-commit-config.yaml. This will now regenerate and overwrite the dev_inventory file every time we try to commit.

 - repo: local
   hooks:
     - id: inventory
       name: Ansible inventory generation
       entry: bin/gen_inventory.sh
       language: system

If the generated file differs from the existing one, git will see an un-staged file preventing the commit. This gives you the opportunity to review the diff and gives you early feedback before executing the code. Shift left and catch the drift or bugs even before it reaches CI.
If you want multiple teams or projects to share this, you can push it to a git repository and point repo to it.

P.S.
Some times, you need an escape hatch to just commit without evaluation, git commit --no-verify will bypass the hooks and let you commit.

Wrapping Up

Retooling can be a constant chase for productivity. Instead an approach could be evidence based. Asking does this help us move left, fail early and fail fast is a powerful guiding principle to evaluate if this investment of time will compound over time to help us ship software faster. Hoping this helps someone, and they leave behind what they have found useful in improving their git workflow. Here’s to continuous improvement and the joy of coding!

Leave a Reply

Your email address will not be published. Required fields are marked *