Git workflow for Rust developers: from first commit to code review

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Git workflow is a lifesaver for Rust projects (and your sanity)

\n\n

Have you ever tried to revert a "broken" commit in a Rust project that accidentally touches src/main.rs, Cargo.toml, and three other modules? Sound familiar? If so, you are definitely not alone. Rust is known for its strictness, but without a proper Git workflow, even the most perfect code turns into a mess.

\n\n

In this article, I will show you how to build a Git workflow that will save you hours of debugging and nerves. We will go through real Rust examples, talk about CI/CD, code review, and how not to lose face in front of your colleagues. Let's go!

\n\n

Basics of Git workflow for Rust: where to start

\n\n

Before diving into the details, let's establish the basics. For Rust projects, I recommend using GitHub Flow or Git Flow — depending on the team size. But there is one specific thing: Rust requires strict typing and tests, so your workflow should be tailored for cargo test and cargo clippy.

\n\n

Branch structure for Rust

\n\n

Here is the minimum set of branches I use in my projects:

\n\n
    \n
  • main — stable version, always ready for release
  • \n
  • develop — integration branch for features
  • \n
  • feature/feature-name — each new feature is isolated
  • \n
  • hotfix/name — for urgent fixes
  • \n
\n\n

Why is this important for Rust? Because cargo check and cargo build can reveal dependency conflicts that are easier to resolve in an isolated branch than in a shared one.

\n\n

Example: creating a feature branch for a new function

\n\n

Let's say you are writing a JSON parser in Rust. You start from the main branch:

\n\n
git checkout main\ngit pull origin main\ngit checkout -b feature/json-parser
\n\n

Now you write the code. Here is a simple example of a function you add to src/parser.rs:

\n\n
use serde_json::Value;\n\npub fn parse_json(input: &str) -> Result<Value, String> {\n    serde_json::from_str(input).map_err(|e| e.to_string())\n}
\n\n

Before committing, be sure to run the tests:

\n\n
cargo test\ncargo clippy -- -D warnings
\n\n

If everything is fine — make the commit:

\n\n
git add src/parser.rs Cargo.toml\ngit commit -m "feat: add basic JSON parser with error handling"
\n\n

Notice the commit format: feat: — this is part of Conventional Commits, which work great with Rust projects, especially if you use automatic changelog generation.

\n\n

CI/CD for Rust: automated code checking

\n\n

Without CI, your Git workflow is just a set of branches. For Rust, I strongly recommend adding a file .github/workflows/ci.yml to the project root (if you use GitHub):

\n\n
name: Rust CI\n\non:\n  push:\n    branches: [ "main", "develop" ]\n  pull_request:\n    branches: [ "main" ]\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v3\n    - name: Build\n      run: cargo build --verbose\n    - name: Run tests\n      run: cargo test --verbose\n    - name: Lint\n      run: cargo clippy -- -D warnings\n    - name: Format check\n      run: cargo fmt -- --check
\n\n

Now every push to main or develop will trigger builds, tests, and linting. If CI fails — the commit is not merged. This is bulletproof protection against "it works on my machine".

\n\n

Why this will save your project

\n\n

Imagine: you forgot to update Cargo.lock after adding a dependency. CI will immediately catch it — cargo build will throw an error. Or you accidentally used unwrap() in production code — clippy with the -D warnings

Blogs

Book Recommendations