Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doctor check & fix db consistency #11111

Merged
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
4120aa1
rename check-db to check-db-version to have multible db checks
6543 Apr 17, 2020
2627b81
add runDoctorCheckDBConsistency
6543 Apr 17, 2020
fe48054
add find pulls without existing issues
6543 Apr 17, 2020
21330fd
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 Apr 17, 2020
7e261d0
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 Apr 18, 2020
ea605b7
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 Apr 18, 2020
5cfa0d4
move Issue Deletion to issue.go
6543 Apr 19, 2020
b8b5de3
use DeleteIssuesByIDs
6543 Apr 19, 2020
ec02b3a
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 Apr 19, 2020
17589b9
use err
6543 Apr 19, 2020
d6f1f0e
just another one
6543 Apr 19, 2020
bd8a751
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 Apr 20, 2020
6d6ed69
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 1, 2020
a3ff964
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 5, 2020
8a6fbfb
move things into models
6543 May 5, 2020
7231236
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 5, 2020
2796aee
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 6, 2020
f738ee9
test ...
6543 May 6, 2020
f6f44e8
restructure things ...
6543 May 6, 2020
8da4ce2
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 6, 2020
44ec589
delete cracy thing
6543 May 6, 2020
280599d
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 7, 2020
dae04ee
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 8, 2020
51503eb
as per @guillep2k
6543 May 9, 2020
1710227
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 9, 2020
08debec
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 9, 2020
b56f267
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 12, 2020
c9b9645
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 16, 2020
2f4ca1e
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 16, 2020
a0f626e
make sure DB version is uptodate
6543 May 16, 2020
2f7ba56
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 16, 2020
44d33cb
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 21, 2020
0fffab5
move DB tasks next to each other
6543 May 21, 2020
9073130
return error if try to fix an outdated DB and do nothing
6543 May 21, 2020
2b4bd51
renamed as per @guillep2k
6543 May 21, 2020
3fe8d16
use Distinct
6543 May 21, 2020
50808fa
Fix: converting NULL to int64 is unsupported
6543 May 21, 2020
2e8d900
refactor
6543 May 21, 2020
9a324b5
outdatedDB message Update
6543 May 21, 2020
b862ee8
remove redundant error & always warn on old version
6543 May 21, 2020
610ddac
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 21, 2020
65497f2
Merge branch 'master' into doctor-check-fix-db-consistency_10280
lunny May 22, 2020
0553629
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 23, 2020
826e2f2
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 23, 2020
cba3390
whatever it takes
6543 May 23, 2020
a9dcb3e
only sync if needed
6543 May 27, 2020
b3cbc73
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 27, 2020
3fd2f1c
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 28, 2020
a76c2f2
Revert "only sync if needed"
6543 May 28, 2020
704e359
add documentation
6543 May 28, 2020
f9bd1a4
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 28, 2020
0ae896d
its Nr 11111
6543 May 28, 2020
d2e438d
its an error now
6543 May 28, 2020
9d8f61e
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 28, 2020
7dca8b4
Merge branch 'master' into doctor-check-fix-db-consistency_10280
6543 May 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 87 additions & 1 deletion cmd/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,17 @@ var checklist = []check{
},
{
title: "Check Database Version",
name: "check-db",
name: "check-db-version",
isDefault: true,
f: runDoctorCheckDBVersion,
abortIfFailed: true,
},
{
title: "Check consistency of database",
name: "check-db-consistency",
isDefault: false,
f: runDoctorCheckDBConsistency,
},
{
title: "Check if OpenSSH authorized_keys file is up-to-date",
name: "authorized_keys",
Expand Down Expand Up @@ -495,3 +501,83 @@ func runDoctorScriptType(ctx *cli.Context) ([]string, error) {
}
return []string{fmt.Sprintf("ScriptType %s is on the current PATH at %s", setting.ScriptType, path)}, nil
}

func runDoctorCheckDBConsistency(ctx *cli.Context) ([]string, error) {
guillep2k marked this conversation as resolved.
Show resolved Hide resolved
var results []string

// make sure DB version is uptodate
if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil {
results = append(results, "Warning: model version on the database does not match the current Gitea version. Model consistency can be checked but not fixed until the database is upgraded.")
if ctx.Bool("fix") {
return results, nil
}
}

//find labels without existing repo or org
count, err := models.CountOrphanedLabels()
if err != nil {
return nil, err
}
if count > 0 {
if ctx.Bool("fix") {
if err = models.DeleteOrphanedLabels(); err != nil {
return nil, err
}
results = append(results, fmt.Sprintf("%d labels without existing repository/organisation deleted", count))
} else {
results = append(results, fmt.Sprintf("%d labels without existing repository/organisation", count))
}
}

//find issues without existing repository
count, err = models.CountOrphanedIssues()
if err != nil {
return nil, err
}
if count > 0 {
if ctx.Bool("fix") {
if err = models.DeleteOrphanedIssues(); err != nil {
return nil, err
}
results = append(results, fmt.Sprintf("%d issues without existing repository deleted", count))
} else {
results = append(results, fmt.Sprintf("%d issues without existing repository", count))
}
}

//find pulls without existing issues
count, err = models.CountOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id")
if err != nil {
return nil, err
}
if count > 0 {
if ctx.Bool("fix") {
if err = models.DeleteOrphanedObjects("pull_request", "issue", "pull_request.issue_id=issue.id"); err != nil {
return nil, err
}
results = append(results, fmt.Sprintf("%d pull requests without existing issue deleted", count))
} else {
results = append(results, fmt.Sprintf("%d pull requests without existing issue", count))
}
}

//find tracked times without existing issues/pulls
count, err = models.CountOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id")
if err != nil {
return nil, err
}
if count > 0 {
if ctx.Bool("fix") {
if err = models.DeleteOrphanedObjects("tracked_time", "issue", "tracked_time.issue_id=issue.id"); err != nil {
return nil, err
}
results = append(results, fmt.Sprintf("%d tracked times without existing issue deleted", count))
} else {
results = append(results, fmt.Sprintf("%d tracked times without existing issue", count))
}
}

//ToDo: function to recalc all counters

return results, nil
}
116 changes: 116 additions & 0 deletions models/consistency.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
"xorm.io/builder"
)

// consistencyCheckable a type that can be tested for database consistency
Expand Down Expand Up @@ -167,3 +168,118 @@ func (action *Action) checkForConsistency(t *testing.T) {
repo := AssertExistsAndLoadBean(t, &Repository{ID: action.RepoID}).(*Repository)
assert.Equal(t, repo.IsPrivate, action.IsPrivate, "action: %+v", action)
}

// CountOrphanedLabels return count of labels witch are broken and not accessible via ui anymore
func CountOrphanedLabels() (int64, error) {
noref, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Count("label.id")
if err != nil {
return 0, err
}

norepo, err := x.Table("label").
Join("LEFT", "repository", "label.repo_id=repository.id").
Where(builder.IsNull{"repository.id"}).And(builder.Gt{"label.repo_id": 0}).
Count("id")
if err != nil {
return 0, err
}

noorg, err := x.Table("label").
Join("LEFT", "`user`", "label.org_id=`user`.id").
Where(builder.IsNull{"`user`.id"}).And(builder.Gt{"label.org_id": 0}).
Count("id")
if err != nil {
return 0, err
}

return noref + norepo + noorg, nil
}

// DeleteOrphanedLabels delete labels witch are broken and not accessible via ui anymore
func DeleteOrphanedLabels() error {
// delete labels with no reference
if _, err := x.Table("label").Where("repo_id=? AND org_id=?", 0, 0).Delete(new(Label)); err != nil {
return err
}

// delete labels with none existing repos
if _, err := x.In("id", builder.Select("label.id").From("label").
Join("LEFT", "repository", "label.repo_id=repository.id").
Where(builder.IsNull{"repository.id"}).And(builder.Gt{"label.repo_id": 0})).
Delete(Label{}); err != nil {
return err
}

// delete labels with none existing orgs
if _, err := x.In("id", builder.Select("label.id").From("label").
Join("LEFT", "`user`", "label.org_id=`user`.id").
Where(builder.IsNull{"`user`.id"}).And(builder.Gt{"label.org_id": 0})).
Delete(Label{}); err != nil {
return err
}

return nil
}

// CountOrphanedIssues count issues without a repo
func CountOrphanedIssues() (int64, error) {
return x.Table("issue").
Join("LEFT", "repository", "issue.repo_id=repository.id").
Where(builder.IsNull{"repository.id"}).
Count("id")
}

// DeleteOrphanedIssues delete issues without a repo
func DeleteOrphanedIssues() error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}

var ids []int64

if err := sess.Table("issue").Distinct("issue.repo_id").
Join("LEFT", "repository", "issue.repo_id=repository.id").
6543 marked this conversation as resolved.
Show resolved Hide resolved
Where(builder.IsNull{"repository.id"}).GroupBy("issue.repo_id").
Find(&ids); err != nil {
return err
}

var attachmentPaths []string
for i := range ids {
paths, err := deleteIssuesByRepoID(sess, ids[i])
if err != nil {
return err
}
attachmentPaths = append(attachmentPaths, paths...)
}

if err := sess.Commit(); err != nil {
return err
}

// Remove issue attachment files.
for i := range attachmentPaths {
removeAllWithNotice(x, "Delete issue attachment", attachmentPaths[i])
}
return nil
}

// CountOrphanedObjects count subjects with have no existing refobject anymore
func CountOrphanedObjects(subject, refobject, joinCond string) (int64, error) {
return x.Table("`"+subject+"`").
Join("LEFT", refobject, joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"}).
Count("id")
}

// DeleteOrphanedObjects delete subjects with have no existing refobject anymore
func DeleteOrphanedObjects(subject, refobject, joinCond string) error {
_, err := x.In("id", builder.Select("`"+subject+"`.id").
From("`"+subject+"`").
Join("LEFT", "`"+refobject+"`", joinCond).
Where(builder.IsNull{"`" + refobject + "`.id"})).
Delete("`" + subject + "`")
return err
}
67 changes: 67 additions & 0 deletions models/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1916,3 +1916,70 @@ func UpdateReactionsMigrationsByType(gitServiceType structs.GitServiceType, orig
})
return err
}

func deleteIssuesByRepoID(sess Engine, repoID int64) (attachmentPaths []string, err error) {
deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"issue.repo_id": repoID})

// Delete comments and attachments
if _, err = sess.In("issue_id", deleteCond).
Delete(&Comment{}); err != nil {
return
}

// Dependencies for issues in this repository
if _, err = sess.In("issue_id", deleteCond).
Delete(&IssueDependency{}); err != nil {
return
}

// Delete dependencies for issues in other repositories
if _, err = sess.In("dependency_id", deleteCond).
Delete(&IssueDependency{}); err != nil {
return
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&IssueUser{}); err != nil {
return
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&Reaction{}); err != nil {
return
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&IssueWatch{}); err != nil {
return
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&Stopwatch{}); err != nil {
return
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&TrackedTime{}); err != nil {
return
}

var attachments []*Attachment
if err = sess.In("issue_id", deleteCond).
Find(&attachments); err != nil {
return
}
for j := range attachments {
attachmentPaths = append(attachmentPaths, attachments[j].LocalPath())
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&Attachment{}); err != nil {
return
}

if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil {
return
}

return
}
65 changes: 3 additions & 62 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"code.gitea.io/gitea/modules/util"

"github.com/unknwon/com"
"xorm.io/builder"
)

var (
Expand Down Expand Up @@ -1586,67 +1585,9 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
return fmt.Errorf("deleteBeans: %v", err)
}

deleteCond := builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID})
// Delete comments and attachments
if _, err = sess.In("issue_id", deleteCond).
Delete(&Comment{}); err != nil {
return err
}

// Dependencies for issues in this repository
if _, err = sess.In("issue_id", deleteCond).
Delete(&IssueDependency{}); err != nil {
return err
}

// Delete dependencies for issues in other repositories
if _, err = sess.In("dependency_id", deleteCond).
Delete(&IssueDependency{}); err != nil {
return err
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&IssueUser{}); err != nil {
return err
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&Reaction{}); err != nil {
return err
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&IssueWatch{}); err != nil {
return err
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&Stopwatch{}); err != nil {
return err
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&TrackedTime{}); err != nil {
return err
}

attachments = attachments[:0]
if err = sess.Join("INNER", "issue", "issue.id = attachment.issue_id").
Where("issue.repo_id = ?", repoID).
Find(&attachments); err != nil {
return err
}
attachmentPaths := make([]string, 0, len(attachments))
for j := range attachments {
attachmentPaths = append(attachmentPaths, attachments[j].LocalPath())
}

if _, err = sess.In("issue_id", deleteCond).
Delete(&Attachment{}); err != nil {
return err
}

if _, err = sess.Delete(&Issue{RepoID: repoID}); err != nil {
// Delete Issues and related objects
var attachmentPaths []string
if attachmentPaths, err = deleteIssuesByRepoID(sess, repoID); err != nil {
return err
}

Expand Down