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