From ba64bf8382e841b860e027112c119a8095558f27 Mon Sep 17 00:00:00 2001 From: Rahmat Eka <155574253+rhmatzeka@users.noreply.github.com> Date: Sun, 1 Nov 2026 09:55:02 +0700 Subject: [PATCH] file: drop removed keys and sections on Reload Reload parsed every data source on top of the sections already in memory, so a key or section deleted from the source stayed around after a reload, and with AllowNonUniqueSections every reload appended another copy of each section. Reload now clears the sections before parsing. Append keeps merging on top of the current sections, so sections created in memory before Append are not lost. Fixes #418 --- file.go | 22 +++++++++++++++++++--- file_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/file.go b/file.go index 58beabc..58729a0 100644 --- a/file.go +++ b/file.go @@ -297,8 +297,24 @@ func (f *File) reload(s dataSource) error { return f.parse(r) } -// Reload reloads and parses all data sources. -func (f *File) Reload() (err error) { +// Reload reloads and parses all data sources. Sections and keys that are no +// longer in any data source are removed, and changes made in memory are +// discarded. +func (f *File) Reload() error { + if f.BlockMode { + f.lock.Lock() + } + f.sectionList = nil + f.sectionIndexes = nil + f.sections = make(map[string][]*Section) + if f.BlockMode { + f.lock.Unlock() + } + return f.loadSources() +} + +// loadSources parses all data sources on top of the sections already in f. +func (f *File) loadSources() (err error) { for _, s := range f.dataSources { if err = f.reload(s); err != nil { // In loose mode, we create an empty default section for nonexistent files. @@ -329,7 +345,7 @@ func (f *File) Append(source interface{}, others ...interface{}) error { } f.dataSources = append(f.dataSources, ds) } - return f.Reload() + return f.loadSources() } func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { diff --git a/file_test.go b/file_test.go index 306c0c8..eff9022 100644 --- a/file_test.go +++ b/file_test.go @@ -17,6 +17,7 @@ package ini import ( "bytes" "os" + "path/filepath" "runtime" "sort" "testing" @@ -535,3 +536,34 @@ v = 3 require.NoError(t, f.Reload()) assert.Equal(t, []string{"1", "2", "3"}, f.Section("slice").Key("v").ValueWithShadows()) } + +// Inspired by https://github.com/go-ini/ini/issues/418 +func TestReloadDropsRemovedKeysAndSections(t *testing.T) { + path := filepath.Join(t.TempDir(), "settings.ini") + require.NoError(t, os.WriteFile(path, []byte("[app]\nold = 1\nkeep = 2\n[gone]\nk = v\n"), 0600)) + + f, err := Load(path) + require.NoError(t, err) + require.True(t, f.Section("app").HasKey("old")) + + require.NoError(t, os.WriteFile(path, []byte("[app]\nkeep = 3\n"), 0600)) + require.NoError(t, f.Reload()) + + assert.Equal(t, map[string]string{"keep": "3"}, f.Section("app").KeysHash()) + assert.False(t, f.Section("app").HasKey("old")) + assert.Equal(t, []string{DefaultSection, "app"}, f.SectionStrings()) +} + +func TestReloadDoesNotDuplicateNonUniqueSections(t *testing.T) { + f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(` +[peer] +name = a +[peer] +name = b +`)) + require.NoError(t, err) + require.Len(t, f.SectionStrings(), 3) + + require.NoError(t, f.Reload()) + assert.Equal(t, []string{DefaultSection, "peer", "peer"}, f.SectionStrings()) +}