RobotForge
Published·~12 min

Git & GitHub for hardware projects

Versioning code is solved. Versioning the URDF, the firmware, the CAD, and the BOM together — that's the part that bites every hobbyist. Here's the layout that scales.

by RobotForge
#foundations#git#tools

A robot project isn't one repo of code. It's code, firmware, a URDF, several STL files, a Bill of Materials, calibration data, and 50 wiring diagrams. Treating all of it as "Git stuff" pays off the moment your first contributor shows up.

The repo layout that works

my-robot/
├── README.md
├── LICENSE
├── docs/
│   ├── wiring.md
│   └── photos/
├── hardware/
│   ├── cad/                 # native CAD files (FreeCAD, Onshape exports, .step)
│   ├── meshes/              # STL/OBJ for visualization
│   └── pcb/                 # KiCad project files
├── firmware/
│   ├── platformio.ini
│   └── src/                 # ESP32/STM32 source
├── ros2_ws/src/my_robot/
│   ├── description/         # URDF, Xacro
│   ├── launch/
│   └── config/
├── scripts/                 # one-off scripts
├── .gitignore
└── .gitattributes

One repo, one history, one issue tracker. Resist the urge to split firmware and ROS workspace into separate repos until you actually have separate teams.

The .gitattributes that saves your hard drive

Binary files (STL, STEP, KiCad, photos) bloat history. Use Git LFS or, more simply, mark them so Git doesn't try to diff them:

# .gitattributes
*.stl filter=lfs diff=lfs merge=lfs -text
*.step filter=lfs diff=lfs merge=lfs -text
*.STEP filter=lfs diff=lfs merge=lfs -text
*.bin binary
*.png binary
*.jpg binary

# But text-based formats stay diffable:
*.urdf text
*.xacro text
*.yaml text
*.json text

Then git lfs install once. Large mesh updates don't blow up your repo.

The .gitignore that keeps secrets out

# build artifacts
build/
install/
log/
.cache/
*.pyc
__pycache__/

# IDE
.vscode/
.idea/

# secrets — never commit
.env
.env.local
*.pem
*.key
calibration_secret.yaml

# big data — track separately
data/
bag_files/
*.bag
*.mcap

The single biggest hardware-Git mistake: committing a calibration file with the robot's serial number, MAC, or OAuth token. Pre-commit hook recommended (see below).

Branches for hardware revisions

Software branches are easy: feature branches, merge to main. Hardware introduces a wrinkle: you might have v1 hardware in the field and v2 hardware in development. Two reasonable patterns:

Pattern A: tag releases

Use git tags for hardware revisions. v1.0-rc1, v1.0, v2.0. The robot's firmware reports its version on boot; debugging a fielded robot starts with checking out the tag matching its version.

Pattern B: long-lived branches

If hardware versions diverge significantly (different MCUs, different protocols), maintain branches: hw/v1, hw/v2. Cherry-pick fixes between them. Heavier process but unavoidable for some lifecycles.

Big binaries: the four options

Option When to use
Git LFSSTLs, photos, occasional bag files. Default choice.
DVCML training data, large datasets. More overhead than LFS but better for ML pipelines.
S3 + symlinkBag files larger than 100 MB. Track an S3 path in Git, fetch on demand.
Git submodulesA separate repo for meshes that's reused across projects.

Pre-commit hooks worth adding

Use the pre-commit Python tool. .pre-commit-config.yaml:

repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: check-added-large-files
        args: ["--maxkb=2000"]
      - id: check-yaml
      - id: check-merge-conflict
      - id: detect-private-key
      - id: end-of-file-fixer

  - repo: https://github.com/python/black
    rev: 24.0.0
    hooks:
      - id: black

  - repo: https://github.com/pre-commit/mirrors-clang-format
    rev: v18.1.0
    hooks:
      - id: clang-format
        types_or: [c++, c]

pre-commit install once and these run on every commit. The "detect private key" hook alone has saved me from leaking SSH keys twice.

CI for robots

What's worth running in CI for a hardware project:

  • Build the firmware. Catches "compiles on my machine" before the robot can't flash.
  • Build the ROS workspace. Same, for the ROS side. colcon build in a Docker container.
  • Run unit tests. Mock the hardware interface; test the controller logic in isolation.
  • Validate the URDF. check_urdf my_robot.urdf catches syntax errors and disconnected trees.
  • Run a quick simulator regression. Drive the robot in Gazebo headless for 30 seconds; assert it didn't crash.

GitHub Actions YAML for the basic version is short enough to copy into your project today.

Why this matters more than for pure-software projects

When a software bug causes a regression, you fix it and push. When a robotics bug causes a regression, the robot might already be in a customer's hands, or in a robot wired with v1 hardware that you don't have anymore. You need to be able to check out exactly the state you shipped, including the URDF, the calibration, and the firmware version. That's a Git problem, and it's the discipline that separates "hobby project" from "shippable robot."

What I'd do for a fresh project today

  1. Use the layout above. Don't bikeshed; copy it.
  2. Set up Git LFS for STL/STEP/KiCad files in the first commit.
  3. Add the pre-commit config, run pre-commit install.
  4. Write a minimal CI workflow that builds firmware + ROS, runs urdf check.
  5. Tag every meaningful state with a hardware revision (v0.1-prototype, v0.2-fixed-bearings, etc.).

That's a half-day of setup that pays back the first time a contributor walks in or you have to debug a fielded robot.

Next

Linear algebra refresher (robotics edition) — back to the math foundations.

Comments

    Sign in to post a comment.