diff --git a/internal/schemas/manager/manager.go b/internal/schemas/manager/manager.go index ee87194..3fe60e7 100644 --- a/internal/schemas/manager/manager.go +++ b/internal/schemas/manager/manager.go @@ -231,10 +231,13 @@ func (m *Manager) updateLock(l *lock) error { return errors.Wrap(err, "failed to ensure schema directory exists") } - bs, err := json.Marshal(l) + bs, err := json.MarshalIndent(l, "", " ") if err != nil { return errors.Wrap(err, "failed to serialize schema lock") } + // Append a trailing newline so the file ends cleanly and edits to the last + // entry produce minimal diffs. + bs = append(bs, '\n') if err := afero.WriteFile(m.fs, lockFileName, bs, 0o600); err != nil { return errors.Wrap(err, "failed to write schema lock file") diff --git a/internal/schemas/manager/manager_test.go b/internal/schemas/manager/manager_test.go index 8dba1a2..62e1b18 100644 --- a/internal/schemas/manager/manager_test.go +++ b/internal/schemas/manager/manager_test.go @@ -171,6 +171,38 @@ func TestManager_Add(t *testing.T) { } } +func TestUpdateLockIsIndented(t *testing.T) { + // The lock file must be written with one entry per line (indented) so it is + // human-readable and avoids spurious merge conflicts. See issue #168. + fs := afero.NewMemMapFs() + m := New(fs, nil, nil) + + l := newLock() + l.Packages["xpkg://pkg.example/bar:v1.0.0"] = "sha256:bbb" + l.Packages["xpkg://pkg.example/foo:v0.5.2"] = "sha256:aaa" + + if err := m.updateLock(l); err != nil { + t.Fatalf("updateLock: %v", err) + } + + got, err := afero.ReadFile(fs, lockFileName) + if err != nil { + t.Fatalf("read lock: %v", err) + } + + // Map keys are marshalled in sorted order, so this golden is deterministic. + want := `{ + "packages": { + "xpkg://pkg.example/bar:v1.0.0": "sha256:bbb", + "xpkg://pkg.example/foo:v0.5.2": "sha256:aaa" + } +} +` + if diff := cmp.Diff(want, string(got)); diff != "" { + t.Errorf("lock file is not indented as expected (-want +got):\n%s", diff) + } +} + type mockGenerator struct { files map[string]string }