gitea
Зеркало из https://github.com/go-gitea/gitea
1// Copyright 2021 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package doctor
5
6import (
7"bytes"
8"context"
9"errors"
10"fmt"
11
12"code.gitea.io/gitea/models/db"
13repo_model "code.gitea.io/gitea/models/repo"
14"code.gitea.io/gitea/models/unit"
15"code.gitea.io/gitea/modules/json"
16"code.gitea.io/gitea/modules/log"
17"code.gitea.io/gitea/modules/timeutil"
18
19"xorm.io/builder"
20)
21
22// #16831 revealed that the dump command that was broken in 1.14.3-1.14.6 and 1.15.0 (#15885).
23// This led to repo_unit and login_source cfg not being converted to JSON in the dump
24// Unfortunately although it was hoped that there were only a few users affected it
25// appears that many users are affected.
26
27// We therefore need to provide a doctor command to fix this repeated issue #16961
28
29func parseBool16961(bs []byte) (bool, error) {
30if bytes.EqualFold(bs, []byte("%!s(bool=false)")) {
31return false, nil
32}
33
34if bytes.EqualFold(bs, []byte("%!s(bool=true)")) {
35return true, nil
36}
37
38return false, fmt.Errorf("unexpected bool format: %s", string(bs))
39}
40
41func fixUnitConfig16961(bs []byte, cfg *repo_model.UnitConfig) (fixed bool, err error) {
42err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
43if err == nil {
44return false, nil
45}
46
47// Handle #16961
48if string(bs) != "&{}" && len(bs) != 0 {
49return false, err
50}
51
52return true, nil
53}
54
55func fixExternalWikiConfig16961(bs []byte, cfg *repo_model.ExternalWikiConfig) (fixed bool, err error) {
56err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
57if err == nil {
58return false, nil
59}
60
61if len(bs) < 3 {
62return false, err
63}
64if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
65return false, err
66}
67cfg.ExternalWikiURL = string(bs[2 : len(bs)-1])
68return true, nil
69}
70
71func fixExternalTrackerConfig16961(bs []byte, cfg *repo_model.ExternalTrackerConfig) (fixed bool, err error) {
72err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
73if err == nil {
74return false, nil
75}
76// Handle #16961
77if len(bs) < 3 {
78return false, err
79}
80
81if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
82return false, err
83}
84
85parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
86if len(parts) != 3 {
87return false, err
88}
89
90cfg.ExternalTrackerURL = string(bytes.Join(parts[:len(parts)-2], []byte{' '}))
91cfg.ExternalTrackerFormat = string(parts[len(parts)-2])
92cfg.ExternalTrackerStyle = string(parts[len(parts)-1])
93return true, nil
94}
95
96func fixPullRequestsConfig16961(bs []byte, cfg *repo_model.PullRequestsConfig) (fixed bool, err error) {
97err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
98if err == nil {
99return false, nil
100}
101
102// Handle #16961
103if len(bs) < 3 {
104return false, err
105}
106
107if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
108return false, err
109}
110
111// PullRequestsConfig was the following in 1.14
112// type PullRequestsConfig struct {
113// IgnoreWhitespaceConflicts bool
114// AllowMerge bool
115// AllowRebase bool
116// AllowRebaseMerge bool
117// AllowSquash bool
118// AllowManualMerge bool
119// AutodetectManualMerge bool
120// }
121//
122// 1.15 added in addition:
123// DefaultDeleteBranchAfterMerge bool
124// DefaultMergeStyle MergeStyle
125parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
126if len(parts) < 7 {
127return false, err
128}
129
130var parseErr error
131cfg.IgnoreWhitespaceConflicts, parseErr = parseBool16961(parts[0])
132if parseErr != nil {
133return false, errors.Join(err, parseErr)
134}
135cfg.AllowMerge, parseErr = parseBool16961(parts[1])
136if parseErr != nil {
137return false, errors.Join(err, parseErr)
138}
139cfg.AllowRebase, parseErr = parseBool16961(parts[2])
140if parseErr != nil {
141return false, errors.Join(err, parseErr)
142}
143cfg.AllowRebaseMerge, parseErr = parseBool16961(parts[3])
144if parseErr != nil {
145return false, errors.Join(err, parseErr)
146}
147cfg.AllowSquash, parseErr = parseBool16961(parts[4])
148if parseErr != nil {
149return false, errors.Join(err, parseErr)
150}
151cfg.AllowManualMerge, parseErr = parseBool16961(parts[5])
152if parseErr != nil {
153return false, errors.Join(err, parseErr)
154}
155cfg.AutodetectManualMerge, parseErr = parseBool16961(parts[6])
156if parseErr != nil {
157return false, errors.Join(err, parseErr)
158}
159
160// 1.14 unit
161if len(parts) == 7 {
162return true, nil
163}
164
165if len(parts) < 9 {
166return false, err
167}
168
169cfg.DefaultDeleteBranchAfterMerge, parseErr = parseBool16961(parts[7])
170if parseErr != nil {
171return false, errors.Join(err, parseErr)
172}
173
174cfg.DefaultMergeStyle = repo_model.MergeStyle(string(bytes.Join(parts[8:], []byte{' '})))
175return true, nil
176}
177
178func fixIssuesConfig16961(bs []byte, cfg *repo_model.IssuesConfig) (fixed bool, err error) {
179err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
180if err == nil {
181return false, nil
182}
183
184// Handle #16961
185if len(bs) < 3 {
186return false, err
187}
188
189if bs[0] != '&' || bs[1] != '{' || bs[len(bs)-1] != '}' {
190return false, err
191}
192
193parts := bytes.Split(bs[2:len(bs)-1], []byte{' '})
194if len(parts) != 3 {
195return false, err
196}
197var parseErr error
198cfg.EnableTimetracker, parseErr = parseBool16961(parts[0])
199if parseErr != nil {
200return false, errors.Join(err, parseErr)
201}
202cfg.AllowOnlyContributorsToTrackTime, parseErr = parseBool16961(parts[1])
203if parseErr != nil {
204return false, errors.Join(err, parseErr)
205}
206cfg.EnableDependencies, parseErr = parseBool16961(parts[2])
207if parseErr != nil {
208return false, errors.Join(err, parseErr)
209}
210return true, nil
211}
212
213func fixBrokenRepoUnit16961(repoUnit *repo_model.RepoUnit, bs []byte) (fixed bool, err error) {
214// Shortcut empty or null values
215if len(bs) == 0 {
216return false, nil
217}
218
219var cfg any
220err = json.UnmarshalHandleDoubleEncode(bs, &cfg)
221if err == nil {
222return false, nil
223}
224
225switch repoUnit.Type {
226case unit.TypeCode, unit.TypeReleases, unit.TypeWiki, unit.TypeProjects:
227cfg := &repo_model.UnitConfig{}
228repoUnit.Config = cfg
229if fixed, err := fixUnitConfig16961(bs, cfg); !fixed {
230return false, err
231}
232case unit.TypeExternalWiki:
233cfg := &repo_model.ExternalWikiConfig{}
234repoUnit.Config = cfg
235
236if fixed, err := fixExternalWikiConfig16961(bs, cfg); !fixed {
237return false, err
238}
239case unit.TypeExternalTracker:
240cfg := &repo_model.ExternalTrackerConfig{}
241repoUnit.Config = cfg
242if fixed, err := fixExternalTrackerConfig16961(bs, cfg); !fixed {
243return false, err
244}
245case unit.TypePullRequests:
246cfg := &repo_model.PullRequestsConfig{}
247repoUnit.Config = cfg
248
249if fixed, err := fixPullRequestsConfig16961(bs, cfg); !fixed {
250return false, err
251}
252case unit.TypeIssues:
253cfg := &repo_model.IssuesConfig{}
254repoUnit.Config = cfg
255if fixed, err := fixIssuesConfig16961(bs, cfg); !fixed {
256return false, err
257}
258default:
259panic(fmt.Sprintf("unrecognized repo unit type: %v", repoUnit.Type))
260}
261return true, nil
262}
263
264func fixBrokenRepoUnits16961(ctx context.Context, logger log.Logger, autofix bool) error {
265// RepoUnit describes all units of a repository
266type RepoUnit struct {
267ID int64
268RepoID int64
269Type unit.Type
270Config []byte
271CreatedUnix timeutil.TimeStamp `xorm:"INDEX CREATED"`
272}
273
274count := 0
275
276err := db.Iterate(
277ctx,
278builder.Gt{
279"id": 0,
280},
281func(ctx context.Context, unit *RepoUnit) error {
282bs := unit.Config
283repoUnit := &repo_model.RepoUnit{
284ID: unit.ID,
285RepoID: unit.RepoID,
286Type: unit.Type,
287CreatedUnix: unit.CreatedUnix,
288}
289
290if fixed, err := fixBrokenRepoUnit16961(repoUnit, bs); !fixed {
291return err
292}
293
294count++
295if !autofix {
296return nil
297}
298
299return repo_model.UpdateRepoUnit(ctx, repoUnit)
300},
301)
302if err != nil {
303logger.Critical("Unable to iterate across repounits to fix the broken units: Error %v", err)
304return err
305}
306
307if !autofix {
308if count == 0 {
309logger.Info("Found no broken repo_units")
310} else {
311logger.Warn("Found %d broken repo_units", count)
312}
313return nil
314}
315logger.Info("Fixed %d broken repo_units", count)
316
317return nil
318}
319
320func init() {
321Register(&Check{
322Title: "Check for incorrectly dumped repo_units (See #16961)",
323Name: "fix-broken-repo-units",
324IsDefault: false,
325Run: fixBrokenRepoUnits16961,
326Priority: 7,
327})
328}
329