Skip to content

Commit 252efbd

Browse files
drewcassidylunny
authored andcommitted
fix: LFS GC is never running because of a bug in the parsing of the INI file (#9202)
After go-gitea/gitea#22385 introduced LFS GC, it never worked due to a bug in the INI library: fields in structs embedded more than one level deep are not populated from the INI file. This PR fixes the issue by replacing the multi-level embedded struct with a single-level struct for parsing the cron.gc_lfs configuration. Added a new test for retrieving cron settings to demonstrate the bug in the INI package. --- Fix #9048 by cherrypicking the fix from Gitea Gitea PR: go-gitea/gitea#35198 Confirmed to work on my own instance, I now see the cron schedule for gc_lfs listed in the site admin menu where it was empty before <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Bug fixes - [PR](https://codeberg.org/forgejo/forgejo/pulls/9202): <!--number 9202 --><!--line 0 --><!--description TEZTIEdDIGlzIG5ldmVyIHJ1bm5pbmcgYmVjYXVzZSBvZiBhIGJ1ZyBpbiB0aGUgcGFyc2luZyBvZiB0aGUgSU5JIGZpbGU=-->LFS GC is never running because of a bug in the parsing of the INI file<!--description--> <!--end release-notes-assistant--> Co-authored-by: Lunny Xiao <[email protected]> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9202 Reviewed-by: Earl Warren <[email protected]> Co-authored-by: Andrew Cassidy <[email protected]> Co-committed-by: Andrew Cassidy <[email protected]>
1 parent c697de9 commit 252efbd

File tree

3 files changed

+127
-21
lines changed

3 files changed

+127
-21
lines changed

modules/setting/cron_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,56 @@ EXTEND = true
4242
assert.Equal(t, "white rabbit", extended.Second)
4343
assert.True(t, extended.Extend)
4444
}
45+
46+
// Test_getCronSettings2 tests that getCronSettings can not handle two levels of embedding
47+
func Test_getCronSettings2(t *testing.T) {
48+
type BaseStruct struct {
49+
Enabled bool
50+
RunAtStart bool
51+
Schedule string
52+
}
53+
54+
type Extended struct {
55+
BaseStruct
56+
Extend bool
57+
}
58+
type Extended2 struct {
59+
Extended
60+
Third string
61+
}
62+
63+
iniStr := `
64+
[cron.test]
65+
ENABLED = TRUE
66+
RUN_AT_START = TRUE
67+
SCHEDULE = @every 1h
68+
EXTEND = true
69+
THIRD = white rabbit
70+
`
71+
cfg, err := NewConfigProviderFromData(iniStr)
72+
require.NoError(t, err)
73+
74+
extended := &Extended2{
75+
Extended: Extended{
76+
BaseStruct: BaseStruct{
77+
Enabled: false,
78+
RunAtStart: false,
79+
Schedule: "@every 72h",
80+
},
81+
Extend: false,
82+
},
83+
Third: "black rabbit",
84+
}
85+
86+
_, err = getCronSettings(cfg, "test", extended)
87+
require.NoError(t, err)
88+
89+
// This confirms the first level of embedding works
90+
assert.Equal(t, "white rabbit", extended.Third)
91+
assert.True(t, extended.Extend)
92+
93+
// This confirms 2 levels of embedding doesn't work
94+
assert.False(t, extended.Enabled)
95+
assert.False(t, extended.RunAtStart)
96+
assert.Equal(t, "@every 72h", extended.Schedule)
97+
}

services/cron/tasks_extended.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -174,34 +174,35 @@ func registerDeleteOldSystemNotices() {
174174
})
175175
}
176176

177+
type GCLFSConfig struct {
178+
BaseConfig
179+
OlderThan time.Duration
180+
LastUpdatedMoreThanAgo time.Duration
181+
NumberToCheckPerRepo int64
182+
ProportionToCheckPerRepo float64
183+
}
184+
177185
func registerGCLFS() {
178186
if !setting.LFS.StartServer {
179187
return
180188
}
181-
type GCLFSConfig struct {
182-
OlderThanConfig
183-
LastUpdatedMoreThanAgo time.Duration
184-
NumberToCheckPerRepo int64
185-
ProportionToCheckPerRepo float64
186-
}
187189

188190
RegisterTaskFatal("gc_lfs", &GCLFSConfig{
189-
OlderThanConfig: OlderThanConfig{
190-
BaseConfig: BaseConfig{
191-
Enabled: false,
192-
RunAtStart: false,
193-
Schedule: "@every 24h",
194-
},
195-
// Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload
196-
// and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby
197-
// an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid
198-
// changes in new branches that might lead to lfs objects becoming temporarily unassociated with git
199-
// objects.
200-
//
201-
// It is likely that a week is potentially excessive but it should definitely be enough that any
202-
// unassociated LFS object is genuinely unassociated.
203-
OlderThan: 24 * time.Hour * 7,
191+
BaseConfig: BaseConfig{
192+
Enabled: false,
193+
RunAtStart: false,
194+
Schedule: "@every 24h",
204195
},
196+
// Only attempt to garbage collect lfs meta objects older than a week as the order of git lfs upload
197+
// and git object upload is not necessarily guaranteed. It's possible to imagine a situation whereby
198+
// an LFS object is uploaded but the git branch is not uploaded immediately, or there are some rapid
199+
// changes in new branches that might lead to lfs objects becoming temporarily unassociated with git
200+
// objects.
201+
//
202+
// It is likely that a week is potentially excessive but it should definitely be enough that any
203+
// unassociated LFS object is genuinely unassociated.
204+
OlderThan: 24 * time.Hour * 7,
205+
205206
// Only GC things that haven't been looked at in the past 3 days
206207
LastUpdatedMoreThanAgo: 24 * time.Hour * 3,
207208
NumberToCheckPerRepo: 100,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package cron
5+
6+
import (
7+
"testing"
8+
"time"
9+
10+
"forgejo.org/modules/setting"
11+
"forgejo.org/modules/test"
12+
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
func Test_GCLFSConfig(t *testing.T) {
18+
cfg, err := setting.NewConfigProviderFromData(`
19+
[cron.gc_lfs]
20+
ENABLED = true
21+
RUN_AT_START = true
22+
SCHEDULE = "@every 2h"
23+
OLDER_THAN = "1h"
24+
LAST_UPDATED_MORE_THAN_AGO = "7h"
25+
NUMBER_TO_CHECK_PER_REPO = 10
26+
PROPORTION_TO_CHECK_PER_REPO = 0.1
27+
`)
28+
require.NoError(t, err)
29+
defer test.MockVariableValue(&setting.CfgProvider, cfg)()
30+
31+
config := &GCLFSConfig{
32+
BaseConfig: BaseConfig{
33+
Enabled: false,
34+
RunAtStart: false,
35+
Schedule: "@every 24h",
36+
},
37+
OlderThan: 24 * time.Hour * 7,
38+
LastUpdatedMoreThanAgo: 24 * time.Hour * 3,
39+
NumberToCheckPerRepo: 100,
40+
ProportionToCheckPerRepo: 0.6,
41+
}
42+
43+
_, err = setting.GetCronSettings("gc_lfs", config)
44+
require.NoError(t, err)
45+
assert.True(t, config.Enabled)
46+
assert.True(t, config.RunAtStart)
47+
assert.Equal(t, "@every 2h", config.Schedule)
48+
assert.Equal(t, 1*time.Hour, config.OlderThan)
49+
assert.Equal(t, 7*time.Hour, config.LastUpdatedMoreThanAgo)
50+
assert.Equal(t, int64(10), config.NumberToCheckPerRepo)
51+
assert.InDelta(t, 0.1, config.ProportionToCheckPerRepo, 0.001)
52+
}

0 commit comments

Comments
 (0)