Skip to content

Configure Git identity inside container from host git config - #74

Draft
leynos wants to merge 1 commit into
mainfrom
configure-git-identity-container-nznyjk
Draft

Configure Git identity inside container from host git config#74
leynos wants to merge 1 commit into
mainfrom
configure-git-identity-container-nznyjk

Conversation

@leynos

@leynos leynos commented Apr 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a complete Git identity configuration feature: reads host git config (user.name and user.email) and applies them inside the container via git config --global. Missing fields emit warnings instead of failing.
  • Introduces a new GitIdentity module with data types, a host reader trait, a production reader, and an applicator that talks to the container via the existing ContainerExecClient seam.
  • Wire-up across the engine, add unit and behavioural tests, and update documentation and roadmap accordingly.

Changes

  • New module: src/engine/connection/git_identity/mod.rs
  • Unit tests: src/engine/connection/git_identity/tests.rs
  • Engine wiring: expose GitIdentity, GitIdentityReader, GitIdentityResult, SystemGitIdentityReader from engine modules
  • Documentation updates:
    • docs/podbot-design.md: Git identity reading and container application section
    • docs/podbot-roadmap.md: Step 4.1 marked complete
    • docs/users-guide.md: new section describing Git identity behavior
  • Behavioural and unit tests for identity flow:
    • Mock readers and mock container exec client
    • Tests cover happy paths (both fields), partial configurations, and missing identity
    • Handling of container exec failures and non-zero exit codes with warnings
  • Behavioural test scaffolding:
    • tests/bdd_git_identity.rs and supporting helpers
    • tests/features/git_identity.feature with scenarios
  • Documentation alignment in exec plan and design docs to reflect the new capability

Why

  • To ensure Git commits created by podbot agents inside the container carry correct author information, derived from the host configuration. The approach uses a straightforward, correct, and testable path by querying host git config via git config --get and applying with git config --global inside the container. Missing identity is treated as a warning, not a hard error, per roadmap guidance.

Implementation details

  • GitIdentity: optional fields name and email with helper predicates (is_complete, is_empty, has_name, has_email).
  • GitIdentityReader: trait to enable dependency injection for deterministic tests.
  • SystemGitIdentityReader: production implementation using std::process::Command to run git config --get.
  • configure_git_identity_async: orchestrates reading host identity and applying it to the container via ContainerExecClient, emitting warnings for missing fields or exec failures.
  • apply_single_config and supporting polling logic execute the container-side git config --global commands and surface results as GitIdentityResult.
  • All existing API semantics preserved; identity handling is additive and injectable for tests.

Tests

  • Unit tests cover identity types, reader, and container-application logic with deterministic mocks.
  • Behavioural tests (rstest-bdd) scaffolded to validate happy and unhappy paths, including missing fields and container exec failures.
  • Tests ensure container_id is passed through to exec commands and that warnings are emitted on non-fatal failures.

Validation and acceptance criteria

  • A callable configure_git_identity_async path exists and applies host Git identity to a container.
  • Host Git identity reading is abstracted behind a GitIdentityReader trait with production and mock implementations.
  • Both user.name and user.email are read from the host and applied to the container when present.
  • Missing fields produce warnings, not errors; container exec failures produce warnings, not aborts.
  • Comprehensive unit tests plus behavioral tests pass locally.
  • Documentation and roadmap reflect the implemented capability.

How to test locally

  • Run code quality gates and tests:
    • make check-fmt
    • make lint
    • make test
  • Optionally run the behavioural tests that rely on rstest-bdd scaffolding after building.

Notes for reviewers

  • This feature uses dependency injection for host Git reading (GitIdentityReader) to enable deterministic unit tests without requiring a host Git installation.
  • The new components are wired into the existing engine exposure surface so downstream callsites can opt into this behavior without API churn.
  • No breaking changes to existing container orchestration logic; this is additive functionality focused on Git identity within the container.

◳ Generated by DevBoxer


ℹ️ Tag @devboxerhub to ask questions and address PR feedback

📎 Task: https://www.devboxer.com/task/fb79b680-e6e5-4870-937b-b0e24407e6a1

Summary by Sourcery

Add a Git identity configuration capability that reads host Git settings and applies them inside containers, with graceful degradation and full engine wiring.

Enhancements:

  • Introduce a GitIdentity module with reader trait, system reader implementation, result type, and engine integration for container Git identity configuration.
  • Implement host Git identity reading via git config, applying available fields in containers while treating missing fields and exec failures as non-fatal warnings.
  • Document the Git identity design, behaviour, and execution plan, and mark the corresponding roadmap step as complete.

Documentation:

  • Add design and user-guide sections describing how Git identity is read from the host and configured inside containers, including warning semantics.
  • Create an execution plan document detailing the Git identity configuration work and its constraints, risks, and validation criteria.
  • Update the roadmap to reflect completion of the Git identity configuration step.

Tests:

  • Add unit tests for GitIdentity types, host reader abstraction, and container application logic using mocked exec clients.
  • Add BDD-style behavioural tests and helpers that cover complete, partial, missing, and failing Git identity scenarios.

…from host to container

Implement Step 4.1: read user.name and user.email from host Git config and apply inside container via git config --global. Missing fields emit warnings, do not fail. Introduce GitIdentityReader trait with SystemGitIdentityReader implementation using git config commands. Apply identity to container using ContainerExecClient exec calls. Add comprehensive unit and behavioral tests covering all cases, including partial and missing identity and container exec failures. Update design and user docs to reflect Git identity configuration behaviour. Mark Step 4.1 as done in roadmap.

This feature ensures commits inside containers carry correct author info derived from host configuration, enabling smoother agent operation without requiring manual identity configuration or causing failures when identity is missing.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gates Failed
New code is healthy (3 new files with code health below 10.00)
Enforce critical code health rules (1 file with Deep, Nested Complexity)
Enforce advisory code health rules (3 files with Excess Number of Function Arguments, Code Duplication, String Heavy Function Arguments, Large Assertion Blocks, Duplicated Assertion Blocks)

Gates Passed
3 Quality Gates Passed

See analysis details in CodeScene

Reason for failure
New code is healthy Violations Code Health Impact
tests.rs 4 rules 6.69 Suppress
mod.rs 2 rules 9.10 Suppress
assertions.rs 1 rule 9.39 Suppress
Enforce critical code health rules Violations Code Health Impact
mod.rs 1 critical rule 9.10 Suppress
Enforce advisory code health rules Violations Code Health Impact
tests.rs 4 advisory rules 6.69 Suppress
mod.rs 1 advisory rule 9.10 Suppress
assertions.rs 1 advisory rule 9.39 Suppress

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

Comment on lines +385 to +414
async fn wait_for_exec_completion<C: ContainerExecClient>(
client: &C,
exec_id: &str,
) -> Result<(), String> {
loop {
let inspect = client
.inspect_exec(exec_id)
.await
.map_err(|e| format!("{e}"))?;

if let Some(running) = inspect.running {
if !running {
// Check exit code for non-zero (Git config failure).
if let Some(code) = inspect.exit_code {
if code != 0 {
return Err(format!(
"git config exited with code {code}"
));
}
}
return Ok(());
}
}

tokio::time::sleep(std::time::Duration::from_millis(
super::exec::EXEC_INSPECT_POLL_INTERVAL_MS,
))
.await;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Deep, Nested Complexity
wait_for_exec_completion has a nested complexity depth of 5, threshold = 4

Suppress

Comment on lines +322 to +382
async fn apply_single_config<C: ContainerExecClient>(
client: &C,
container_id: &str,
key: &str,
value: &str,
warnings: &mut Vec<String>,
) -> bool {
let options = bollard::exec::CreateExecOptions::<String> {
attach_stdout: Some(false),
attach_stderr: Some(false),
attach_stdin: Some(false),
tty: Some(false),
cmd: Some(vec![
String::from("git"),
String::from("config"),
String::from("--global"),
String::from(key),
String::from(value),
]),
..bollard::exec::CreateExecOptions::default()
};

let create_result = client.create_exec(container_id, options).await;
let exec_id = match create_result {
Ok(result) => result.id,
Err(error) => {
warnings.push(format!(
"failed to create exec for git config {key}: {error}"
));
return false;
}
};

let start_result = client
.start_exec(
&exec_id,
Some(bollard::exec::StartExecOptions {
detach: true,
tty: false,
output_capacity: None,
}),
)
.await;

if let Err(error) = start_result {
warnings.push(format!(
"failed to start exec for git config {key}: {error}"
));
return false;
}

// Poll for completion.
if let Err(error) = wait_for_exec_completion(client, &exec_id).await {
warnings.push(format!(
"failed to inspect exec for git config {key}: {error}"
));
return false;
}

true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Excess Number of Function Arguments
apply_single_config has 5 arguments, max arguments = 4

Suppress

Comment on lines +292 to +312
async fn apply_name_only_warns_about_missing_email(
container_id: &str,
mock_client: MockExecClient,
) {
let identity = GitIdentity::new(Some(String::from("Alice")), None);
let result = EngineConnector::apply_git_identity_async(
&mock_client,
container_id,
&identity,
)
.await
.expect("should succeed");

assert!(result.name_applied());
assert!(!result.email_applied());
assert_eq!(result.warnings().len(), 1);
assert!(result.warnings()[0].contains("user.email"));

let calls = mock_client.calls();
assert_eq!(calls.len(), 1, "expected one exec call for name only");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Code Duplication
The module contains 11 functions with similar structure: apply_email_only_warns_about_missing_name,apply_empty_identity_warns_and_makes_no_exec_calls,apply_name_only_warns_about_missing_email,configure_reads_identity_and_applies_it and 7 more functions

Suppress

@@ -0,0 +1,540 @@
//! Unit tests for Git identity reading and container configuration.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: String Heavy Function Arguments
In this module, 40.0% of all arguments to its 33 functions are strings. The threshold for string arguments is 39.0%

Suppress

Comment on lines +191 to +202
fn identity_with_both_fields_is_complete() {
let identity = GitIdentity::new(
Some(String::from("Alice")),
Some(String::from("alice@example.com")),
);
assert!(identity.is_complete());
assert!(!identity.is_empty());
assert!(identity.has_name());
assert!(identity.has_email());
assert_eq!(identity.name(), Some("Alice"));
assert_eq!(identity.email(), Some("alice@example.com"));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Large Assertion Blocks
The test suite contains 11 assertion blocks with at least 4 assertions, threshold = 4

Suppress

Comment on lines +191 to +202
fn identity_with_both_fields_is_complete() {
let identity = GitIdentity::new(
Some(String::from("Alice")),
Some(String::from("alice@example.com")),
);
assert!(identity.is_complete());
assert!(!identity.is_empty());
assert!(identity.has_name());
assert!(identity.has_email());
assert_eq!(identity.name(), Some("Alice"));
assert_eq!(identity.email(), Some("alice@example.com"));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Duplicated Assertion Blocks
The test suite contains 4 functions with duplicated assertion blocks (create_exec_failure_produces_warning_not_error,inspect_exec_failure_produces_warning_not_error,nonzero_exit_code_produces_warning_not_error,start_exec_failure_produces_warning_not_error), threshold = 2

Suppress

Comment on lines +34 to +43
fn user_name_is_applied(
git_identity_state: &GitIdentityState,
) -> StepResult<()> {
let outcome = get_outcome(git_identity_state)?;
if outcome.name_applied() {
Ok(())
} else {
Err(String::from("expected user.name to be applied"))
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ New issue: Code Duplication
The module contains 4 functions with similar structure: user_email_is_applied,user_email_is_not_applied,user_name_is_applied,user_name_is_not_applied

Suppress

@sourcery-ai

sourcery-ai Bot commented Apr 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new Git identity configuration subsystem that reads user.name/user.email from the host via a pluggable reader, applies them inside containers via the existing exec seam with graceful degradation, wires the types through the engine API surface, and covers the behavior with unit, BDD tests, and documentation updates.

Sequence diagram for configure_git_identity_async flow

sequenceDiagram
    actor User
    participant HostGitConfig
    participant GitIdentityReader
    participant EngineConnector
    participant ContainerExecClient
    participant Container

    User->>EngineConnector: configure_git_identity_async(client, container_id, reader)
    EngineConnector->>EngineConnector: validate container_id not empty
    EngineConnector->>GitIdentityReader: read_git_identity()
    GitIdentityReader->>HostGitConfig: git config --get user.name
    HostGitConfig-->>GitIdentityReader: optional name
    GitIdentityReader->>HostGitConfig: git config --get user.email
    HostGitConfig-->>GitIdentityReader: optional email
    GitIdentityReader-->>EngineConnector: GitIdentity{name, email}

    EngineConnector->>ContainerExecClient: apply_git_identity_async(client, container_id, identity)
    alt both fields missing
        ContainerExecClient-->>EngineConnector: GitIdentityResult{warnings:["no Git identity configured on the host"], applied:false}
    else some fields present
        alt user.name present
            EngineConnector->>ContainerExecClient: create_exec(container_id, git config --global user.name value)
            alt create_exec fails
                ContainerExecClient-->>EngineConnector: error
                EngineConnector-->>EngineConnector: record warning for user.name
            else create_exec succeeds
                ContainerExecClient-->>EngineConnector: exec_id_name
                EngineConnector->>ContainerExecClient: start_exec(exec_id_name, detach=true)
                EngineConnector->>ContainerExecClient: inspect_exec(exec_id_name) until finished
            end
        else user.name missing
            EngineConnector-->>EngineConnector: record warning for missing user.name
        end

        alt user.email present
            EngineConnector->>ContainerExecClient: create_exec(container_id, git config --global user.email value)
            alt create_exec fails
                ContainerExecClient-->>EngineConnector: error
                EngineConnector-->>EngineConnector: record warning for user.email
            else create_exec succeeds
                ContainerExecClient-->>EngineConnector: exec_id_email
                EngineConnector->>ContainerExecClient: start_exec(exec_id_email, detach=true)
                EngineConnector->>ContainerExecClient: inspect_exec(exec_id_email) until finished
            end
        else user.email missing
            EngineConnector-->>EngineConnector: record warning for missing user.email
        end
    end

    EngineConnector-->>User: GitIdentityResult{name_applied, email_applied, warnings}
Loading

Class diagram for Git identity configuration module

classDiagram
    class GitIdentity {
        -name : Option~String~
        -email : Option~String~
        +new(name : Option~String~, email : Option~String~) GitIdentity
        +name() Option~str~
        +email() Option~str~
        +has_name() bool
        +has_email() bool
        +is_empty() bool
        +is_complete() bool
    }

    class GitIdentityReader {
        <<interface>>
        +read_git_identity() GitIdentity
    }

    class SystemGitIdentityReader {
        +new() SystemGitIdentityReader
        +read_git_identity() GitIdentity
    }

    class GitIdentityResult {
        -name_applied : bool
        -email_applied : bool
        -warnings : Vec~String~
        +name_applied() bool
        +email_applied() bool
        +warnings() Vec~String~
        +is_empty() bool
    }

    class ContainerExecClient {
        <<interface>>
        +create_exec(container_id : str, options : CreateExecOptions) CreateExecResult
        +start_exec(exec_id : str, options : StartExecOptions) StartExecResult
        +inspect_exec(exec_id : str) InspectExecResult
    }

    class EngineConnector {
        +configure_git_identity_async(client : ContainerExecClient, container_id : str, reader : GitIdentityReader) GitIdentityResult
        +apply_git_identity_async(client : ContainerExecClient, container_id : str, identity : GitIdentity) GitIdentityResult
    }

    class HelperFunctions {
        +read_git_config_value(key : str) Option~String~
        +apply_git_identity_async(client : ContainerExecClient, container_id : str, identity : GitIdentity) GitIdentityResult
        +apply_single_config(client : ContainerExecClient, container_id : str, key : str, value : str, warnings : Vec~String~) bool
        +wait_for_exec_completion(client : ContainerExecClient, exec_id : str) Result~(), String~
    }

    GitIdentityReader <|.. SystemGitIdentityReader
    ContainerExecClient <.. EngineConnector
    GitIdentityReader <.. EngineConnector
    GitIdentity ..> GitIdentityResult
    EngineConnector ..> GitIdentityResult
    EngineConnector ..> HelperFunctions
    HelperFunctions ..> ContainerExecClient
    HelperFunctions ..> GitIdentity
    HelperFunctions ..> GitIdentityResult
    SystemGitIdentityReader ..> GitIdentity
    SystemGitIdentityReader ..> HelperFunctions
Loading

File-Level Changes

Change Details Files
Introduce GitIdentity module to read host Git config and apply identity inside containers via exec.
  • Define GitIdentity struct with optional name/email and helper predicates (accessors, is_complete, is_empty, has_name, has_email).
  • Add GitIdentityReader trait and SystemGitIdentityReader production implementation using std::process::Command to run git config --get for user.name/user.email, handling missing values and git absence gracefully.
  • Implement GitIdentityResult to report which fields were applied and any warnings emitted.
  • Implement EngineConnector::configure_git_identity_async and apply_git_identity_async that validate container_id, read identity via the reader, and orchestrate application using ContainerExecClient with warning-only failure semantics.
  • Implement internal helpers apply_git_identity_async, apply_single_config, and wait_for_exec_completion that build bollard CreateExecOptions, start detached execs, poll inspect, and map failures/exit codes into warnings instead of hard errors.
src/engine/connection/git_identity/mod.rs
Wire new Git identity types into the engine’s public surface.
  • Expose GitIdentity, GitIdentityReader, GitIdentityResult, and SystemGitIdentityReader from engine::connection.
  • Re-export these new types from the top-level engine module alongside existing engine traits and types.
src/engine/connection/mod.rs
src/engine/mod.rs
Add unit tests for Git identity types and container application behavior using mocks.
  • Create MockGitIdentityReader and MockExecClient implementing GitIdentityReader and ContainerExecClient to make tests deterministic and daemon-free.
  • Test GitIdentity helpers for all combinations of presence/absence of name/email and default behavior.
  • Test EngineConnector::apply_git_identity_async and configure_git_identity_async for full identity, partial identity, empty identity, invalid container IDs, exec creation/start/inspect failures, non-zero exit codes, and container_id propagation into exec calls.
src/engine/connection/git_identity/tests.rs
Add BDD-style behavioral tests for Git identity flows using rstest-bdd.
  • Define GitIdentityState with slots for host identity, exec failure flag, outcome GitIdentityResult, and captured exec commands.
  • Implement Given/When/Then steps using MockReader and MockExecClient, driving EngineConnector::configure_git_identity_async via a Tokio runtime and capturing results and commands.
  • Create feature scenarios covering: both fields present, only name, only email, neither field, and container exec failure, each asserting applied flags and warning contents.
  • Register scenarios in a bdd_git_identity test harness that wires the feature file to the state fixture.
tests/bdd_git_identity_helpers/state.rs
tests/bdd_git_identity_helpers/steps.rs
tests/bdd_git_identity_helpers/assertions.rs
tests/bdd_git_identity_helpers/mod.rs
tests/bdd_git_identity.rs
tests/features/git_identity.feature
Document the Git identity configuration design, behavior, and roadmap status, and add an execution plan document.
  • Add a detailed ExecPlan describing motivation, constraints, risks, design decisions, and implementation/test plan for Step 4.1.1 Git identity configuration.
  • Document Git identity configuration in the design doc: how host identity is read, how it is applied via git config --global in the container, and the graceful degradation rules for missing fields and exec failures.
  • Update the user guide with a Git identity configuration section describing behavior, expected warnings, and that no extra user configuration is needed.
  • Mark Step 4.1 in the roadmap as complete with all associated tasks checked off.
docs/execplans/4-1-1-git-identity-configuration.md
docs/podbot-design.md
docs/users-guide.md
docs/podbot-roadmap.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 34720629-960b-4142-819c-b8e21667689b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch configure-git-identity-container-nznyjk

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant