Why Git workflow is a lifesaver for Rust projects (and your sanity)
\n\nHave 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.
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\nBasics of Git workflow for Rust: where to start
\n\nBefore 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.
Branch structure for Rust
\n\nHere 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
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.
Example: creating a feature branch for a new function
\n\nLet's say you are writing a JSON parser in Rust. You start from the main branch:
git checkout main\ngit pull origin main\ngit checkout -b feature/json-parser\n\nNow you write the code. Here is a simple example of a function you add to src/parser.rs:
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\nBefore committing, be sure to run the tests:
\n\ncargo test\ncargo clippy -- -D warnings\n\nIf everything is fine — make the commit:
\n\ngit add src/parser.rs Cargo.toml\ngit commit -m "feat: add basic JSON parser with error handling"\n\nNotice the commit format: feat: — this is part of Conventional Commits, which work great with Rust projects, especially if you use automatic changelog generation.
CI/CD for Rust: automated code checking
\n\nWithout 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):
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\nNow 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".
Why this will save your project
\n\nImagine: 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