Skip to content

Commit 9238ab7

Browse files
committed
Add repounit fixer for go-gitea#16961
Unfortunately a number of people appear to have been bitten by the bug in the dump command. This PR adds a doctor command to attempt to fix the broken repo_units Fix go-gitea#16961 Signed-off-by: Andrew Thornton <[email protected]>
1 parent 4a26550 commit 9238ab7

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed

models/repo_unit.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,8 @@ func getUnitsByRepoID(e db.Engine, repoID int64) (units []*RepoUnit, err error)
219219

220220
return units, nil
221221
}
222+
223+
func UpdateRepoUnit(unit *RepoUnit) error {
224+
_, err := db.GetEngine(db.DefaultContext).ID(unit.ID).Update(unit)
225+
return err
226+
}

modules/doctor/fix16961.go

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package doctor
6+
7+
import (
8+
"bytes"
9+
"fmt"
10+
"strconv"
11+
12+
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/models/db"
14+
"code.gitea.io/gitea/modules/log"
15+
"code.gitea.io/gitea/modules/timeutil"
16+
"xorm.io/builder"
17+
)
18+
19+
// #16831 revealed that the dump command that was broken in 1.14.3-1.14.6 and 1.15.0 (#15885).
20+
// This led to repo_unit and login_source cfg not being converted to JSON in the dump
21+
// Unfortunately although it was hoped that there were only a few users affected it
22+
// appears that many users are affected.
23+
24+
// We therefore need to provide a doctor command to fix this repeated issue #16961
25+
26+
func fixBrokenRepoUnits(logger log.Logger, autofix bool) error {
27+
// RepoUnit describes all units of a repository
28+
type RepoUnit struct {
29+
ID int64
30+
RepoID int64
31+
Type models.UnitType
32+
Config []byte
33+
CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
34+
}
35+
36+
count := 0
37+
38+
err := db.Iterate(
39+
db.DefaultContext,
40+
new(RepoUnit),
41+
builder.Eq{"1": "1"},
42+
func(idx int, bean interface{}) error {
43+
unit := bean.(*RepoUnit)
44+
45+
bs := unit.Config
46+
repoUnit := &models.RepoUnit{
47+
ID: unit.ID,
48+
RepoID: unit.RepoID,
49+
Type: unit.Type,
50+
CreatedUnix: unit.CreatedUnix,
51+
}
52+
53+
switch models.UnitType(unit.Type) {
54+
case models.UnitTypeCode, models.UnitTypeReleases, models.UnitTypeWiki, models.UnitTypeProjects:
55+
cfg := &models.UnitConfig{}
56+
repoUnit.Config = cfg
57+
58+
err := models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
59+
if err == nil {
60+
return nil
61+
}
62+
63+
// Handle #16961
64+
if string(bs) != "&{}" {
65+
return err
66+
}
67+
case models.UnitTypeExternalWiki:
68+
cfg := &models.ExternalWikiConfig{}
69+
repoUnit.Config = cfg
70+
err := models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
71+
if err == nil {
72+
return nil
73+
}
74+
75+
if len(bs) < 3 {
76+
return err
77+
}
78+
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
79+
return err
80+
}
81+
cfg.ExternalWikiURL = string(bs[2 : len(bs)-1])
82+
case models.UnitTypeExternalTracker:
83+
cfg := &models.ExternalTrackerConfig{}
84+
repoUnit.Config = cfg
85+
err := models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
86+
if err == nil {
87+
return nil
88+
}
89+
// Handle #16961
90+
if len(bs) < 3 {
91+
return err
92+
}
93+
94+
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
95+
return err
96+
}
97+
98+
parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
99+
if len(parts) != 3 {
100+
return err
101+
}
102+
103+
cfg.ExternalTrackerURL = string(bytes.Join(parts[:len(parts)-2], []byte{' '}))
104+
cfg.ExternalTrackerFormat = string(parts[len(parts)-2])
105+
cfg.ExternalTrackerStyle = string(parts[len(parts)-1])
106+
case models.UnitTypePullRequests:
107+
cfg := &models.PullRequestsConfig{}
108+
repoUnit.Config = cfg
109+
err := models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
110+
if err == nil {
111+
return nil
112+
}
113+
114+
// Handle #16961
115+
if len(bs) < 3 {
116+
return err
117+
}
118+
119+
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
120+
return err
121+
}
122+
123+
// PullRequestsConfig was the following in 1.14
124+
// type PullRequestsConfig struct {
125+
// IgnoreWhitespaceConflicts bool
126+
// AllowMerge bool
127+
// AllowRebase bool
128+
// AllowRebaseMerge bool
129+
// AllowSquash bool
130+
// AllowManualMerge bool
131+
// AutodetectManualMerge bool
132+
// }
133+
//
134+
// 1.15 added in addition:
135+
// DefaultDeleteBranchAfterMerge bool
136+
// DefaultMergeStyle MergeStyle
137+
parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
138+
if len(parts) < 7 {
139+
return err
140+
}
141+
142+
var parseErr error
143+
cfg.IgnoreWhitespaceConflicts, parseErr = strconv.ParseBool(string(parts[0]))
144+
if parseErr != nil {
145+
return err
146+
}
147+
cfg.AllowMerge, parseErr = strconv.ParseBool(string(parts[1]))
148+
if parseErr != nil {
149+
return err
150+
}
151+
cfg.AllowRebase, parseErr = strconv.ParseBool(string(parts[2]))
152+
if parseErr != nil {
153+
return err
154+
}
155+
cfg.AllowRebaseMerge, parseErr = strconv.ParseBool(string(parts[3]))
156+
if parseErr != nil {
157+
return err
158+
}
159+
cfg.AllowSquash, parseErr = strconv.ParseBool(string(parts[4]))
160+
if parseErr != nil {
161+
return err
162+
}
163+
cfg.AllowManualMerge, parseErr = strconv.ParseBool(string(parts[5]))
164+
if parseErr != nil {
165+
return err
166+
}
167+
cfg.AutodetectManualMerge, parseErr = strconv.ParseBool(string(parts[6]))
168+
if parseErr != nil {
169+
return err
170+
}
171+
172+
// 1.14 unit
173+
if len(parts) == 7 {
174+
count++
175+
if !autofix {
176+
return nil
177+
}
178+
return models.UpdateRepoUnit(repoUnit)
179+
}
180+
181+
if len(parts) < 9 {
182+
return err
183+
}
184+
185+
cfg.DefaultDeleteBranchAfterMerge, parseErr = strconv.ParseBool(string(parts[7]))
186+
if parseErr != nil {
187+
return err
188+
}
189+
190+
cfg.DefaultMergeStyle = models.MergeStyle(string(bytes.Join(parts[8:], []byte{' '})))
191+
case models.UnitTypeIssues:
192+
cfg := &models.IssuesConfig{}
193+
repoUnit.Config = cfg
194+
err := models.JSONUnmarshalHandleDoubleEncode(bs, &cfg)
195+
if err == nil {
196+
return nil
197+
}
198+
199+
// Handle #16961
200+
if len(bs) < 3 {
201+
return err
202+
}
203+
204+
if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
205+
return err
206+
}
207+
208+
parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
209+
if len(parts) != 3 {
210+
return err
211+
}
212+
var parseErr error
213+
cfg.EnableTimetracker, parseErr = strconv.ParseBool(string(parts[0]))
214+
if parseErr != nil {
215+
return err
216+
}
217+
cfg.AllowOnlyContributorsToTrackTime, parseErr = strconv.ParseBool(string(parts[1]))
218+
if parseErr != nil {
219+
return err
220+
}
221+
cfg.EnableDependencies, parseErr = strconv.ParseBool(string(parts[2]))
222+
if parseErr != nil {
223+
return err
224+
}
225+
default:
226+
panic(fmt.Sprintf("unrecognized repo unit type: %v", unit.Type))
227+
}
228+
229+
count++
230+
if !autofix {
231+
return nil
232+
}
233+
234+
return models.UpdateRepoUnit(repoUnit)
235+
},
236+
)
237+
238+
if err != nil {
239+
logger.Critical("Unable to iterate acrosss repounits to fix the broken units: Error %v", err)
240+
return err
241+
}
242+
243+
if !autofix {
244+
logger.Warn("Found %d broken repo_units", count)
245+
return nil
246+
}
247+
logger.Info("Fixed %d broken repo_units", count)
248+
249+
return nil
250+
}
251+
252+
func init() {
253+
Register(&Check{
254+
Title: "Check for incorrectly dumped repo_units (See #16961)",
255+
Name: "fix-broken-repo-units",
256+
IsDefault: false,
257+
Run: fixBrokenRepoUnits,
258+
Priority: 7,
259+
})
260+
}

0 commit comments

Comments
 (0)