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