Issue #2: The Most Important Tool in Your Toolchain

pocketDevOps Newsletter

Welcome to the "pocket DevOps" newsletter.

This is a copy of the newsletter sent to subscribers' inboxes on March 8, 2026.

Not subscribed yet? Want the next issue delivered straight to your inbox? Join the newsletter here.

Last time we talked about documentation. Today, let's talk about the single most important tool in your toolchain. The one tool that, if you're not using it yet, you should start with today.

Git.

I know, I know. You've heard about Git before. Maybe you're already using it. Maybe you tried it once, got confused, and went back to copying project folders named project_v3_final_FINAL2. No judgment. I've been there too.

Let me tell you why Git deserves a second look. Especially if you're working on embedded systems projects in a small team.

Experimentation Needs a Safety Net

Let me connect two ideas first.

Agile gives you processes to reduce project risk. It's about adapting to changing requirements instead of pretending you can predict the future.

DevOps gives you the tools to make those processes actually work in practice.

One of the core Agile principles is making decisions based on real data, not assumptions. Where does real data come from? Experiments. You try something, observe the result, and adjust.

Now, here's the key question. How do you iterate safely? How do you try a risky change to your firmware without the fear of breaking what already works? You need the ability to experiment, fail, and roll back to a known good state. Quickly and reliably.

That's exactly what Git gives you.

What Problems Does Git Actually Solve?

Let's look at this from several angles.

For the team - Git gives you a single source of truth for your project files. Everyone works from the same codebase. No more "which version is the latest?" conversations. No more emailing zip files around. One place. One truth.

For the engineer - Git gives you a safe playground for experiments. Want to try a different approach to your SPI driver? Create a branch and experiment freely. If it doesn't work out, your stable code is still sitting right there on the main branch. Untouched.

For automation - CI/CD pipelines need something to trigger them. They need a defined source to build from. Without version control, automated builds and tests are practically impossible to set up reliably.

For disaster recovery - Git gives you backup and restore as a built-in feature. Every clone of your repository is a full backup. Your laptop dies? Clone the repo again and you're back in business.

How to Work with Git

Let me be clear about something. This article won't teach you Git from scratch. If that's what you need, here are some excellent resources to start with:

And if you ever want to peek under the hood and see what's actually inside that .git folder:

What I want to do here is show you several aspects of Git that I find particularly valuable for embedded projects. Git is flexible. There's no single "correct" workflow. But some features and practices have made a real difference in my work, and I think they can make a difference in yours too.

Git Config - Start Here

Git has two main levels of configuration: global (for all your repositories) and local (per repository). At minimum, set your user.name and user.email. These appear as the author in every commit you make.

git config --global user.name "Your Name"
git config --global user.email "your@email.com"

Everything else can stay at defaults. Change settings as your preferences evolve.

Want to see how the experts set up their Git? Check these out:

Remote Repository - Your Single Source of Truth

You need a central place where your repository lives. A single source of truth that your team syncs with. You have options:

  • Bare repository on a server or network drive. Simple. No frills. Works.
  • Self-hosted Git server like Gogs. Lightweight. Easy to set up. Gives you a web interface.
  • Cloud platforms like GitLab or GitHub. Full-featured. Probably overkill for most small teams, but convenient if you don't want to maintain anything yourself.

Pick the simplest option that meets your needs. You can always upgrade later.

How to Organize Your Project in Git

This is a classic debate. There are two main approaches.

Monorepo: Everything lives in one repository. Firmware, libraries, tools, documentation. Simple to manage. Easy to keep everything in sync. One clone and you have the entire project.

Multirepo: Each component gets its own repository. Firmware in one place, shared libraries in another, hardware files in a third. More flexible. Better for reusing components across projects.

My preference? A hybrid approach. Monorepo for the main project, with shared libraries as separate repositories pulled in via Git submodules.

Branching Strategy

How you organize your branches matters. It affects how you collaborate, how you release, and how safely you can experiment.

There are many branching strategies out there. Trunk-based development. Git Flow. GitHub Flow. Feature branching. The right choice depends on your team size, release cadence, and project complexity.

Don't overthink it. Start simple. One main branch plus feature branches is enough for most small teams. Add complexity only when you actually need it.

Development Branches - Your Daily Workspace

This is the part I want you to really pay attention to.

Work on a development branch. Always. Never commit directly to main. Your development branch is your personal playground. Your safe space for daily work and experiments.

Why? Because mistakes happen. You start a refactor, go too far, and now nothing compiles. On a dev branch, no problem. Just discard your changes and you're back to a known good state. On main? You just broke the build for everyone.

Here's how I think about development branches:

One branch per feature. This keeps you focused on a single goal. When you're done, merge it. Start a new branch for the next thing. Clear boundaries. Clear purpose.

Commit often. Every time you have something working, commit it. Think of commits as save points in a video game. You wouldn't play for hours without saving, right? Same principle. Save your progress so you can restore it if things go wrong.

Use commit messages as a work log. Write down what you did, when, and why. It's documentation that happens naturally as part of your workflow. When you look back in three months, you'll thank yourself.

Clean up before merging. Your development branch history can be messy. That's fine. But before you merge to main, squash or rebase your commits into logical, meaningful units. A clean history on main is worth the effort.

Work to be interruptible. Commit frequently so that when your colleague needs help, or when a production issue comes up, you can switch context without losing your work.

Intentional Commits and Commit Messages

A commit should be a single logical change. It means one commit does one thing. If you fixed a bug in the SPI driver and also reformatted the UART module, that's two commits, not one. If you added a new sensor reading function and wrote the unit test for it, that can be one commit - because the test and the code are part of the same logical unit.

Here's a simple test. Can you describe what the commit does in one sentence without using the word "and"? If you can't, you're probably stuffing too much into one commit.

In practice, this means you sometimes need to split your work consciously. You're fixing a bug and notice a typo in a comment nearby? Fix the bug in one commit. Fix the typo in another. It feels pedantic at first. But it pays off every single time you need to trace back through your history.

A good commit message follows a simple format:

Fix: [SPI] Prevent data corruption at clock speeds above 8 MHz

The SPI driver was not inserting a delay between chip select
assertion and the first clock edge. At higher clock speeds,
the slave device missed the first bit. Added a configurable
setup time to fix the issue.

The subject line tells you what changed. The body tells you why. That's it. When you're debugging an issue six months from now and running git log, you'll appreciate commits that actually explain themselves.

Smaller, focused commits also make it easier to:

  • Debug: Isolate bugs to specific changes.
  • Review: Give your reviewer a coherent piece of work to evaluate.
  • Revert: Undo a specific change without affecting everything else.

Resources to learn more:

Collaboration and Code Review

Even in small teams, code review matters. In embedded projects, where a bug can mean a hardware recall or a safety issue, having someone else look at your code before it reaches main is not a luxury. It's risk management.

Git makes this straightforward:

  • Use git diff to review changes between commits or branches.
  • Use git log to review commit history.
  • If you're using GitLab or GitHub, use their built-in code review tools with merge/pull requests.

And remember - code review isn't about finding fault. It's about sharing knowledge and catching things that the author's brain filters out because they've been staring at the same code for hours.

Worktree - Better Parallel Work

Ever needed to quickly check something on another branch while you're in the middle of work? git switch or git checkout forces you to stash or commit your current changes first. Annoying.

git worktree lets you have multiple branches checked out simultaneously in different directories. You keep working on your feature in one folder while reviewing a colleague's branch in another. No stashing. No context switching overhead.

This is one of those Git features that most people don't know about, but once you try it, you'll wonder how you worked without it.

Submodules - Managing External Dependencies

In embedded projects, you often depend on vendor libraries, shared components, or tools maintained elsewhere. Git submodules let you include external repositories inside your project while keeping precise control over which version you're using.

Here's a typical scenario. You've built a communication protocol library that works well. Now your second product needs it too. Without submodules, you'd copy the code. Then you fix a bug in one copy and forget to update the other. Sound familiar?

With submodules, that library lives in its own repo. Both firmware projects include it as a submodule, pinned to a specific commit. You fix the bug once, and each project pulls the update when it's ready. Deliberately, not accidentally.

LFS - Handling Large Binary Files

Embedded projects attract binary files like magnets. Vendor SDKs with precompiled libraries. FPGA bitstreams. Production firmware images you keep for reference. PCB design files. Maybe even test data recordings from the lab. These files are large, they change over time, and they don't diff like source code.

Regular Git doesn't handle large binaries well. Every version of every binary file gets stored in your repository history forever. Your repo grows. Cloning slows down. Everyone suffers.

Git LFS (Large File Storage) solves this. It replaces large files with lightweight pointer files in your repo while storing the actual binary data on a separate server. Your repo stays lean. Cloning stays fast.

Incorporate Git LFS early in your workflow. Adding it later, after your repo is already bloated with binary history, is a pain you don't want to experience.

Ignoring Files - Keep Your Repo Clean

Not everything belongs in version control. Build artifacts, IDE temporary files, OS-specific files - these pollute your repository and cause unnecessary merge conflicts.

Git gives you two mechanisms for ignoring files. They serve different purposes, and understanding the difference will save you from arguments with your teammates about what belongs in the repo.

.gitignore - For files that are project-specific. Build outputs, generated files, binaries. Everyone on the team should ignore these. Commit this file to your repo.

# Build artifacts
*.bin
*.hex
*.elf
/build/
Debug/
Release/

# Generated files
*.o
*.d
*.map

.git/info/exclude - For files specific to your personal environment. Your IDE settings, your OS files (.DS_Store, Thumbs.db), your personal scripts. These don't belong in .gitignore because they're your problem, not the project's.

A few practical tips:

  • Create .gitignore at the start of your project. Don't add it after you've already committed files you shouldn't have.
  • Test your ignore patterns before committing. Run git status and make sure the right files are excluded.
  • Document your versioning rules in the README so new team members know what goes in and what stays out.

More info:

Tagging Releases

You get a bug report from production. The customer says: "It's broken." You want to reproduce the issue. How do you make sure you're running the exact same firmware version that's in the customer's hands?

Git tags. Tag every production release with a version number.

git tag -a v1.2.0 -m "Production release for ClientX, PCB rev3"

Now you can check out v1.2.0, build the same binary, and debug on your bench with confidence that you're looking at the exact same code.

But this only works if you also know what version is running in production. Embed the version string in your firmware. Print it on boot. Log it. Make it accessible. Without this link between the tag in Git and the firmware running on the device, tags are useless.

Automating Stuff with Hooks

Git hooks are scripts that run automatically at specific points in your Git workflow. They're a simple but powerful way to enforce quality gates without relying on discipline alone.

The most useful hooks for embedded projects:

  • pre-commit - Run code formatting checks, linting, or unit tests before allowing a commit. Catches issues before they enter your history.
  • commit-msg - Verify that your commit message follows the team's convention. Enforce a ticket number. Reject lazy "fix" messages.
  • pre-push - Run integration tests or static analysis before code leaves your machine. The last line of defense before your changes affect others.

Use a framework like pre-commit to manage your hooks. Define them in a YAML file. Store that file in your repo. Now the entire team uses the same quality gates automatically.

Keep hooks fast. If a pre-commit hook takes 30 seconds, developers will bypass it with --no-verify. If it takes 2 seconds, they'll barely notice it.

Debugging with Git Bisect

You've discovered a regression. Something that worked last week is now broken. But you've made 50 commits since then. Which one broke it?

git bisect uses binary search to find the offending commit. You mark a known good commit and a known bad commit. Git checks out the middle one. You test. You mark it good or bad. Git narrows it down. In about 6 steps, you've found your culprit.

Even better - if you can write a script that tests for the bug automatically, you can run git bisect run ./test-script.sh and let Git find the problem for you while you grab a coffee.

This is where those small, descriptive commits pay off. When git bisect points you to a commit that says "refactored timer initialization to support low-power mode", you know exactly where to look. When it points to "various changes", good luck.

git blame is another useful debugging companion. It shows you who changed each line and when. Not for finger-pointing - for understanding context. Why was this line changed? What commit introduced it? What was the reasoning?

GitOps - Push to Deploy

Here's an interesting idea for embedded projects. What if pushing to a specific remote repository could trigger a deployment? Not necessarily to production hardware (though that's possible with OTA), but to a test bench. Or a staging environment. Or a documentation server.

GitOps uses Git as the single source of truth not just for code, but for infrastructure and deployment state. You push a change. Automation takes over. The new firmware gets built, tested, and deployed to your test devices.

This connects directly to the pocket DevOps philosophy. Your Git repository becomes the control center for your entire development workflow.

Your Next Step

If you're not using Git yet, start today. Initialize a repository for your current project. Make your first commit. Push it to a remote. That's your single source of truth now.

If you're already using Git, pick one thing from this article that you haven't tried yet. Worktrees. Bisect. Hooks. LFS. Try it on your project this week.

Git is one of those tools where the basics get you 80% of the value. But that remaining 20%? That's where the real productivity gains hide. And the only way to find them is to experiment.


That's it for this newsletter. I'd love to hear what you think about it. Just hit reply and tell me.

And see you in the next one.

Jakub Zawadzki