Browse code

validate/module-replace: Ignore client diff if it only removes the replace rule

The check is supposed to detect if we're missing replace rules (mostly
for the actual code changes).

When dropping the replace rules we're also updating the client/go.mod to
use the yet-untagged `api` module version (but that's ok).

For context, an example client diff for a commit that drops replace
rules is:

```diff
index d032891256..04cb6f3bee 100644
--- client/go.mod
+++ client/go.mod
@@ -10,7 +10,7 @@ require (
github.com/docker/go-connections v0.6.0
github.com/docker/go-units v0.5.0
github.com/google/go-cmp v0.7.0
- github.com/moby/moby/api v1.52.0
+ github.com/moby/moby/api v1.52.1-0.20251216183007-8316b79e045e
github.com/moby/term v0.5.2
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
@@ -32,5 +32,3 @@ require (
go.opentelemetry.io/otel/metric v1.35.0 // indirect
golang.org/x/sys v0.33.0 // indirect
)
-
-replace github.com/moby/moby/api => ../api
diff --git client/go.sum client/go.sum
index 29b5ea6130..558c150354 100644
--- client/go.sum
+++ client/go.sum
@@ -29,6 +29,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
+github.com/moby/moby/api v1.52.1-0.20251216183007-8316b79e045e h1:OU/YmCsRJtfx4OhTt7DD8WANQ57eHSUkYrGFNnS5yxc=
+github.com/moby/moby/api v1.52.1-0.20251216183007-8316b79e045e/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=' ']'
```

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2025/12/17 20:27:59
Showing 1 changed files
... ...
@@ -4,23 +4,62 @@ set -e
4 4
 SCRIPTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5 5
 source "${SCRIPTDIR}/.validate"
6 6
 
7
-api_files=$(
8
-	validate_diff --diff-filter=ACMR --name-only -- \
7
+# Leaves only actual changes, not the diff header
8
+filter_diff() {
9
+	sed '1,4d' | grep -E '^[+-]' | grep -Ev '^(\+\+\+|\-\-\-)'
10
+}
11
+
12
+api_diff=$(
13
+	validate_diff --diff-filter=ACMR -U0 -- \
9 14
 		'api/' \
10 15
 		':(exclude)api/README.md' \
11 16
 		':(exclude)api/swagger.yaml' \
12 17
 		':(exclude)api/docs/' \
18
+		| filter_diff \
13 19
 		|| true
14 20
 )
15 21
 
16
-client_files=$(validate_diff --diff-filter=ACMR --name-only -- 'client/' || true)
22
+client_diff=$(validate_diff --diff-filter=ACMR -U0 -- 'client/' | filter_diff || true)
17 23
 
18 24
 has_errors=0
19 25
 
26
+echo "================================================"
27
+echo "go.mod:"
20 28
 cat go.mod
29
+echo "================================================"
30
+
31
+# reads stdin and returns 1 or 0 if the replace rule for the module is present
32
+# example input:
33
+# -	github.com/moby/moby/api v1.52.0
34
+# +	github.com/moby/moby/api v1.52.1-0.20251217102041-b8093b76fb43
35
+# -
36
+# -replace github.com/moby/moby/api => ../api
37
+# +github.com/moby/moby/api v1.52.1-0.20251217102041-b8093b76fb43 h1:CTVB9ByfoOULdpH0GiDWwHK3B1jxwjoWtHiEGia1nRc=
38
+# +github.com/moby/moby/api v1.52.1-0.20251217102041-b8093b76fb43/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
39
+only_removes_replace_rule_for_module() {
40
+	local module="$1"
41
+	input=$(cat | sed '/^[-+]$/d') # strip empty lines
42
+
43
+	if ! grep -qE "^-replace ${module} => " <<< "${input}"; then
44
+		return 1
45
+	fi
46
+
47
+	# Check if ALL lines contain the module string
48
+	# The grep command checks if there's NO match for the module string, and
49
+	# then we negate the results
50
+	if grep -vqF "${module}" <<< "${input}"; then
51
+		return 1
52
+	fi
53
+
54
+	return 0
55
+}
21 56
 
57
+echo >&2 "================================================"
58
+echo >&2 "api diff:"
59
+echo >&2 "${api_diff}"
60
+echo >&2 "================================================"
22 61
 # Check if changes to ./api require a replace rule in go.mod
23
-if [ -n "${TEST_FORCE_VALIDATE:-}" ] || [ -n "${api_files}" ]; then
62
+if [ -n "${TEST_FORCE_VALIDATE:-}" ] || [ -n "${api_diff}" ]; then
24 63
 	echo "Detected changes in ./api directory, checking for replace rule..."
25 64
 	if ! go list -mod=readonly -m -json github.com/moby/moby/api | jq -e '.Replace'; then
26 65
 		echo >&2 "ERROR: Changes detected in ./api but go.mod is missing a replace rule for github.com/moby/moby/api"
... ...
@@ -31,10 +70,16 @@ if [ -n "${TEST_FORCE_VALIDATE:-}" ] || [ -n "${api_files}" ]; then
31 31
 	fi
32 32
 fi
33 33
 
34
+echo >&2 "================================================"
35
+echo >&2 "client diff:"
36
+echo >&2 "${client_diff}"
37
+echo >&2 "================================================"
34 38
 # Check if changes to ./client require a replace rule in go.mod
35
-if [ -n "${TEST_FORCE_VALIDATE:-}" ] || [ -n "${client_files}" ]; then
39
+if [ -n "${TEST_FORCE_VALIDATE:-}" ] || [ -n "${client_diff}" ]; then
36 40
 	echo "Detected changes in ./client directory, checking for replace rule..."
37
-	if ! go list -mod=readonly -m -json github.com/moby/moby/client | jq -e '.Replace'; then
41
+	if only_removes_replace_rule_for_module "github.com/moby/moby/api" <<< "${client_diff}"; then
42
+		echo >&2 "✓ This seems to only remove the replace rule for github.com/moby/moby/api"
43
+	elif ! go list -mod=readonly -m -json github.com/moby/moby/client | jq -e '.Replace'; then
38 44
 		echo >&2 "ERROR: Changes detected in ./client but go.mod is missing a replace rule for github.com/moby/moby/client"
39 45
 		echo >&2 "Please run ./hack/vendor.sh replace"
40 46
 		has_errors=1