From 3b4e73680656bf3215bab893ae757c6c483066c0 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 16 Sep 2026 19:52:55 +0900 Subject: [PATCH] Parse lock file leniently --- CHANGELOG.md | 2 + private/bufpkg/bufconfig/buf_lock_file.go | 4 +- .../bufpkg/bufconfig/buf_lock_file_test.go | 71 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76c945721b..debfd16c6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - Add `--stdin-filepath` flag to `buf format`, which reads a single `.proto` file from stdin and writes the formatted result to stdout. The path is not read from disk, and is only used to report parse errors and diffs. +- Parse v2 `buf.lock` files non-strictly, ignoring unknown fields. A `buf.lock` written by a + newer `buf` that records additional fields no longer fails to parse on an older `buf`. ## [v1.73.0] - 2026-09-11 diff --git a/private/bufpkg/bufconfig/buf_lock_file.go b/private/bufpkg/bufconfig/buf_lock_file.go index 831b1755a1..e9b895ae4a 100644 --- a/private/bufpkg/bufconfig/buf_lock_file.go +++ b/private/bufpkg/bufconfig/buf_lock_file.go @@ -405,7 +405,9 @@ func readBufLockFile( return newBufLockFile(fileVersion, objectData, depModuleKeys, nil /* remotePluginKeys */, nil /* remotePolicyKeys */, nil /* remotePolicyPluginKeys */) case FileVersionV2: var externalBufLockFile externalBufLockFileV2 - if err := getUnmarshalStrict(allowJSON)(data, &externalBufLockFile); err != nil { + // Parse non-strict to allow unknown fields possibly written by a newer version of buf. + // Unknown fields are dropped and not written back. + if err := getUnmarshalNonStrict(allowJSON)(data, &externalBufLockFile); err != nil { return nil, err } depModuleKeys := make([]bufmodule.ModuleKey, len(externalBufLockFile.Deps)) diff --git a/private/bufpkg/bufconfig/buf_lock_file_test.go b/private/bufpkg/bufconfig/buf_lock_file_test.go index 04169bedb7..810955d49f 100644 --- a/private/bufpkg/bufconfig/buf_lock_file_test.go +++ b/private/bufpkg/bufconfig/buf_lock_file_test.go @@ -122,6 +122,77 @@ policies: ) } +// TestReadBufLockFileIgnoresUnknownFields covers that a buf.lock written by a newer buf +// with fields this version does not know is still readable, and that the unknown fields +// are dropped rather than written back. +func TestReadBufLockFileIgnoresUnknownFields(t *testing.T) { + t.Parallel() + + testReadWriteBufLockFileRoundTrip( + t, + // input + `version: v2 +future_top_level_field: value +deps: + - name: buf.testing/acme/date + future_dep_field: value + commit: ffded0b4cf6b47cab74da08d291a3c2f + digest: b5:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 +plugins: + - name: buf.testing/acme/plugin + future_plugin_field: value + commit: ffded0b4cf6b47cab74da08d291a3c2f + digest: p1:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 +policies: + - name: buf.testing/acme/policy + future_policy_field: value + commit: b8488077ea6d4f6d9562a337b98259c8 + digest: o1:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 + plugins: + - name: buf.testing/acme/plugin + future_policy_plugin_field: value + commit: ffded0b4cf6b47cab74da08d291a3c2f + digest: p1:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 +`, + // expected output + `version: v2 +deps: + - name: buf.testing/acme/date + commit: ffded0b4cf6b47cab74da08d291a3c2f + digest: b5:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 +plugins: + - name: buf.testing/acme/plugin + commit: ffded0b4cf6b47cab74da08d291a3c2f + digest: p1:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 +policies: + - name: buf.testing/acme/policy + commit: b8488077ea6d4f6d9562a337b98259c8 + digest: o1:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 + plugins: + - name: buf.testing/acme/plugin + commit: ffded0b4cf6b47cab74da08d291a3c2f + digest: p1:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 +`, + ) + + // v1 buf.lock files are still parsed strictly. They are no longer written, so there + // is no newer buf that could add a field to them. + _, err := ReadBufLockFile( + t.Context(), + strings.NewReader(testCleanYAMLData(`version: v1 +future_top_level_field: value +deps: + - remote: buf.testing + owner: acme + repository: date + commit: ffded0b4cf6b47cab74da08d291a3c2f + digest: shake256:24ed4f13925cf89ea0ae0127fa28540704c7ae14750af027270221b737a1ce658f8014ca2555f6f7fcd95ea84e071d33f37f86cc36d07fe0d0963329a5ec2462 +`)), + DefaultBufLockFileName, + ) + require.ErrorContains(t, err, "future_top_level_field") +} + func testReadBufLockFile( t *testing.T, inputBufLockFileData string,