Skip to content

Expose --recurse-submodules on clone, fetch, pull, checkout, and push #94

Description

@matt-edmondson

Written by Claude (Claude Code) on behalf of Matt Edmondson. The analysis and proposed shape below are Claude's, filed at Matt's request.

Five verbs in this library wrap git commands that accept a submodule-recursion flag, and none of them expose it. grep -rn 'recurse\|Recurse' GitIntegration --include='*.cs' (excluding obj/) returns nothing.

Assumes submodules are now in scope for the local layer — see the submodule listing issue for the scope note.

Current option surfaces, for reference

IGitCloneBuilder     WithBranch, WithDepth, Bare, ReportingProgress
IGitFetchBuilder     FromRemote, AllRemotes, Prune, WithTags, WithDepth, ReportingProgress
IGitPullBuilder      FromRemote, WithBranch, FastForwardOnly, Rebase, Merge, ...
IGitCheckoutBuilder  CreatingBranch, Force, Detach
IGitPushBuilder      ToRemote, WithBranch, SettingUpstream, Force, ForceWithLease, DeletingRemoteBranch, DryRun, ReportingProgress

Push's flag is a different thing and must not share a name

This is the part worth getting right. clone, checkout, fetch, and pull all use --recurse-submodules to mean "also operate on the submodules' working trees or refs". push --recurse-submodules means something else entirely: it takes check, on-demand, only, or no, and it governs whether git verifies or pushes the submodules' own commits to their own remotes before pushing the superproject. Giving both the same method name would put one word on two unrelated behaviours.

Suggested shape

Two enums, because the accepted values differ:

  • GitSubmoduleRecursion { No, Yes, OnDemand } for fetch and pull, which accept --recurse-submodules=<value>.
  • GitSubmodulePushCheck { No, Check, OnDemand, Only } for push.

Then:

  • IGitCloneBuilder.RecursingSubmodules() — a plain flag; git also accepts an optional pathspec, which can be added later if wanted.
  • IGitCheckoutBuilder.RecursingSubmodules() — a plain flag.
  • IGitFetchBuilder.RecursingSubmodules(GitSubmoduleRecursion) and IGitPullBuilder.RecursingSubmodules(GitSubmoduleRecursion).
  • IGitPushBuilder.CheckingSubmodules(GitSubmodulePushCheck) — deliberately a different verb in the name.

Each should throw InvalidEnumArgumentException for an unrecognised enum value, matching IGitStatusBuilder.WithUntrackedFiles(GitUntrackedFilesMode).

Two things to verify during implementation

Does fetch --porcelain still behave with --recurse-submodules? GitFetchBuilder probes the git version and degrades below 2.41 because fetch --porcelain only exists from then (CLAUDE.md architecture point 7). Whether the porcelain stream still covers only the superproject's refs, or interleaves submodule output that GitFetchParser would then misread, needs checking against a real repository rather than assuming. If it interleaves, GitFetchResult.DetailAvailable is already the honest way to report that detail is unavailable for this combination.

checkout --recurse-submodules can discard work. Unlike the other four, it will overwrite a submodule's checked-out state. IGitCheckoutBuilder already has Force(), and its remarks explain the checkout-versus-switch choice; the new flag's remarks should say plainly what it can destroy.

Motivating use case

A consumer implementing a guarded pull workflow wants pull --recurse-submodules so that a single invocation moves the superproject and its submodules together, rather than pulling and then discovering the submodules are stale.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions