How I work with Version Control: Tools, Practices, and Personal Workflow
Table of Contents
How I work with Version Control: Tools, Practices, and Personal Workflow #
Sharing experience is a great way to reflect on your own practices and learn. I’ve decided to record my practices of working based on last 8 years of experience in software engineering. This is a series of notes where I document how I work with version control, what tools I use, how do I manage my workstations, and how do I engineer software.
The first note in this series is about the core of the software engineering and the most important tool in the developer’s toolbox - version control systems. I am going to share how I work with version control, what practices I follow, what habits I have. Hope you will find it useful.
Version control #
It’s hard to imagine writing software without a version control. It’s a nutshell of the software engineering and it took me a while to develop what works best for me.
Let me start with my understanding of why should a good engineer use version control.
Version control is a source of truth. What’s in the repository is what you get in production.
Version control is a great audit tool. It helps you to understand what has been changed, when and why.
Version control in a team of software engineers is a collaboration tool. It helps to coordinate work, to review changes, and finally build things.
Version control is a tool. Never let your version control get in the way-configure it to work for you.
Perhaps, this is not a complete list of why, but this is what important for me.
Git #
I use Git. It’s the standard de facto, and a skill to work with Git is a must-have for a software engineer.
Git CLI vs GUIs #
I feel more productive with the CLI. I tried GUIs, several times, including GitHub Desktop, GitKraken, GitButler, VCS in IntelliJ IDEA, and others. Yet, once I mastered the CLI, I could not find a reason to switch back. CLI gives you more freedom by automation and scripting.
Branching model #
A short article describing what I’ve lead Trunk-based development with short lived branches. It cannot be easier. It cannot be more streamlined. It requires discipline within a team. You need to ensure and communicate the importance of these approaches and then establish the habits.
Trunk with rebase is the best I’ve tried so far. I don’t want to start another holy war and argue why you should use rebase, but probably it’s worth trying.
Atomic commits #
I am doing my best to make commits atomic. Make changes self-contained, as small as posbbile, and logically separated. It’s easier to review, easier to understand, easier to revert. A great practice I really recommend to follow.
Commit messages #
Until I am not an author of a library, that is used by millions of developers, I would not bother to follow a commit message specification or convention. I think simple and clear message describing what has been changed and why is more than enough.
Stacked pull / merge requests #
In a situation where I can’t make an atomic change within a single PR, I prefer to stacked PRs. Learn how to do so. Make life of your reviewers easier. Reviewing a PR with a small set of changes is much easier than a PR that contains tons of different additions and deletions.
General practices and habits #
Frequently pull changes from trunk. How frequent depends on the structure of repository and how often your team pushes the changes to main / master. I start my day with pulling changes and resolving conflicts if any. Communicate with your team, avoid integration challenges.
Consice commit messages. A commit message is not a novel and it’s not just a “fix”. Communicate what have been changed, explain why you did it.
GitHub, and other platforms #
I keep my personal repositories on GitHub. I want to at least mirror some of them to Codeberg, but I am not sure if I will do it in the nearest future. I like GitHub a lot. Collaboration features became better in the last years.
I was a little nerdy and pedantic about the configuration once I learned Terraform / OpenTofu, so all my repositories are managed in OpenTofu. Even this very repository is created using one-line change in this file.
Configuration #
My .gitconfig is splitted into the several files. A personal.gitconfig I use when I work on my personal projects, and a work.gitconfig I use when I work for my daily job. To achieve this, I use the following configuration in my .gitconfig:
[includeIf "gitdir:~/personal/"]
path = .config/git/personal.gitconfig
[includeIf "gitdir:~/work/"]
path = .config/git/work.gitconfig
[user]
name = Nikita Barskov
This is really great, because you can configure different email addresses, use different signing methods.
1Password SSH agent is a great tool to manage your SSH keys and use it with Git.
Global ignore files #
I’ve learned that from my collegue. And it’s actually great. Create a global .gitignore and keep there things you don’t want to see in your repositories.
- OS-specific files,
- IDE-specific files,
- and other things.
Create a file .config/git/ignore and populate it with what you think is right for you.
Configuration for trunk-based development #
There are parts of my .gitconfig that I configured specifically to make my life with a trunk-based development easier.
- Set auto remote setup to
true. This is a great feature that allows you to push your branch to the remote and set up the tracking branch with a single command.
[push]
autoSetupRemote = true
- Set rebase by default for pull.
[pull]
rebase = true
- Use SSH to access remotes and for signing commits and tags.
[gpg]
format = ssh
[gpg "ssh"]
program = "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
[commit]
gpgsign = true
signoff = true
[tag]
gpgsign = true
signoff = true
[user]
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAWKGfG1hnaUAyfKkCa2ELVwO+2Z9FMCj3ysC0I6uTzi
[url "ssh://[email protected]/"]
insteadOf = https://github.com/
Useful aliases #
In addition to configuration, I use several aliases to be more productive.
git bt- creates a branch with a prefix. In my scenario I use my name and current datetime:
[alias]
bt = "!bt() { git checkout -b nikita.barskov-$(date +"%Y%m%dT%H%M%S")-$1;}; bt"
git gone- deletes all branches that are gone on the remote:
[alias]
gone = ! "git fetch -p && git for-each-ref --format '%(refname:short) %(upstream:track)' | awk '$2 == \"[gone]\" {print $1}' | xargs -r git branch -D"
Working with Pull Requests and code reviews #
The most important rule of a Pull Request for me: a change should be profitable. It’s not about the number of lines of code, or elegance of the solution. It’s always about the value.
I really like a Playbook from Google and agree with the points there. Try to follow them as much as possible.
Code Review are also supposed to be a respectful and constructive environment. You should realise that a person made a change did it with the best abilities and intentions, while a review has limited context and should first understand the reasons before commenting.
Pull Request Automations #
I rely on GitHub CLI a lot. An extremely powerful and useful tool to enhance your workflow.
For instance, I rely on Dependabot, and sometimes I find it useful to list specifically Dependabot PRs. This is how you do it:
gh pr list --authot "app/dependabot"
Sometimes, I want to batch close PRs:
gh pr list --json "number" --author app/dependabot | jq -r '.[].number' | xargs -I {} gh pr close
Deployment and version control #
I am trying my best to set the correct foundation for the good engineering, therefore I prefer to have CI / CD pipelines in place beforehand. Some things machines can do better than humans, and continuous integration and continuous deployment are for sure among them.
Conclusion #
I prefer simplicity and pragmatism in everything I do. Finding a balance between the over-engineering and poor design is a key. I hope you find this note useful and can take something for yourself.
In the next article I will share how I manage to incorporate LLMs into my version control routines.