Skip to content

Conversation

@blooop
Copy link
Owner

@blooop blooop commented Oct 14, 2025

Summary by Sourcery

Add support for git submodules in the reference cache by cloning with --recurse-submodules and updating submodules on cache refresh, and update tests and documentation accordingly.

New Features:

  • Clone cache repositories with --recurse-submodules
  • Replace fetch with pull and add recursive submodule updates during cache refresh

Documentation:

  • Add implementation plan and specification documents for submodule support

Tests:

  • Assert that tests check for --recurse-submodules flag on clone, git pull usage, and submodule update commands

blooop and others added 3 commits October 14, 2025 08:44
Add support for git submodules in reference cache repositories:
- Clone with --recurse-submodules
- Pull and update submodules before branch setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 14, 2025

Reviewer's Guide

Add support for git submodules in the cache repository workflow by enabling recursive cloning, replacing fetch with pull and submodule updates; update tests to validate these behaviors and include design specs.

Sequence diagram for updated cache repository setup with submodule support

sequenceDiagram
    participant Caller
    participant "setup_cache_repo()"
    participant "git"
    Caller->>"setup_cache_repo()": Call with repo_spec
    alt Repository does not exist
        "setup_cache_repo()"->>"git": clone --recurse-submodules repo_url repo_dir
    else Repository exists
        "setup_cache_repo()"->>"git": pull (update repo)
        "setup_cache_repo()"->>"git": submodule update --recursive --init
    end
    "setup_cache_repo()"-->>Caller: Return repo_dir
Loading

Class diagram for updated setup_cache_repo function

classDiagram
    class RepoSpec {
        +repo_url: str
        +repo_dir: pathlib.Path
    }
    class renv {
        +setup_cache_repo(repo_spec: RepoSpec) pathlib.Path
    }
    renv --> RepoSpec: uses
Loading

File-Level Changes

Change Details Files
Enable submodule support in setup_cache_repo implementation
  • Add --recurse-submodules flag to initial git clone
  • Replace git fetch --all with git pull for updates
  • Add git submodule update --recursive --init after pull
  • Update logging message for cache refresh
rockerc/renv.py
Enhance tests to cover recursive clone and submodule updates
  • Assert --recurse-submodules in clone test
  • Change fetch assertions to pull in update test
  • Add assertions for git submodule update --recursive --init
test/test_renv.py
Add submodule support specifications
  • Create implementation plan document
  • Add spec overview file
specs/19/submodule-support/plan.md
specs/19/submodule-support/spec.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

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • Consider refactoring the repeated subprocess.run git invocations into a shared helper utility to reduce duplication and ensure consistency.
  • It may be helpful to add logging or error handling around the submodule update step to surface failures when initializing or updating submodules.
  • Add a test case for nested submodules (multi-level) to ensure the --recursive flag correctly initializes deeper submodule hierarchies.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider refactoring the repeated subprocess.run git invocations into a shared helper utility to reduce duplication and ensure consistency.
- It may be helpful to add logging or error handling around the submodule update step to surface failures when initializing or updating submodules.
- Add a test case for nested submodules (multi-level) to ensure the --recursive flag correctly initializes deeper submodule hierarchies.

## Individual Comments

### Comment 1
<location> `test/test_renv.py:196-198` </location>
<code_context>
-        assert "git" in fetch_call[0][0]
-        assert "fetch" in fetch_call[0][0]
-        assert "--all" in fetch_call[0][0]
+        assert pull_call is not None
+        assert "git" in pull_call[0][0]
+        assert "pull" in pull_call[0][0]
+
+        # Check git submodule update was called
</code_context>

<issue_to_address>
**suggestion (testing):** Suggest adding a test for repositories without submodules.

Consider adding a test to verify behavior when no submodules are present, ensuring the update command handles this case gracefully.

Suggested implementation:

```python
        # Check git submodule update was called
        submodule_call = None
        for call in mock_run.call_args_list:
            if "submodule" in call[0][0]:
                submodule_call = call
                break

        assert submodule_call is not None
        assert "git" in submodule_call[0][0]
        assert "submodule" in submodule_call[0][0]
        assert "update" in submodule_call[0][0]

def test_git_pull_no_submodules(mocker):
    """
    Test that git submodule update is not called when there are no submodules.
    """
    # Setup: mock subprocess.run and simulate no submodules
    mock_run = mocker.patch("subprocess.run")
    # Simulate calls: only git pull, no submodule update
    mock_run.call_args_list = [
        (["git", "pull"],),
    ]

    # Simulate function under test (replace with actual function call)
    # e.g. update_repo(repo_path, has_submodules=False)
    # For demonstration, we just check the calls

    pull_call = None
    for call in mock_run.call_args_list:
        if "pull" in call[0][0]:
            pull_call = call
            break

    assert pull_call is not None
    assert "git" in pull_call[0][0]
    assert "pull" in pull_call[0][0]

    # Ensure submodule update was NOT called
    submodule_call = None
    for call in mock_run.call_args_list:
        if "submodule" in call[0][0]:
            submodule_call = call
            break

    assert submodule_call is None

```

- You may need to adjust the mock setup and function call in `test_git_pull_no_submodules` to match your actual implementation and how you determine the presence of submodules.
- If your codebase uses a helper or fixture for mocking subprocess calls, use that instead of direct patching.
- Ensure the new test is discovered by your test runner (pytest, unittest, etc).
</issue_to_address>

### Comment 2
<location> `test/test_renv.py:191-194` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Avoid loops in tests. ([`no-loop-in-tests`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/no-loop-in-tests))

<details><summary>Explanation</summary>Avoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
* loops
* conditionals

Some ways to fix this:

* Use parametrized tests to get rid of the loop.
* Move the complex logic into helpers.
* Move the complex part into pytest fixtures.

> Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / [Don't Put Logic in Tests](https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests)
</details>
</issue_to_address>

### Comment 3
<location> `test/test_renv.py:192-194` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Avoid conditionals in tests. ([`no-conditionals-in-tests`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/no-conditionals-in-tests))

<details><summary>Explanation</summary>Avoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
* loops
* conditionals

Some ways to fix this:

* Use parametrized tests to get rid of the loop.
* Move the complex logic into helpers.
* Move the complex part into pytest fixtures.

> Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / [Don't Put Logic in Tests](https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests)
</details>
</issue_to_address>

### Comment 4
<location> `test/test_renv.py:202-205` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Avoid loops in tests. ([`no-loop-in-tests`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/no-loop-in-tests))

<details><summary>Explanation</summary>Avoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
* loops
* conditionals

Some ways to fix this:

* Use parametrized tests to get rid of the loop.
* Move the complex logic into helpers.
* Move the complex part into pytest fixtures.

> Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / [Don't Put Logic in Tests](https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests)
</details>
</issue_to_address>

### Comment 5
<location> `test/test_renv.py:203-205` </location>
<code_context>

</code_context>

<issue_to_address>
**issue (code-quality):** Avoid conditionals in tests. ([`no-conditionals-in-tests`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/no-conditionals-in-tests))

<details><summary>Explanation</summary>Avoid complex code, like conditionals, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
* loops
* conditionals

Some ways to fix this:

* Use parametrized tests to get rid of the loop.
* Move the complex logic into helpers.
* Move the complex part into pytest fixtures.

> Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / [Don't Put Logic in Tests](https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests)
</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +196 to +198
assert pull_call is not None
assert "git" in pull_call[0][0]
assert "pull" in pull_call[0][0]
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Suggest adding a test for repositories without submodules.

Consider adding a test to verify behavior when no submodules are present, ensuring the update command handles this case gracefully.

Suggested implementation:

        # Check git submodule update was called
        submodule_call = None
        for call in mock_run.call_args_list:
            if "submodule" in call[0][0]:
                submodule_call = call
                break

        assert submodule_call is not None
        assert "git" in submodule_call[0][0]
        assert "submodule" in submodule_call[0][0]
        assert "update" in submodule_call[0][0]

def test_git_pull_no_submodules(mocker):
    """
    Test that git submodule update is not called when there are no submodules.
    """
    # Setup: mock subprocess.run and simulate no submodules
    mock_run = mocker.patch("subprocess.run")
    # Simulate calls: only git pull, no submodule update
    mock_run.call_args_list = [
        (["git", "pull"],),
    ]

    # Simulate function under test (replace with actual function call)
    # e.g. update_repo(repo_path, has_submodules=False)
    # For demonstration, we just check the calls

    pull_call = None
    for call in mock_run.call_args_list:
        if "pull" in call[0][0]:
            pull_call = call
            break

    assert pull_call is not None
    assert "git" in pull_call[0][0]
    assert "pull" in pull_call[0][0]

    # Ensure submodule update was NOT called
    submodule_call = None
    for call in mock_run.call_args_list:
        if "submodule" in call[0][0]:
            submodule_call = call
            break

    assert submodule_call is None
  • You may need to adjust the mock setup and function call in test_git_pull_no_submodules to match your actual implementation and how you determine the presence of submodules.
  • If your codebase uses a helper or fixture for mocking subprocess calls, use that instead of direct patching.
  • Ensure the new test is discovered by your test runner (pytest, unittest, etc).

@codecov
Copy link

codecov bot commented Oct 14, 2025

Codecov Report

❌ Patch coverage is 68.96552% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 67.06%. Comparing base (89d2527) to head (4fc8c26).

Files with missing lines Patch % Lines
rockerc/renv.py 68.96% 9 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #114      +/-   ##
==========================================
+ Coverage   66.54%   67.06%   +0.51%     
==========================================
  Files           7        7              
  Lines        1405     1430      +25     
==========================================
+ Hits          935      959      +24     
- Misses        470      471       +1     
Files with missing lines Coverage Δ
rockerc/renv.py 58.55% <68.96%> (+1.46%) ⬆️

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

2 participants