This repository contains gitoxide - a pure Rust implementation of Git. This document provides guidance for GitHub Copilot when working with this codebase.
- Language: Rust (MSRV documented in gix/Cargo.toml)
- Structure: Cargo workspace with multiple crates (gix-*, gitoxide-core, etc.)
- Main crates:
gix(library entrypoint),gitoxidebinary (CLI tools:gixandein) - Purpose: Provide a high-performance, safe Git implementation with both library and CLI interfaces
- AI agents communicating through a person's account must identify themselves, for example in issue or PR descriptions and comments.
- AI assistance that does not replace the person as the speaker, such as proofreading or wording polish, does not require identification.
- Attributing AI assistance in commit metadata, for example with an
Assisted-by:orCo-authored-by:trailer, is welcome but not required.
- Protect against regression and make implementing features easy
- Keep it practical - the Rust compiler handles mundane things
- Use git itself as reference implementation; run same tests against git where feasible
- Never use
.unwrap()in production code, avoid it in tests in favor of.expect()or?. Usegix_error::TestResultfor test functions and helpers. - Use
.expect("why")with context explaining why expectations should hold, but only if it's relevant to the test.
- Handle all errors, never
unwrap() - Provide error chains making it easy to understand what went wrong
- Applications built on
gixusegix::Resultandgix::Error, preserving error context and source locations. Import error helpers, traits, and macros throughgix::errorinstead of adding a separategix-errordependency.
Plumbing crates are migrating from thiserror enums to gix-error. Check whether a crate already
uses gix-error (look at its Cargo.toml); if it does, follow the patterns below. If it still uses
thiserror, keep using thiserror for consistency within that crate.
- Public error APIs: use
gix_error::Result<T>andgix_error::Errorfor erased or message-based errors at public plumbing boundaries. Preserve concrete error types already exposed by public signatures, includingExnResult<T, Specific>andExn<Specific>. This includes public traits, callbacks, iterator items, associated errors, and re-exported APIs. Keep nativeio::Result, standalone concrete-error results, and generic error adapters when they do not expose exceptions. This exemption covers native or operation-specific recovery errors, not shared diagnostics likeMessage. Public message-based errors useErroreven without anExnwrapper, includingFromStr::ErrandTryFrom::Error. Do not introduce public crate-specific or operation-specific forwarding aliases or renamed error exports. - Internal results: use
Result<T>for erased or message-based errors in private andpub(crate)helpers too. Retain concrete error types when callers benefit from typed recovery or payload access. UseExnResult<T, E>orExnMessageResult<T>when a specific exception type or exception-tree manipulation is actually needed; being private alone is not a reason to introduce an exception-returning wrapper. These aliases and typed construction helpers remain available. - Imports: import
Result,ExnResult, andExnMessageResultunder their canonical names directly fromgix_error(or theirgixre-exports), and use their bare names in signatures. Prefer to importbailwhen usingbail!, viause gix_error::bail;oruse gix::error::bail;, and invoke it without a qualified path. - Porcelain errors: use the central
gix::Errorandgix::Resultre-exports at public boundaries that return erased or message-based exceptions. - Static messages:
gix_error::message("something failed") - Formatted messages:
gix_error::message!("failed to read {path}") - Wrapping callee errors with context:
.or_raise(|| message("context about what failed"))? - Standalone error (no callee): use
bail!(error)for an early return with a concrete error or message, such asbail!(message("something went wrong")). For a result expression at a boundary returningResult, useErr(error.raise()). - Wrapping an
impl Errorwith context:err.and_raise(message("context")) - Typed exceptions: use
.raise_typed(),.and_raise_typed(...),.or_raise_typed(...), or.ok_or_raise_typed(...)when anExn<E>is required; the default helpers returnErrororResult. - Conversion without context:
.or_error()converts native errors and exceptions toResult. Propagate existingResultvalues directly when the callee already provides enough context. - Closure/callback bounds: preserve concrete callback errors; use
Result<T>for erased or message-based errors in public and private APIs. Private callbacks may useExnResult<T>when they need exception-tree operations, or a specific error when useful. Use.or_raise_erased(...)to add context or.or_erased()to convert when an internal callback requires an erased exception. Exn<E>does NOT implementstd::error::Error— this is by design. Convert with?,.into(), or.into_error()when a boundary returnsError. Conversion retains the original error types, causes, metadata, and locations; erased errors support downcasting for recovery.- In tests:
gix_error::TestResult(also re-exported bygix_testtools) accepts exceptions directly with?. Publicgix_error::Resultalso works withgix_testtools::Resultthrough?. - Common imports:
use gix_error::{bail, message, ErrorExt, Result, ResultExt};plus typed exception aliases as needed. - See
gix-error/src/lib.rsmodule docs for a full migration guide fromthiserror
Follow "purposeful conventional commits" style:
- Write commit messages in Markdown and assume readers view them with syntax highlighting.
- Enclose anything that occurs in code, as well as crate names and shell commands, in backticks.
- Use Markdown generously whenever markup helps readers understand or navigate the prose.
- Use the body to share everything known about what motivated the change, not merely what changed.
- Use conventional commit prefixes ONLY if message should appear in changelog
- Breaking changes MUST use
!before the colon:change!:,remove!:,rename!:, or scoped forms likefeat(gix-odb)!: - Features/fixes visible to users:
feat:,fix: - For a changelog-worthy commit touching multiple crates, scope it to the crate that should receive the changelog entry, like
feat!(gix-ref). - Refactors/chores: no prefix (don't affect users)
- Examples:
feat: add Repository::foo() to do great things. (#234)fix: don't panic when calling foo() in a bare repository. (#456)change!: rename Foo to Bar. (#123)feat(gix-odb)!: add a new object lookup APIfix(gix-ref)!: reject invalid reference names
- Follow existing patterns in the codebase
- Skip stylistic rewrites that increase SLOC after formatting; keep simpler existing forms instead of applying style rules unconditionally.
- Start new Rust modules as
foo.rs. Create a module directory only when it contains multiple module files; then usefoo/mod.rsrather than a siblingfoo.rsfile. - No
.unwrap()- use.expect("context")if you are sure this can't fail. - Prefer references in plumbing crates to avoid expensive clones
- Avoid calling
.detach()unless an owned value is explicitly required. ManygixAPIs accept attached ids and references directly, so prefer keeping repository-backed handles likegix::Idwhen possible. - Name variables holding untyped Git object IDs
<type>_idor*_<type>_id(for example,commit_id,root_tree_id, ornote_blob_id) so the object kind is always explicit. - Use
gix_features::threading::*for interior mutability primitives
- Paths are byte-oriented in git (even on Windows via MSYS2 abstraction)
- Use
gix::path::*utilities to convert git paths (BString) toOsStr/Pathor use custom types
just test- Run all tests, clippy, journey tests, and try building docsjust check- Build all code in suitable configurationsjust clippy- Run clippy on all cratescargo test- Run unit tests only
cargo build --release- Default build (big but pretty, ~2.5min)cargo build --release --no-default-features --features lean- Lean build (~1.5min)cargo build --release --no-default-features --features small- Minimal deps (~46s)
- Tests must be isolated from the developer's checkout, other worktrees, shared
Git metadata, and user configuration. Use disposable repositories provided by
gix-testtools, such asscripted_fixture_writable(); never mutate a shared read-only fixture or use the source checkout as a test repository. - Git invocations in test code must go through
gix_testtools::git(),git_command(), or fixture scripts executed bygix-testtools. Do not spawn Git directly withCommand::new("git")or ad hoc subprocess wrappers. If a test needs unsupported command options, extend the shared isolated helper instead of bypassing it.run_git()andinvoke_bash()share this isolation. Subprocesses that invoke Git indirectly must usegix_testtools::configure_git_environment()too. Tests of APIs that read the process environment must isolate it before exercising those APIs or changing environment variables. In#[serial]tests, keep agix_testtools::isolate_git_environment()guard alive for the entire test; it restores only its recorded changes on drop. Chain test-specific overrides onto that guard or use a separategix_testtools::Envguard to restore them too. All concurrent environment access must participate in the same serialization. Otherwise, usegix_testtools::run_in_isolated_process()so changes cannot race other tests. Restore working-directory changes separately withgix_testtools::set_current_dir(). - Setting a subprocess's working directory or passing
git -Cis not isolation: inheritedGIT_DIR,GIT_WORK_TREE,GIT_COMMON_DIR,GIT_INDEX_FILE, or object-directory variables can redirect operations outside the fixture. Tests that exercise such overrides must scope them to disposable test repositories. Open repositories through the crate's isolated test helper or isolatedgixoptions, with only test-specific configuration overrides. - Run tests before making changes to understand existing issues
- Use
GIX_TEST_IGNORE_ARCHIVES=1when testing on macOS/Windows - Journey tests validate CLI behavior end-to-end
- Fixture scripts should document what behavior they exercise and what makes the fixture special. Leave clear-text breadcrumbs so future readers can tell which details are essential to the test. Use markdown doc-strings when available.
- Stabilize fixtures whose generated contents can vary by using the
_needs_archivevariants of functions ingix-testtools; these always use packaged archived fixtures instead of platform-local generated output. - Use assertion descriptions to state what is being asserted. For
assert*!macros this is the last parameter; forinsta::assert*macros it is the second parameter. Prefer messages that explain the invariant, not just that an assertion failed.
- Plumbing crates: Low-level, take references, expose mutable parts as arguments
- Porcelain (gix): High-level, convenient, may clone Repository for user convenience
- Platforms: cheap to create, keep reference to Repository
- Caches: more expensive, clone
Repositoryor free of lifetimes
- Use
Optionsfor branching behavior configuration (can be defaulted) - Use
Contextfor data required for operation (cannot be defaulted)
gix: Main library entrypoint (porcelain)gix-object,gix-ref,gix-config: Core git data structuresgix-odb,gix-pack: Object database and pack handlinggix-diff,gix-merge,gix-status: Operationsgitoxide-core: Shared CLI functionality
- High-level docs: README.md, CONTRIBUTING.md, DEVELOPMENT.md
- Crate status: crate-status.md
- Stability guide: STABILITY.md
- Always update docs if directly related to code changes
- Ubuntu-latest git version is the compatibility target
cargo smart-releasefor releases (driven by commit messages)- Every commit must be self-contained and pass CI independently
- Feel free to run
etc/scripts/ci-check-local.sh --thoroughuntil it passes as proxy, as running every commit against CI isn't feasible.
- Feel free to run
- Keep breaking changes and all adaptations required to build and test the workspace in the same commit
- When such a commit touches multiple crates, scope its conventional commit message to the crate whose changelog should receive the entry
- Understand the plumbing vs porcelain distinction
- Check existing patterns in similar crates
- Follow error handling conventions strictly
- Ensure changes work with feature flags (small, lean, max, max-pure)
- Consider impact on both library and CLI users
- Test against real git repositories when possible