2021-09-27 18:55:12 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-09-27 18:55:12 +03:00
package doctor
import (
"bytes"
2022-01-20 02:26:57 +03:00
"context"
2023-07-07 08:31:56 +03:00
"errors"
2021-09-27 18:55:12 +03:00
"fmt"
"code.gitea.io/gitea/models/db"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-09 22:57:58 +03:00
"code.gitea.io/gitea/models/unit"
2021-12-10 04:27:50 +03:00
"code.gitea.io/gitea/modules/json"
2021-09-27 18:55:12 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
2021-11-17 15:34:35 +03:00
2021-09-27 18:55:12 +03:00
"xorm.io/builder"
)
// #16831 revealed that the dump command that was broken in 1.14.3-1.14.6 and 1.15.0 (#15885).
// This led to repo_unit and login_source cfg not being converted to JSON in the dump
// Unfortunately although it was hoped that there were only a few users affected it
// appears that many users are affected.
// We therefore need to provide a doctor command to fix this repeated issue #16961
func parseBool16961 ( bs [ ] byte ) ( bool , error ) {
if bytes . EqualFold ( bs , [ ] byte ( "%!s(bool=false)" ) ) {
return false , nil
}
if bytes . EqualFold ( bs , [ ] byte ( "%!s(bool=true)" ) ) {
return true , nil
}
return false , fmt . Errorf ( "unexpected bool format: %s" , string ( bs ) )
}
2021-12-10 04:27:50 +03:00
func fixUnitConfig16961 ( bs [ ] byte , cfg * repo_model . UnitConfig ) ( fixed bool , err error ) {
err = json . UnmarshalHandleDoubleEncode ( bs , & cfg )
2021-09-27 18:55:12 +03:00
if err == nil {
2023-07-07 08:31:56 +03:00
return false , nil
2021-09-27 18:55:12 +03:00
}
// Handle #16961
if string ( bs ) != "&{}" && len ( bs ) != 0 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
return true , nil
}
2021-12-10 04:27:50 +03:00
func fixExternalWikiConfig16961 ( bs [ ] byte , cfg * repo_model . ExternalWikiConfig ) ( fixed bool , err error ) {
err = json . UnmarshalHandleDoubleEncode ( bs , & cfg )
2021-09-27 18:55:12 +03:00
if err == nil {
2023-07-07 08:31:56 +03:00
return false , nil
2021-09-27 18:55:12 +03:00
}
if len ( bs ) < 3 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
if bs [ 0 ] != '&' || bs [ 1 ] != '{' || bs [ len ( bs ) - 1 ] != '}' {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
cfg . ExternalWikiURL = string ( bs [ 2 : len ( bs ) - 1 ] )
return true , nil
}
2021-12-10 04:27:50 +03:00
func fixExternalTrackerConfig16961 ( bs [ ] byte , cfg * repo_model . ExternalTrackerConfig ) ( fixed bool , err error ) {
err = json . UnmarshalHandleDoubleEncode ( bs , & cfg )
2021-09-27 18:55:12 +03:00
if err == nil {
2023-07-07 08:31:56 +03:00
return false , nil
2021-09-27 18:55:12 +03:00
}
// Handle #16961
if len ( bs ) < 3 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
if bs [ 0 ] != '&' || bs [ 1 ] != '{' || bs [ len ( bs ) - 1 ] != '}' {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
parts := bytes . Split ( bs [ 2 : len ( bs ) - 1 ] , [ ] byte { ' ' } )
if len ( parts ) != 3 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
cfg . ExternalTrackerURL = string ( bytes . Join ( parts [ : len ( parts ) - 2 ] , [ ] byte { ' ' } ) )
cfg . ExternalTrackerFormat = string ( parts [ len ( parts ) - 2 ] )
cfg . ExternalTrackerStyle = string ( parts [ len ( parts ) - 1 ] )
return true , nil
}
2021-12-10 04:27:50 +03:00
func fixPullRequestsConfig16961 ( bs [ ] byte , cfg * repo_model . PullRequestsConfig ) ( fixed bool , err error ) {
err = json . UnmarshalHandleDoubleEncode ( bs , & cfg )
2021-09-27 18:55:12 +03:00
if err == nil {
2023-07-07 08:31:56 +03:00
return false , nil
2021-09-27 18:55:12 +03:00
}
// Handle #16961
if len ( bs ) < 3 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
if bs [ 0 ] != '&' || bs [ 1 ] != '{' || bs [ len ( bs ) - 1 ] != '}' {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
// PullRequestsConfig was the following in 1.14
// type PullRequestsConfig struct {
// IgnoreWhitespaceConflicts bool
// AllowMerge bool
// AllowRebase bool
// AllowRebaseMerge bool
// AllowSquash bool
// AllowManualMerge bool
// AutodetectManualMerge bool
// }
//
// 1.15 added in addition:
// DefaultDeleteBranchAfterMerge bool
// DefaultMergeStyle MergeStyle
parts := bytes . Split ( bs [ 2 : len ( bs ) - 1 ] , [ ] byte { ' ' } )
if len ( parts ) < 7 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
var parseErr error
cfg . IgnoreWhitespaceConflicts , parseErr = parseBool16961 ( parts [ 0 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . AllowMerge , parseErr = parseBool16961 ( parts [ 1 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . AllowRebase , parseErr = parseBool16961 ( parts [ 2 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . AllowRebaseMerge , parseErr = parseBool16961 ( parts [ 3 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . AllowSquash , parseErr = parseBool16961 ( parts [ 4 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . AllowManualMerge , parseErr = parseBool16961 ( parts [ 5 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . AutodetectManualMerge , parseErr = parseBool16961 ( parts [ 6 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
// 1.14 unit
if len ( parts ) == 7 {
return true , nil
}
if len ( parts ) < 9 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
cfg . DefaultDeleteBranchAfterMerge , parseErr = parseBool16961 ( parts [ 7 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
2021-12-10 04:27:50 +03:00
cfg . DefaultMergeStyle = repo_model . MergeStyle ( string ( bytes . Join ( parts [ 8 : ] , [ ] byte { ' ' } ) ) )
2021-09-27 18:55:12 +03:00
return true , nil
}
2021-12-10 04:27:50 +03:00
func fixIssuesConfig16961 ( bs [ ] byte , cfg * repo_model . IssuesConfig ) ( fixed bool , err error ) {
err = json . UnmarshalHandleDoubleEncode ( bs , & cfg )
2021-09-27 18:55:12 +03:00
if err == nil {
2023-07-07 08:31:56 +03:00
return false , nil
2021-09-27 18:55:12 +03:00
}
// Handle #16961
if len ( bs ) < 3 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
if bs [ 0 ] != '&' || bs [ 1 ] != '{' || bs [ len ( bs ) - 1 ] != '}' {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
parts := bytes . Split ( bs [ 2 : len ( bs ) - 1 ] , [ ] byte { ' ' } )
if len ( parts ) != 3 {
2023-07-07 08:31:56 +03:00
return false , err
2021-09-27 18:55:12 +03:00
}
var parseErr error
cfg . EnableTimetracker , parseErr = parseBool16961 ( parts [ 0 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . AllowOnlyContributorsToTrackTime , parseErr = parseBool16961 ( parts [ 1 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
cfg . EnableDependencies , parseErr = parseBool16961 ( parts [ 2 ] )
if parseErr != nil {
2023-07-07 08:31:56 +03:00
return false , errors . Join ( err , parseErr )
2021-09-27 18:55:12 +03:00
}
return true , nil
}
2021-12-10 04:27:50 +03:00
func fixBrokenRepoUnit16961 ( repoUnit * repo_model . RepoUnit , bs [ ] byte ) ( fixed bool , err error ) {
2021-09-27 18:55:12 +03:00
// Shortcut empty or null values
if len ( bs ) == 0 {
return false , nil
}
2024-03-26 09:45:11 +03:00
var cfg any
err = json . UnmarshalHandleDoubleEncode ( bs , & cfg )
if err == nil {
return false , nil
}
2022-06-20 13:02:49 +03:00
switch repoUnit . Type {
2021-11-09 22:57:58 +03:00
case unit . TypeCode , unit . TypeReleases , unit . TypeWiki , unit . TypeProjects :
2021-12-10 04:27:50 +03:00
cfg := & repo_model . UnitConfig { }
2021-09-27 18:55:12 +03:00
repoUnit . Config = cfg
if fixed , err := fixUnitConfig16961 ( bs , cfg ) ; ! fixed {
return false , err
}
2021-11-09 22:57:58 +03:00
case unit . TypeExternalWiki :
2021-12-10 04:27:50 +03:00
cfg := & repo_model . ExternalWikiConfig { }
2021-09-27 18:55:12 +03:00
repoUnit . Config = cfg
if fixed , err := fixExternalWikiConfig16961 ( bs , cfg ) ; ! fixed {
return false , err
}
2021-11-09 22:57:58 +03:00
case unit . TypeExternalTracker :
2021-12-10 04:27:50 +03:00
cfg := & repo_model . ExternalTrackerConfig { }
2021-09-27 18:55:12 +03:00
repoUnit . Config = cfg
if fixed , err := fixExternalTrackerConfig16961 ( bs , cfg ) ; ! fixed {
return false , err
}
2021-11-09 22:57:58 +03:00
case unit . TypePullRequests :
2021-12-10 04:27:50 +03:00
cfg := & repo_model . PullRequestsConfig { }
2021-09-27 18:55:12 +03:00
repoUnit . Config = cfg
if fixed , err := fixPullRequestsConfig16961 ( bs , cfg ) ; ! fixed {
return false , err
}
2021-11-09 22:57:58 +03:00
case unit . TypeIssues :
2021-12-10 04:27:50 +03:00
cfg := & repo_model . IssuesConfig { }
2021-09-27 18:55:12 +03:00
repoUnit . Config = cfg
if fixed , err := fixIssuesConfig16961 ( bs , cfg ) ; ! fixed {
return false , err
}
default :
panic ( fmt . Sprintf ( "unrecognized repo unit type: %v" , repoUnit . Type ) )
}
return true , nil
}
2022-01-20 02:26:57 +03:00
func fixBrokenRepoUnits16961 ( ctx context . Context , logger log . Logger , autofix bool ) error {
2021-09-27 18:55:12 +03:00
// RepoUnit describes all units of a repository
type RepoUnit struct {
ID int64
RepoID int64
2021-11-09 22:57:58 +03:00
Type unit . Type
2021-09-27 18:55:12 +03:00
Config [ ] byte
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX CREATED" `
}
count := 0
err := db . Iterate (
2022-03-22 18:22:54 +03:00
ctx ,
2021-09-27 18:55:12 +03:00
builder . Gt {
"id" : 0 ,
} ,
2022-10-31 18:51:14 +03:00
func ( ctx context . Context , unit * RepoUnit ) error {
2021-09-27 18:55:12 +03:00
bs := unit . Config
2021-12-10 04:27:50 +03:00
repoUnit := & repo_model . RepoUnit {
2021-09-27 18:55:12 +03:00
ID : unit . ID ,
RepoID : unit . RepoID ,
Type : unit . Type ,
CreatedUnix : unit . CreatedUnix ,
}
if fixed , err := fixBrokenRepoUnit16961 ( repoUnit , bs ) ; ! fixed {
return err
}
count ++
if ! autofix {
return nil
}
2023-10-03 13:30:41 +03:00
return repo_model . UpdateRepoUnit ( ctx , repoUnit )
2021-09-27 18:55:12 +03:00
} ,
)
if err != nil {
2022-01-10 12:32:37 +03:00
logger . Critical ( "Unable to iterate across repounits to fix the broken units: Error %v" , err )
2021-09-27 18:55:12 +03:00
return err
}
if ! autofix {
2022-05-31 21:49:40 +03:00
if count == 0 {
logger . Info ( "Found no broken repo_units" )
} else {
logger . Warn ( "Found %d broken repo_units" , count )
}
2021-09-27 18:55:12 +03:00
return nil
}
logger . Info ( "Fixed %d broken repo_units" , count )
return nil
}
func init ( ) {
Register ( & Check {
Title : "Check for incorrectly dumped repo_units (See #16961)" ,
Name : "fix-broken-repo-units" ,
IsDefault : false ,
Run : fixBrokenRepoUnits16961 ,
Priority : 7 ,
} )
}