Skip to content

feat(gomod): add gomodTidyAll option for indirect dependency updates in Go monorepos - #37138

Open
ducthinh993 wants to merge 7 commits into
renovatebot:mainfrom
ducthinh993:feat/recursive-gomod
Open

feat(gomod): add gomodTidyAll option for indirect dependency updates in Go monorepos#37138
ducthinh993 wants to merge 7 commits into
renovatebot:mainfrom
ducthinh993:feat/recursive-gomod

Conversation

@ducthinh993

@ducthinh993 ducthinh993 commented Jul 23, 2025

Copy link
Copy Markdown

Changes

Adds a gomodTidyAll post-update option for Go monorepos that use local replace directives.

When a shared module is updated in a Go monorepo, go mod tidy currently runs only on the directly updated module.
Dependent modules that reference it through a local replace directive (for example replace shared => ../shared) keep a stale go.sum, which breaks their build.

gomodTidyAll solves this by:

  1. Building a dependency graph from all go.mod files and their local replace directives
  2. Finding all modules that transitively depend on the updated module
  3. Appending a go mod tidy command for each dependent module, in topological order (dependencies before dependents)

New file lib/modules/manager/gomod/package-tree.ts exports getGoModulesInTidyOrder(), which returns the transitive dependents of a go.mod in topological order.
It builds the graph from parseLocalReplacePaths(), which reads the target of every replace directive that points at a local directory.
A single regex covers both the single line form and the lines inside a replace (...) block, and skips comment lines.

Two path helpers move out of the NuGet manager into lib/util/fs/util.ts so both managers share them:

  • resolveRelativePathToRoot() — resolves a path reference that is relative to a package file into a repo-relative path, used by NuGet for ProjectReference and by Go for replace
  • getMatchingFiles() — filters a repo file list by a minimatch pattern

lib/modules/manager/nuget/package-tree.ts calls these two helpers instead of its own copies, and is otherwise unchanged.

Key behaviors

  • All tidy commands go into the single execCommands array, so everything still runs in one exec call.
    This matters in Docker mode, where every extra exec starts a new container
  • Dependent modules are tidied with go -C <dir> mod tidy, so the commands need no shell.
    A subshell would only work in Docker mode, where all commands are concatenated into a single bash -l -c.
    With binarySource=install or global each command is split with shlex and run without a shell.
    This means the option needs Go 1.20 or later
  • gomodTidyAll alone implies gomodTidy on the primary module
  • gomodMassage only rewrites the go.mod of the updated module, and Go ignores replace directives outside the main module, so the tidy commands for the dependent modules are unaffected by it.
    The two options still work against each other, because massaging comments out the relative replace directives that this option follows, so the docs tell users not to combine them
  • tidyOpts (-compat=1.17, -e) propagate to the dependent module tidy commands
  • If graph building fails, the standard single-module tidy still runs
  • The feature is behind a config flag, so there is no impact when it is disabled

Usage:

{
  "postUpdateOptions": ["gomodTidyAll"]
}

Context

AI assistance disclosure

Did you use AI tools to create any part of this pull request?

Please select one option and, if yes, briefly describe how AI was used (e.g., code, tests, docs) and which tool(s) you used.

  • No — I did not use AI for this contribution.
  • Yes — minimal assistance (e.g., IDE autocomplete, small code completions, grammar fixes).
  • Yes — substantive assistance (AI-generated non‑trivial portions of code, tests, or documentation).
  • Yes — other (please describe): Code, tests and documentation were written with Claude Code. Review feedback was also applied with Claude Code.

Documentation (please check one with an [x])

  • I have updated the documentation, or
  • No documentation update is required

How I've tested my work (please select one)

I have verified these changes via:

  • Code inspection only, or
  • Newly added/modified unit tests, or
  • No unit tests, but ran on a real repository, or
  • Both unit tests + ran on a real repository

Test coverage:

  • lib/util/fs/util.spec.ts — 8 new tests for resolveRelativePathToRoot and getMatchingFiles
  • lib/modules/manager/gomod/package-tree.spec.ts — 5 tests for local replace parsing and tidy ordering
  • lib/modules/manager/gomod/artifacts-gomodtidyall.spec.ts — 6 tests for the integration (dependent modules, error handling, tidyOpts propagation, standalone gomodTidyAll, feature disabled)
  • pnpm check --all passes, and the new code is at 100% coverage

@ducthinh993
ducthinh993 marked this pull request as ready for review July 23, 2025 17:07
@ducthinh993 ducthinh993 changed the title feat(gomod): add gomodTidyAll option for indirect dependency updates in Go monorepos #36848 feat(gomod): add gomodTidyAll option for indirect dependency updates in Go monorepos #36848 Jul 27, 2025
@ducthinh993 ducthinh993 changed the title feat(gomod): add gomodTidyAll option for indirect dependency updates in Go monorepos #36848 feat(gomod): add gomodTidyAll option for indirect dependency updates in Go monorepos Jul 27, 2025
@rarkins
rarkins requested review from Copilot and viceice July 28, 2025 15:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements the gomodTidyAll option for Go monorepos to handle indirect dependency updates across modules with local replace directives. The implementation adds graph-based dependency resolution to determine the proper update order for interdependent Go modules.

  • Adds a new package-tree.ts module for graph-based dependency resolution and topological ordering
  • Integrates gomodTidyAll logic into the artifacts processing pipeline to update dependent modules in dependency order
  • Updates documentation and configuration to include the new gomodTidyAll option

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
lib/modules/manager/gomod/readme.md Adds documentation for the new gomodTidyAll post-update option
lib/modules/manager/gomod/package-tree.ts New module implementing graph-based dependency resolution for Go modules
lib/modules/manager/gomod/package-tree.spec.ts Unit tests for the package-tree functionality
lib/modules/manager/gomod/artifacts.ts Integrates gomodTidyAll processing and adds functions to tidy dependent modules
lib/modules/manager/gomod/artifacts.spec.ts Tests for the new gomodTidyAll functionality in artifacts processing
lib/config/options/index.ts Adds gomodTidyAll to the list of valid post-update options
docs/usage/golang.md Updates Go documentation to describe the gomodTidyAll option behavior
Comments suppressed due to low confidence (4)

lib/modules/manager/gomod/package-tree.ts:58

  • [nitpick] The variable name deps is ambiguous in this context. Consider renaming it to visitedModules or dependentModuleFlags to better reflect that it contains module names with their leaf status flags.
  return Array.from(deps).map(([name, isLeaf]) => ({ name, isLeaf }));

lib/modules/manager/gomod/package-tree.ts:68

  • [nitpick] The parameter name deps is ambiguous. Consider renaming it to visitedModules or moduleFlags to better indicate it's a map tracking visited modules and their leaf status.
): void {

lib/modules/manager/gomod/package-tree.ts:84

  • The variable name dep is misleading here as it represents a dependent module, not a dependency. Consider renaming it to dependent or dependentModule for clarity.
  for (const dep of dependents) {

lib/modules/manager/gomod/package-tree.ts:85

  • The variable name dep should be dependent and deps should be renamed to visitedModules for consistency with the previous naming suggestion.
    recursivelyGetDependentGoModFiles(dep, graph, deps);

Comment thread lib/modules/manager/gomod/artifacts.ts Outdated
Comment thread lib/modules/manager/gomod/artifacts.ts Outdated
Comment thread lib/modules/manager/gomod/artifacts.ts Outdated
@ducthinh993
ducthinh993 requested a review from viceice July 28, 2025 16:59
@ducthinh993

Copy link
Copy Markdown
Author

Hi @viceice , could you please help review? This issue is also blocking our organization. Thanks

Comment thread lib/modules/manager/gomod/package-tree.ts Outdated
Comment thread lib/modules/manager/gomod/package-tree.ts Outdated
Comment thread lib/modules/manager/gomod/package-tree.ts Outdated
Comment thread lib/modules/manager/gomod/package-tree.ts Outdated
Comment thread lib/modules/manager/gomod/artifacts.ts Outdated
@viceice

viceice commented Aug 13, 2025

Copy link
Copy Markdown
Member

needs deconflicting

@ducthinh993
ducthinh993 force-pushed the feat/recursive-gomod branch from a91d263 to 1f5f019 Compare August 14, 2025 01:36
@ducthinh993
ducthinh993 marked this pull request as draft August 17, 2025 03:25
@ducthinh993
ducthinh993 force-pushed the feat/recursive-gomod branch from c33d932 to 9ee3560 Compare March 22, 2026 00:47
@ducthinh993
ducthinh993 force-pushed the feat/recursive-gomod branch from 9ee3560 to 9a8f320 Compare March 22, 2026 02:04
@ducthinh993 ducthinh993 reopened this Mar 22, 2026
@ducthinh993
ducthinh993 marked this pull request as ready for review March 22, 2026 05:04
@ducthinh993
ducthinh993 requested a review from viceice March 22, 2026 05:44
@ducthinh993

Copy link
Copy Markdown
Author

Hello @viceice, could you please kindly help review the PR? Thanks

…s in Go monorepos

In Go monorepos it is common for one module to depend on another in the
same repo via a local `replace` directive (e.g. `replace example.com/shared
=> ../shared`). When Renovate updates the shared module, today `go mod
tidy` runs only in that module, leaving dependent `go.sum` files stale.

Add a new `gomodTidyAll` post-update option that:

- Discovers every `go.mod` in the repo and parses its local `replace`
  directives (both inline and block form).
- Builds a dependency graph with `graph-data-structure`, with edges from
  dependency → dependent, matching the existing NuGet convention.
- Runs `go mod tidy` on every transitive dependent in topological order,
  propagating any `gomodTidy1.17` / `gomodTidyE` flags to the dependent
  commands.
- Collects updated `go.mod` and `go.sum` files from each dependent
  directory into the artifact result.
- Implies `gomodTidy` so users only need to enable one option.

Supporting changes:

- Extract `getMatchingFiles` and `resolveRelativePathToRoot` into
  `lib/util/fs/util.ts` so NuGet and gomod can share them instead of
  each having its own glob/resolve helper.
- Refactor `lib/modules/manager/nuget/package-tree.ts` to use the
  shared `getMatchingFiles` helper and rename local variables for
  clarity (`deps` → `visited`, `dep` → `dependent`) per review feedback.

Docs and config:

- Register `gomodTidyAll` in `postUpdateOptions` `allowedValues`.
- Add a row for `gomodTidyAll` to the `postUpdateOptions` table in
  `docs/usage/configuration-options.md`.
- Add a "Monorepo tidying for local `replace` directives" subsection
  to `docs/usage/golang.md` describing when to use the option.

Tests and coverage:

- `lib/modules/manager/gomod/package-tree.spec.ts` — parse/graph/topo
  order, 100% line/branch/function coverage.
- `lib/modules/manager/gomod/artifacts-gomodtidyall.spec.ts` — six
  integration cases covering subshell commands, `tidyOpts` propagation,
  result collection, no-op with no dependents, error handling, and
  the disabled path.
- Full `lib/modules/manager/gomod` suite (151 tests) and NuGet
  package-tree suite (9 tests) pass; new code is fully covered.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ducthinh993
ducthinh993 force-pushed the feat/recursive-gomod branch from 019982c to 8b5a63e Compare April 18, 2026 06:04
@mschfh

mschfh commented Jul 17, 2026

Copy link
Copy Markdown

Hi @viceice / @secustor, could this please be reviewed? Is there any other remaining blocker to getting this merged?

@secustor secustor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

has conflicts

Comment thread lib/modules/manager/gomod/package-tree.spec.ts Outdated
Comment thread lib/modules/manager/gomod/package-tree.ts Outdated
Comment thread lib/modules/manager/gomod/package-tree.ts Outdated
Comment thread lib/modules/manager/gomod/package-tree.ts Outdated
Comment thread lib/util/fs/index.ts Outdated
Comment thread lib/util/fs/util.ts Outdated
Comment thread lib/util/fs/util.ts Outdated
@mschfh

mschfh commented Jul 25, 2026

Copy link
Copy Markdown

@ducthinh993 Could you please resolve the conflicts and review the feedback?

# Conflicts:
#	docs/usage/configuration-options.md
@ducthinh993
ducthinh993 requested a review from secustor July 26, 2026 02:47
A subshell only works in Docker mode, where all commands are concatenated
into a single `bash -l -c`. With `binarySource=install` or `global` each
command is split with shlex and run without a shell, so `(cd dir && ...)`
is treated as the name of a binary.
A single regex now covers both the single line form and the lines inside
a `replace (...)` block. The previous block pattern stopped at the first
`)`, so a block containing a comment with a bracket was skipped, and the
single line pattern did not allow a trailing comment.

Also keeps the NuGet package tree unchanged apart from the two helpers it
now shares.
Massaging comments out the relative `replace` directives that
`gomodTidyAll` follows, so the two options work against each other.
@mschfh

mschfh commented Jul 30, 2026

Copy link
Copy Markdown

@secustor Could you please re-review?

@secustor secustor added the auto:no-mentions Don't cause unnecessary notifications label Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Hi there,

This is intended as a polite, automated request that users avoid @ mentioning repository maintainers like @viceice. Doing so causes annoying mobile notifications and makes it harder to maintain this repository.

We know it might be common elsewhere but we participate in hundreds of discussions a week and would need to turn off GitHub mobile notifications if we were mentioned in every one.

As a general rule, we will read and respond to all discussions in this repository, so there is no need to mention us.

Thanks, the Renovate team

@secustor secustor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Else LGTM

Comment thread lib/modules/manager/gomod/artifacts.ts Outdated
Comment thread lib/util/fs/util.ts Outdated
@ducthinh993
ducthinh993 force-pushed the feat/recursive-gomod branch from 06ca274 to 989642c Compare August 3, 2026 00:54
@ducthinh993
ducthinh993 requested a review from secustor August 3, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto:no-mentions Don't cause unnecessary notifications

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants