From 4f32abaf941ba30ade1f96930a05d14f1c6a6782 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 8 Sep 2023 12:51:15 +0800 Subject: [PATCH] move repository deletion to service layer (#26948) Co-authored-by: wxiaoguang --- models/activities/main_test.go | 1 + models/auth/main_test.go | 1 + models/git/main_test.go | 2 + models/issues/main_test.go | 2 + models/main_test.go | 1 + models/org_team.go | 79 ---- models/org_team_test.go | 30 -- models/organization/main_test.go | 2 + models/packages/package_test.go | 2 + models/perm/access/main_test.go | 2 + models/repo.go | 326 -------------- models/repo/main_test.go | 4 +- models/system/main_test.go | 4 +- models/user/main_test.go | 2 + modules/activitypub/client_test.go | 2 - modules/activitypub/main_test.go | 4 + modules/indexer/code/indexer_test.go | 2 + modules/indexer/issues/indexer_test.go | 2 + modules/indexer/stats/indexer_test.go | 2 + modules/repository/main_test.go | 2 + routers/api/v1/org/team.go | 3 +- routers/api/v1/repo/migrate.go | 4 +- routers/api/v1/repo/teams.go | 8 +- routers/web/org/teams.go | 3 +- routers/web/repo/setting/collaboration.go | 3 +- routers/web/repo/setting/settings_test.go | 10 +- services/asymkey/main_test.go | 3 + services/attachment/attachment_test.go | 2 + services/convert/main_test.go | 2 + services/feed/action_test.go | 2 + services/gitdiff/main_test.go | 2 + services/issue/main_test.go | 2 + services/mailer/main_test.go | 2 + services/migrations/gitea_uploader.go | 2 +- services/org/repo.go | 2 +- services/packages/cargo/index.go | 2 +- services/pull/main_test.go | 2 + services/release/release_test.go | 2 + services/repository/archiver/archiver_test.go | 2 + services/repository/check.go | 3 +- services/repository/create.go | 7 +- services/repository/create_test.go | 10 +- services/repository/delete.go | 424 ++++++++++++++++++ services/repository/delete_test.go | 45 ++ services/repository/files/content_test.go | 2 + services/repository/repository.go | 5 +- services/task/task.go | 3 +- services/user/user.go | 3 +- services/webhook/main_test.go | 1 + services/wiki/wiki_test.go | 2 + tests/integration/api_repo_test.go | 4 +- tests/integration/mirror_pull_test.go | 2 +- tests/integration/mirror_push_test.go | 2 +- 53 files changed, 567 insertions(+), 476 deletions(-) create mode 100644 services/repository/delete.go create mode 100644 services/repository/delete_test.go diff --git a/models/activities/main_test.go b/models/activities/main_test.go index a8740f53c4..6be54db658 100644 --- a/models/activities/main_test.go +++ b/models/activities/main_test.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" ) func TestMain(m *testing.M) { diff --git a/models/auth/main_test.go b/models/auth/main_test.go index 3205d8816f..f8cbf3bd54 100644 --- a/models/auth/main_test.go +++ b/models/auth/main_test.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" _ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/auth" _ "code.gitea.io/gitea/models/perm/access" diff --git a/models/git/main_test.go b/models/git/main_test.go index 5ef9cde607..a8658d70c4 100644 --- a/models/git/main_test.go +++ b/models/git/main_test.go @@ -10,6 +10,8 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" ) func TestMain(m *testing.M) { diff --git a/models/issues/main_test.go b/models/issues/main_test.go index 9fbe294f70..190df027f4 100644 --- a/models/issues/main_test.go +++ b/models/issues/main_test.go @@ -11,6 +11,8 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/repo" _ "code.gitea.io/gitea/models/user" diff --git a/models/main_test.go b/models/main_test.go index d490507649..c09b661d2c 100644 --- a/models/main_test.go +++ b/models/main_test.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + _ "code.gitea.io/gitea/models/actions" _ "code.gitea.io/gitea/models/system" "github.com/stretchr/testify/assert" diff --git a/models/org_team.go b/models/org_team.go index be0c859a4b..f887c9ee98 100644 --- a/models/org_team.go +++ b/models/org_team.go @@ -151,85 +151,6 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error return nil } -// HasRepository returns true if given repository belong to team. -func HasRepository(t *organization.Team, repoID int64) bool { - return organization.HasTeamRepo(db.DefaultContext, t.OrgID, t.ID, repoID) -} - -// removeRepository removes a repository from a team and recalculates access -// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted) -func removeRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) { - e := db.GetEngine(ctx) - if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil { - return err - } - - t.NumRepos-- - if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil { - return err - } - - // Don't need to recalculate when delete a repository from organization. - if recalculate { - if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil { - return err - } - } - - teamUsers, err := organization.GetTeamUsersByTeamID(ctx, t.ID) - if err != nil { - return fmt.Errorf("getTeamUsersByTeamID: %w", err) - } - for _, teamUser := range teamUsers { - has, err := access_model.HasAccess(ctx, teamUser.UID, repo) - if err != nil { - return err - } else if has { - continue - } - - if err = repo_model.WatchRepo(ctx, teamUser.UID, repo.ID, false); err != nil { - return err - } - - // Remove all IssueWatches a user has subscribed to in the repositories - if err := issues_model.RemoveIssueWatchersByRepoID(ctx, teamUser.UID, repo.ID); err != nil { - return err - } - } - - return nil -} - -// RemoveRepository removes repository from team of organization. -// If the team shall include all repositories the request is ignored. -func RemoveRepository(t *organization.Team, repoID int64) error { - if !HasRepository(t, repoID) { - return nil - } - - if t.IncludesAllRepositories { - return nil - } - - repo, err := repo_model.GetRepositoryByID(db.DefaultContext, repoID) - if err != nil { - return err - } - - ctx, committer, err := db.TxContext(db.DefaultContext) - if err != nil { - return err - } - defer committer.Close() - - if err = removeRepository(ctx, t, repo, true); err != nil { - return err - } - - return committer.Commit() -} - // NewTeam creates a record of new team. // It's caller's responsibility to assign organization ID. func NewTeam(t *organization.Team) (err error) { diff --git a/models/org_team_test.go b/models/org_team_test.go index 446084c815..4978f8ef99 100644 --- a/models/org_team_test.go +++ b/models/org_team_test.go @@ -51,36 +51,6 @@ func TestTeam_RemoveMember(t *testing.T) { assert.True(t, organization.IsErrLastOrgOwner(err)) } -func TestTeam_HasRepository(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - test := func(teamID, repoID int64, expected bool) { - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.Equal(t, expected, HasRepository(team, repoID)) - } - test(1, 1, false) - test(1, 3, true) - test(1, 5, true) - test(1, unittest.NonexistentID, false) - - test(2, 3, true) - test(2, 5, false) -} - -func TestTeam_RemoveRepository(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - testSuccess := func(teamID, repoID int64) { - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) - assert.NoError(t, RemoveRepository(team, repoID)) - unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID}) - unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID}) - } - testSuccess(2, 3) - testSuccess(2, 5) - testSuccess(1, unittest.NonexistentID) -} - func TestIsUsableTeamName(t *testing.T) { assert.NoError(t, organization.IsUsableTeamName("usable")) assert.True(t, db.IsErrNameReserved(organization.IsUsableTeamName("new"))) diff --git a/models/organization/main_test.go b/models/organization/main_test.go index 7ccf8c8efd..bc5bde2565 100644 --- a/models/organization/main_test.go +++ b/models/organization/main_test.go @@ -10,6 +10,8 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/organization" _ "code.gitea.io/gitea/models/repo" _ "code.gitea.io/gitea/models/user" diff --git a/models/packages/package_test.go b/models/packages/package_test.go index 735688a731..525a9f08d6 100644 --- a/models/packages/package_test.go +++ b/models/packages/package_test.go @@ -13,6 +13,8 @@ import ( user_model "code.gitea.io/gitea/models/user" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" "github.com/stretchr/testify/assert" ) diff --git a/models/perm/access/main_test.go b/models/perm/access/main_test.go index 837a9db437..8102cae496 100644 --- a/models/perm/access/main_test.go +++ b/models/perm/access/main_test.go @@ -10,6 +10,8 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/repo" _ "code.gitea.io/gitea/models/user" ) diff --git a/models/repo.go b/models/repo.go index 74a88d4c48..de0f7ee345 100644 --- a/models/repo.go +++ b/models/repo.go @@ -11,28 +11,15 @@ import ( _ "image/jpeg" // Needed for jpeg support - actions_model "code.gitea.io/gitea/models/actions" - activities_model "code.gitea.io/gitea/models/activities" - admin_model "code.gitea.io/gitea/models/admin" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" - git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" - "code.gitea.io/gitea/models/organization" access_model "code.gitea.io/gitea/models/perm/access" - project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" - secret_model "code.gitea.io/gitea/models/secret" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/models/webhook" - actions_module "code.gitea.io/gitea/modules/actions" - "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/storage" - - "xorm.io/builder" ) // Init initialize model @@ -43,319 +30,6 @@ func Init(ctx context.Context) error { return system_model.Init(ctx) } -// DeleteRepository deletes a repository for a user or organization. -// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) -func DeleteRepository(doer *user_model.User, uid, repoID int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) - if err != nil { - return err - } - defer committer.Close() - sess := db.GetEngine(ctx) - - // Query the action tasks of this repo, they will be needed after they have been deleted to remove the logs - tasks, err := actions_model.FindTasks(ctx, actions_model.FindTaskOptions{RepoID: repoID}) - if err != nil { - return fmt.Errorf("find actions tasks of repo %v: %w", repoID, err) - } - - // Query the artifacts of this repo, they will be needed after they have been deleted to remove artifacts files in ObjectStorage - artifacts, err := actions_model.ListArtifactsByRepoID(ctx, repoID) - if err != nil { - return fmt.Errorf("list actions artifacts of repo %v: %w", repoID, err) - } - - // In case is a organization. - org, err := user_model.GetUserByID(ctx, uid) - if err != nil { - return err - } - - repo := &repo_model.Repository{OwnerID: uid} - has, err := sess.ID(repoID).Get(repo) - if err != nil { - return err - } else if !has { - return repo_model.ErrRepoNotExist{ - ID: repoID, - UID: uid, - OwnerName: "", - Name: "", - } - } - - // Delete Deploy Keys - deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID}) - if err != nil { - return fmt.Errorf("listDeployKeys: %w", err) - } - needRewriteKeysFile := len(deployKeys) > 0 - for _, dKey := range deployKeys { - if err := DeleteDeployKey(ctx, doer, dKey.ID); err != nil { - return fmt.Errorf("deleteDeployKeys: %w", err) - } - } - - if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil { - return err - } else if cnt != 1 { - return repo_model.ErrRepoNotExist{ - ID: repoID, - UID: uid, - OwnerName: "", - Name: "", - } - } - - if org.IsOrganization() { - teams, err := organization.FindOrgTeams(ctx, org.ID) - if err != nil { - return err - } - for _, t := range teams { - if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) { - continue - } else if err = removeRepository(ctx, t, repo, false); err != nil { - return err - } - } - } - - attachments := make([]*repo_model.Attachment, 0, 20) - if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id"). - Where("`release`.repo_id = ?", repoID). - Find(&attachments); err != nil { - return err - } - releaseAttachments := make([]string, 0, len(attachments)) - for i := 0; i < len(attachments); i++ { - releaseAttachments = append(releaseAttachments, attachments[i].RelativePath()) - } - - if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil { - return err - } - - if _, err := db.GetEngine(ctx).In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"webhook.repo_id": repo.ID})). - Delete(&webhook.HookTask{}); err != nil { - return err - } - - if err := db.DeleteBeans(ctx, - &access_model.Access{RepoID: repo.ID}, - &activities_model.Action{RepoID: repo.ID}, - &repo_model.Collaboration{RepoID: repoID}, - &issues_model.Comment{RefRepoID: repoID}, - &git_model.CommitStatus{RepoID: repoID}, - &git_model.Branch{RepoID: repoID}, - &git_model.LFSLock{RepoID: repoID}, - &repo_model.LanguageStat{RepoID: repoID}, - &issues_model.Milestone{RepoID: repoID}, - &repo_model.Mirror{RepoID: repoID}, - &activities_model.Notification{RepoID: repoID}, - &git_model.ProtectedBranch{RepoID: repoID}, - &git_model.ProtectedTag{RepoID: repoID}, - &repo_model.PushMirror{RepoID: repoID}, - &repo_model.Release{RepoID: repoID}, - &repo_model.RepoIndexerStatus{RepoID: repoID}, - &repo_model.Redirect{RedirectRepoID: repoID}, - &repo_model.RepoUnit{RepoID: repoID}, - &repo_model.Star{RepoID: repoID}, - &admin_model.Task{RepoID: repoID}, - &repo_model.Watch{RepoID: repoID}, - &webhook.Webhook{RepoID: repoID}, - &secret_model.Secret{RepoID: repoID}, - &actions_model.ActionTaskStep{RepoID: repoID}, - &actions_model.ActionTask{RepoID: repoID}, - &actions_model.ActionRunJob{RepoID: repoID}, - &actions_model.ActionRun{RepoID: repoID}, - &actions_model.ActionRunner{RepoID: repoID}, - &actions_model.ActionScheduleSpec{RepoID: repoID}, - &actions_model.ActionSchedule{RepoID: repoID}, - &actions_model.ActionArtifact{RepoID: repoID}, - ); err != nil { - return fmt.Errorf("deleteBeans: %w", err) - } - - // Delete Labels and related objects - if err := issues_model.DeleteLabelsByRepoID(ctx, repoID); err != nil { - return err - } - - // Delete Pulls and related objects - if err := issues_model.DeletePullsByBaseRepoID(ctx, repoID); err != nil { - return err - } - - // Delete Issues and related objects - var attachmentPaths []string - if attachmentPaths, err = issues_model.DeleteIssuesByRepoID(ctx, repoID); err != nil { - return err - } - - // Delete issue index - if err := db.DeleteResourceIndex(ctx, "issue_index", repoID); err != nil { - return err - } - - if repo.IsFork { - if _, err := db.Exec(ctx, "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { - return fmt.Errorf("decrease fork count: %w", err) - } - } - - if _, err := db.Exec(ctx, "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { - return err - } - - if len(repo.Topics) > 0 { - if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil { - return err - } - } - - if err := project_model.DeleteProjectByRepoID(ctx, repoID); err != nil { - return fmt.Errorf("unable to delete projects for repo[%d]: %w", repoID, err) - } - - // Remove LFS objects - var lfsObjects []*git_model.LFSMetaObject - if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { - return err - } - - lfsPaths := make([]string, 0, len(lfsObjects)) - for _, v := range lfsObjects { - count, err := db.CountByBean(ctx, &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}}) - if err != nil { - return err - } - if count > 1 { - continue - } - - lfsPaths = append(lfsPaths, v.RelativePath()) - } - - if _, err := db.DeleteByBean(ctx, &git_model.LFSMetaObject{RepositoryID: repoID}); err != nil { - return err - } - - // Remove archives - var archives []*repo_model.RepoArchiver - if err = sess.Where("repo_id=?", repoID).Find(&archives); err != nil { - return err - } - - archivePaths := make([]string, 0, len(archives)) - for _, v := range archives { - archivePaths = append(archivePaths, v.RelativePath()) - } - - if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil { - return err - } - - if repo.NumForks > 0 { - if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { - log.Error("reset 'fork_id' and 'is_fork': %v", err) - } - } - - // Get all attachments with both issue_id and release_id are zero - var newAttachments []*repo_model.Attachment - if err := sess.Where(builder.Eq{ - "repo_id": repo.ID, - "issue_id": 0, - "release_id": 0, - }).Find(&newAttachments); err != nil { - return err - } - - newAttachmentPaths := make([]string, 0, len(newAttachments)) - for _, attach := range newAttachments { - newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath()) - } - - if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil { - return err - } - - if err = committer.Commit(); err != nil { - return err - } - - committer.Close() - - if needRewriteKeysFile { - if err := asymkey_model.RewriteAllPublicKeys(); err != nil { - log.Error("RewriteAllPublicKeys failed: %v", err) - } - } - - // We should always delete the files after the database transaction succeed. If - // we delete the file but the database rollback, the repository will be broken. - - // Remove repository files. - repoPath := repo.RepoPath() - system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath) - - // Remove wiki files - if repo.HasWiki() { - system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath()) - } - - // Remove archives - for _, archive := range archivePaths { - system_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive) - } - - // Remove lfs objects - for _, lfsObj := range lfsPaths { - system_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj) - } - - // Remove issue attachment files. - for _, attachment := range attachmentPaths { - system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment) - } - - // Remove release attachment files. - for _, releaseAttachment := range releaseAttachments { - system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment) - } - - // Remove attachment with no issue_id and release_id. - for _, newAttachment := range newAttachmentPaths { - system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment) - } - - if len(repo.Avatar) > 0 { - if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil { - return fmt.Errorf("Failed to remove %s: %w", repo.Avatar, err) - } - } - - // Finally, delete action logs after the actions have already been deleted to avoid new log files - for _, task := range tasks { - err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename) - if err != nil { - log.Error("remove log file %q: %v", task.LogFilename, err) - // go on - } - } - - // delete actions artifacts in ObjectStorage after the repo have already been deleted - for _, art := range artifacts { - if err := storage.ActionsArtifacts.Delete(art.StoragePath); err != nil { - log.Error("remove artifact file %q: %v", art.StoragePath, err) - // go on - } - } - - return nil -} - type repoChecker struct { querySQL func(ctx context.Context) ([]map[string][]byte, error) correctSQL func(ctx context.Context, id int64) error diff --git a/models/repo/main_test.go b/models/repo/main_test.go index bb9be54b9c..ff97c7ac9e 100644 --- a/models/repo/main_test.go +++ b/models/repo/main_test.go @@ -9,7 +9,9 @@ import ( "code.gitea.io/gitea/models/unittest" - _ "code.gitea.io/gitea/models" // register table model + _ "code.gitea.io/gitea/models" // register table model + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/perm/access" // register table model _ "code.gitea.io/gitea/models/repo" // register table model _ "code.gitea.io/gitea/models/user" // register table model diff --git a/models/system/main_test.go b/models/system/main_test.go index 94e2906447..e074abc155 100644 --- a/models/system/main_test.go +++ b/models/system/main_test.go @@ -9,7 +9,9 @@ import ( "code.gitea.io/gitea/models/unittest" - _ "code.gitea.io/gitea/models" // register models + _ "code.gitea.io/gitea/models" // register models + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/system" // register models of system ) diff --git a/models/user/main_test.go b/models/user/main_test.go index 0d76aacd5f..8833cc7386 100644 --- a/models/user/main_test.go +++ b/models/user/main_test.go @@ -10,6 +10,8 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" _ "code.gitea.io/gitea/models/user" ) diff --git a/modules/activitypub/client_test.go b/modules/activitypub/client_test.go index 0ab512c5ba..83000b96d5 100644 --- a/modules/activitypub/client_test.go +++ b/modules/activitypub/client_test.go @@ -15,8 +15,6 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" - _ "code.gitea.io/gitea/models" // https://discourse.gitea.io/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4 - "github.com/stretchr/testify/assert" ) diff --git a/modules/activitypub/main_test.go b/modules/activitypub/main_test.go index 15399ca380..dcd57bb59e 100644 --- a/modules/activitypub/main_test.go +++ b/modules/activitypub/main_test.go @@ -8,6 +8,10 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" ) func TestMain(m *testing.M) { diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index 55616a0361..2a650646bd 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -16,6 +16,8 @@ import ( "code.gitea.io/gitea/modules/indexer/code/internal" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" "github.com/stretchr/testify/assert" ) diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index d6812f714e..a4e1c899fc 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -15,6 +15,8 @@ import ( "code.gitea.io/gitea/modules/setting" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" "github.com/stretchr/testify/assert" ) diff --git a/modules/indexer/stats/indexer_test.go b/modules/indexer/stats/indexer_test.go index 2d9844f8c9..c031515434 100644 --- a/modules/indexer/stats/indexer_test.go +++ b/modules/indexer/stats/indexer_test.go @@ -16,6 +16,8 @@ import ( "code.gitea.io/gitea/modules/setting" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" "github.com/stretchr/testify/assert" ) diff --git a/modules/repository/main_test.go b/modules/repository/main_test.go index 007790f2a9..abaae69866 100644 --- a/modules/repository/main_test.go +++ b/modules/repository/main_test.go @@ -8,6 +8,8 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models/actions" ) func TestMain(m *testing.M) { diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 0e11acc901..4b52fb8987 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -23,6 +23,7 @@ import ( "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/convert" org_service "code.gitea.io/gitea/services/org" + repo_service "code.gitea.io/gitea/services/repository" ) // ListTeams list all the teams of an organization @@ -726,7 +727,7 @@ func RemoveTeamRepository(ctx *context.APIContext) { ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository") return } - if err := models.RemoveRepository(ctx.Org.Team, repo.ID); err != nil { + if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, repo.ID); err != nil { ctx.Error(http.StatusInternalServerError, "RemoveRepository", err) return } diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index 41374831de..4ddd452372 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -170,7 +170,7 @@ func Migrate(ctx *context.APIContext) { opts.Releases = false } - repo, err := repo_service.CreateRepositoryDirectly(ctx.Doer, repoOwner, repo_service.CreateRepoOptions{ + repo, err := repo_service.CreateRepositoryDirectly(ctx, ctx.Doer, repoOwner, repo_service.CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, OriginalURL: form.CloneAddr, @@ -200,7 +200,7 @@ func Migrate(ctx *context.APIContext) { } if repo != nil { - if errDelete := models.DeleteRepository(ctx.Doer, repoOwner.ID, repo.ID); errDelete != nil { + if errDelete := repo_service.DeleteRepositoryDirectly(ctx, ctx.Doer, repoOwner.ID, repo.ID); errDelete != nil { log.Error("DeleteRepository: %v", errDelete) } } diff --git a/routers/api/v1/repo/teams.go b/routers/api/v1/repo/teams.go index 01292f18d8..d1be619cac 100644 --- a/routers/api/v1/repo/teams.go +++ b/routers/api/v1/repo/teams.go @@ -7,11 +7,11 @@ import ( "fmt" "net/http" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/services/convert" org_service "code.gitea.io/gitea/services/org" + repo_service "code.gitea.io/gitea/services/repository" ) // ListTeams list a repository's teams @@ -97,7 +97,7 @@ func IsTeam(ctx *context.APIContext) { return } - if models.HasRepository(team, ctx.Repo.Repository.ID) { + if repo_service.HasRepository(team, ctx.Repo.Repository.ID) { apiTeam, err := convert.ToTeam(ctx, team) if err != nil { ctx.InternalServerError(err) @@ -192,7 +192,7 @@ func changeRepoTeam(ctx *context.APIContext, add bool) { return } - repoHasTeam := models.HasRepository(team, ctx.Repo.Repository.ID) + repoHasTeam := repo_service.HasRepository(team, ctx.Repo.Repository.ID) var err error if add { if repoHasTeam { @@ -205,7 +205,7 @@ func changeRepoTeam(ctx *context.APIContext, add bool) { ctx.Error(http.StatusUnprocessableEntity, "notAdded", fmt.Errorf("team '%s' was not added to repo", team.Name)) return } - err = models.RemoveRepository(team, ctx.Repo.Repository.ID) + err = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID) } if err != nil { ctx.InternalServerError(err) diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 38bc8b4e3a..2fd2a681af 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -29,6 +29,7 @@ import ( "code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/forms" org_service "code.gitea.io/gitea/services/org" + repo_service "code.gitea.io/gitea/services/repository" ) const ( @@ -248,7 +249,7 @@ func TeamsRepoAction(ctx *context.Context) { } err = org_service.TeamAddRepository(ctx.Org.Team, repo) case "remove": - err = models.RemoveRepository(ctx.Org.Team, ctx.FormInt64("repoid")) + err = repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, ctx.FormInt64("repoid")) case "addall": err = models.AddAllRepositories(ctx.Org.Team) case "removeall": diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index b708422cbd..84e5fd30ca 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -21,6 +21,7 @@ import ( "code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/services/mailer" org_service "code.gitea.io/gitea/services/org" + repo_service "code.gitea.io/gitea/services/repository" ) // Collaboration render a repository's collaboration page @@ -196,7 +197,7 @@ func DeleteTeam(ctx *context.Context) { return } - if err = models.RemoveRepository(team, ctx.Repo.Repository.ID); err != nil { + if err = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID); err != nil { ctx.ServerError("team.RemoveRepositorys", err) return } diff --git a/routers/web/repo/setting/settings_test.go b/routers/web/repo/setting/settings_test.go index 51d127bfc2..55f292f143 100644 --- a/routers/web/repo/setting/settings_test.go +++ b/routers/web/repo/setting/settings_test.go @@ -7,7 +7,6 @@ import ( "net/http" "testing" - "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" @@ -19,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" + repo_service "code.gitea.io/gitea/services/repository" "github.com/stretchr/testify/assert" ) @@ -248,7 +248,7 @@ func TestAddTeamPost(t *testing.T) { AddTeamPost(ctx) - assert.True(t, models.HasRepository(team, re.ID)) + assert.True(t, repo_service.HasRepository(team, re.ID)) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assert.Empty(t, ctx.Flash.ErrorMsg) } @@ -288,7 +288,7 @@ func TestAddTeamPost_NotAllowed(t *testing.T) { AddTeamPost(ctx) - assert.False(t, models.HasRepository(team, re.ID)) + assert.False(t, repo_service.HasRepository(team, re.ID)) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -329,7 +329,7 @@ func TestAddTeamPost_AddTeamTwice(t *testing.T) { AddTeamPost(ctx) AddTeamPost(ctx) - assert.True(t, models.HasRepository(team, re.ID)) + assert.True(t, repo_service.HasRepository(team, re.ID)) assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -402,5 +402,5 @@ func TestDeleteTeam(t *testing.T) { DeleteTeam(ctx) - assert.False(t, models.HasRepository(team, re.ID)) + assert.False(t, repo_service.HasRepository(team, re.ID)) } diff --git a/services/asymkey/main_test.go b/services/asymkey/main_test.go index 3fa88340fd..e7a03861b9 100644 --- a/services/asymkey/main_test.go +++ b/services/asymkey/main_test.go @@ -8,6 +8,9 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" ) func TestMain(m *testing.M) { diff --git a/services/attachment/attachment_test.go b/services/attachment/attachment_test.go index 1b9af34427..35fcef7445 100644 --- a/services/attachment/attachment_test.go +++ b/services/attachment/attachment_test.go @@ -13,6 +13,8 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + _ "code.gitea.io/gitea/models/actions" + "github.com/stretchr/testify/assert" ) diff --git a/services/convert/main_test.go b/services/convert/main_test.go index 4c8e57bf79..c2298dcb74 100644 --- a/services/convert/main_test.go +++ b/services/convert/main_test.go @@ -8,6 +8,8 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models/actions" ) func TestMain(m *testing.M) { diff --git a/services/feed/action_test.go b/services/feed/action_test.go index 0d725d532d..fd84bb675b 100644 --- a/services/feed/action_test.go +++ b/services/feed/action_test.go @@ -14,6 +14,8 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + _ "code.gitea.io/gitea/models/actions" + "github.com/stretchr/testify/assert" ) diff --git a/services/gitdiff/main_test.go b/services/gitdiff/main_test.go index a5ac274b8f..7f4243576c 100644 --- a/services/gitdiff/main_test.go +++ b/services/gitdiff/main_test.go @@ -10,6 +10,8 @@ import ( "code.gitea.io/gitea/models/unittest" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/activities" ) func TestMain(m *testing.M) { diff --git a/services/issue/main_test.go b/services/issue/main_test.go index 0f2427122f..6bce694cca 100644 --- a/services/issue/main_test.go +++ b/services/issue/main_test.go @@ -8,6 +8,8 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models/actions" ) func TestMain(m *testing.M) { diff --git a/services/mailer/main_test.go b/services/mailer/main_test.go index 16a6a26545..e906f4cb6e 100644 --- a/services/mailer/main_test.go +++ b/services/mailer/main_test.go @@ -8,6 +8,8 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models/actions" ) func TestMain(m *testing.M) { diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index a4a3af82e7..3f055707ae 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -100,7 +100,7 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate var r *repo_model.Repository if opts.MigrateToRepoID <= 0 { - r, err = repo_service.CreateRepositoryDirectly(g.doer, owner, repo_service.CreateRepoOptions{ + r, err = repo_service.CreateRepositoryDirectly(g.ctx, g.doer, owner, repo_service.CreateRepoOptions{ Name: g.repoName, Description: repo.Description, OriginalURL: repo.OriginalURL, diff --git a/services/org/repo.go b/services/org/repo.go index 179249c7a8..0edbf2d464 100644 --- a/services/org/repo.go +++ b/services/org/repo.go @@ -17,7 +17,7 @@ import ( func TeamAddRepository(t *organization.Team, repo *repo_model.Repository) (err error) { if repo.OwnerID != t.OrgID { return errors.New("repository does not belong to organization") - } else if models.HasRepository(t, repo.ID) { + } else if organization.HasTeamRepo(db.DefaultContext, t.OrgID, t.ID, repo.ID) { return nil } diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index 572f5e1f5b..0561f168e1 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -206,7 +206,7 @@ func getOrCreateIndexRepository(ctx context.Context, doer, owner *user_model.Use repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, owner.Name, IndexRepositoryName) if err != nil { if errors.Is(err, util.ErrNotExist) { - repo, err = repo_service.CreateRepositoryDirectly(doer, owner, repo_service.CreateRepoOptions{ + repo, err = repo_service.CreateRepositoryDirectly(ctx, doer, owner, repo_service.CreateRepoOptions{ Name: IndexRepositoryName, }) if err != nil { diff --git a/services/pull/main_test.go b/services/pull/main_test.go index 2014b19275..f5297354d6 100644 --- a/services/pull/main_test.go +++ b/services/pull/main_test.go @@ -9,6 +9,8 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models/actions" ) func TestMain(m *testing.M) { diff --git a/services/release/release_test.go b/services/release/release_test.go index 805269413d..0732dbc54d 100644 --- a/services/release/release_test.go +++ b/services/release/release_test.go @@ -16,6 +16,8 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/services/attachment" + _ "code.gitea.io/gitea/models/actions" + "github.com/stretchr/testify/assert" ) diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go index aa4140d3ec..a1bc355d4e 100644 --- a/services/repository/archiver/archiver_test.go +++ b/services/repository/archiver/archiver_test.go @@ -12,6 +12,8 @@ import ( "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/contexttest" + _ "code.gitea.io/gitea/models/actions" + "github.com/stretchr/testify/assert" ) diff --git a/services/repository/check.go b/services/repository/check.go index 84fdb7159b..6ad644561e 100644 --- a/services/repository/check.go +++ b/services/repository/check.go @@ -9,7 +9,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" system_model "code.gitea.io/gitea/models/system" @@ -165,7 +164,7 @@ func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error default: } log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID) - if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { + if err := DeleteRepositoryDirectly(ctx, doer, repo.OwnerID, repo.ID); err != nil { log.Error("Failed to DeleteRepository %-v: Error: %v", repo, err) if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil { log.Error("CreateRepositoryNotice: %v", err) diff --git a/services/repository/create.go b/services/repository/create.go index a5d521e353..09956b74d4 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -12,7 +12,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -199,7 +198,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re } // CreateRepositoryDirectly creates a repository for the user/organization. -func CreateRepositoryDirectly(doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { +func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { if !doer.IsAdmin && !u.CanCreateRepo() { return nil, repo_model.ErrReachLimitOfRepo{ Limit: u.MaxRepoCreation, @@ -239,7 +238,7 @@ func CreateRepositoryDirectly(doer, u *user_model.User, opts CreateRepoOptions) var rollbackRepo *repo_model.Repository - if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := db.WithTx(ctx, func(ctx context.Context) error { if err := repo_module.CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil { return err } @@ -303,7 +302,7 @@ func CreateRepositoryDirectly(doer, u *user_model.User, opts CreateRepoOptions) return nil }); err != nil { if rollbackRepo != nil { - if errDelete := models.DeleteRepository(doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil { + if errDelete := DeleteRepositoryDirectly(ctx, doer, rollbackRepo.OwnerID, rollbackRepo.ID); errDelete != nil { log.Error("Rollback deleteRepository: %v", errDelete) } } diff --git a/services/repository/create_test.go b/services/repository/create_test.go index ec3d62ce07..78be93bf12 100644 --- a/services/repository/create_test.go +++ b/services/repository/create_test.go @@ -28,7 +28,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.Len(t, team.Repos, len(repoIds), "%s: repo count", team.Name) for i, rid := range repoIds { if rid > 0 { - assert.True(t, models.HasRepository(team, rid), "%s: HasRepository(%d) %d", rid, i) + assert.True(t, HasRepository(team, rid), "%s: HasRepository(%d) %d", rid, i) } } } @@ -54,7 +54,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { // Create repos. repoIds := make([]int64, 0) for i := 0; i < 3; i++ { - r, err := CreateRepositoryDirectly(user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}) + r, err := CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}) assert.NoError(t, err, "CreateRepository %d", i) if r != nil { repoIds = append(repoIds, r.ID) @@ -116,7 +116,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { } // Create repo and check teams repositories. - r, err := CreateRepositoryDirectly(user, org.AsUser(), CreateRepoOptions{Name: "repo-last"}) + r, err := CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), CreateRepoOptions{Name: "repo-last"}) assert.NoError(t, err, "CreateRepository last") if r != nil { repoIds = append(repoIds, r.ID) @@ -129,7 +129,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { } // Remove repo and check teams repositories. - assert.NoError(t, models.DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository") + assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, org.ID, repoIds[0]), "DeleteRepository") teamRepos[0] = repoIds[1:] teamRepos[1] = repoIds[1:] teamRepos[3] = repoIds[1:3] @@ -141,7 +141,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { // Wipe created items. for i, rid := range repoIds { if i > 0 { // first repo already deleted. - assert.NoError(t, models.DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i) + assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, org.ID, rid), "DeleteRepository %d", i) } } assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") diff --git a/services/repository/delete.go b/services/repository/delete.go new file mode 100644 index 0000000000..8e28c9b255 --- /dev/null +++ b/services/repository/delete.go @@ -0,0 +1,424 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models" + actions_model "code.gitea.io/gitea/models/actions" + activities_model "code.gitea.io/gitea/models/activities" + admin_model "code.gitea.io/gitea/models/admin" + asymkey_model "code.gitea.io/gitea/models/asymkey" + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/organization" + access_model "code.gitea.io/gitea/models/perm/access" + project_model "code.gitea.io/gitea/models/project" + repo_model "code.gitea.io/gitea/models/repo" + secret_model "code.gitea.io/gitea/models/secret" + system_model "code.gitea.io/gitea/models/system" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/models/webhook" + actions_module "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/lfs" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/storage" + + "xorm.io/builder" +) + +// DeleteRepository deletes a repository for a user or organization. +// make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) +func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, uid, repoID int64) error { + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + sess := db.GetEngine(ctx) + + // Query the action tasks of this repo, they will be needed after they have been deleted to remove the logs + tasks, err := actions_model.FindTasks(ctx, actions_model.FindTaskOptions{RepoID: repoID}) + if err != nil { + return fmt.Errorf("find actions tasks of repo %v: %w", repoID, err) + } + + // Query the artifacts of this repo, they will be needed after they have been deleted to remove artifacts files in ObjectStorage + artifacts, err := actions_model.ListArtifactsByRepoID(ctx, repoID) + if err != nil { + return fmt.Errorf("list actions artifacts of repo %v: %w", repoID, err) + } + + // In case is a organization. + org, err := user_model.GetUserByID(ctx, uid) + if err != nil { + return err + } + + repo := &repo_model.Repository{OwnerID: uid} + has, err := sess.ID(repoID).Get(repo) + if err != nil { + return err + } else if !has { + return repo_model.ErrRepoNotExist{ + ID: repoID, + UID: uid, + OwnerName: "", + Name: "", + } + } + + // Delete Deploy Keys + deployKeys, err := asymkey_model.ListDeployKeys(ctx, &asymkey_model.ListDeployKeysOptions{RepoID: repoID}) + if err != nil { + return fmt.Errorf("listDeployKeys: %w", err) + } + needRewriteKeysFile := len(deployKeys) > 0 + for _, dKey := range deployKeys { + if err := models.DeleteDeployKey(ctx, doer, dKey.ID); err != nil { + return fmt.Errorf("deleteDeployKeys: %w", err) + } + } + + if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil { + return err + } else if cnt != 1 { + return repo_model.ErrRepoNotExist{ + ID: repoID, + UID: uid, + OwnerName: "", + Name: "", + } + } + + if org.IsOrganization() { + teams, err := organization.FindOrgTeams(ctx, org.ID) + if err != nil { + return err + } + for _, t := range teams { + if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) { + continue + } else if err = removeRepositoryFromTeam(ctx, t, repo, false); err != nil { + return err + } + } + } + + attachments := make([]*repo_model.Attachment, 0, 20) + if err = sess.Join("INNER", "`release`", "`release`.id = `attachment`.release_id"). + Where("`release`.repo_id = ?", repoID). + Find(&attachments); err != nil { + return err + } + releaseAttachments := make([]string, 0, len(attachments)) + for i := 0; i < len(attachments); i++ { + releaseAttachments = append(releaseAttachments, attachments[i].RelativePath()) + } + + if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars=num_stars-1 WHERE id IN (SELECT `uid` FROM `star` WHERE repo_id = ?)", repo.ID); err != nil { + return err + } + + if _, err := db.GetEngine(ctx).In("hook_id", builder.Select("id").From("webhook").Where(builder.Eq{"webhook.repo_id": repo.ID})). + Delete(&webhook.HookTask{}); err != nil { + return err + } + + if err := db.DeleteBeans(ctx, + &access_model.Access{RepoID: repo.ID}, + &activities_model.Action{RepoID: repo.ID}, + &repo_model.Collaboration{RepoID: repoID}, + &issues_model.Comment{RefRepoID: repoID}, + &git_model.CommitStatus{RepoID: repoID}, + &git_model.Branch{RepoID: repoID}, + &git_model.LFSLock{RepoID: repoID}, + &repo_model.LanguageStat{RepoID: repoID}, + &issues_model.Milestone{RepoID: repoID}, + &repo_model.Mirror{RepoID: repoID}, + &activities_model.Notification{RepoID: repoID}, + &git_model.ProtectedBranch{RepoID: repoID}, + &git_model.ProtectedTag{RepoID: repoID}, + &repo_model.PushMirror{RepoID: repoID}, + &repo_model.Release{RepoID: repoID}, + &repo_model.RepoIndexerStatus{RepoID: repoID}, + &repo_model.Redirect{RedirectRepoID: repoID}, + &repo_model.RepoUnit{RepoID: repoID}, + &repo_model.Star{RepoID: repoID}, + &admin_model.Task{RepoID: repoID}, + &repo_model.Watch{RepoID: repoID}, + &webhook.Webhook{RepoID: repoID}, + &secret_model.Secret{RepoID: repoID}, + &actions_model.ActionTaskStep{RepoID: repoID}, + &actions_model.ActionTask{RepoID: repoID}, + &actions_model.ActionRunJob{RepoID: repoID}, + &actions_model.ActionRun{RepoID: repoID}, + &actions_model.ActionRunner{RepoID: repoID}, + &actions_model.ActionScheduleSpec{RepoID: repoID}, + &actions_model.ActionSchedule{RepoID: repoID}, + &actions_model.ActionArtifact{RepoID: repoID}, + ); err != nil { + return fmt.Errorf("deleteBeans: %w", err) + } + + // Delete Labels and related objects + if err := issues_model.DeleteLabelsByRepoID(ctx, repoID); err != nil { + return err + } + + // Delete Pulls and related objects + if err := issues_model.DeletePullsByBaseRepoID(ctx, repoID); err != nil { + return err + } + + // Delete Issues and related objects + var attachmentPaths []string + if attachmentPaths, err = issues_model.DeleteIssuesByRepoID(ctx, repoID); err != nil { + return err + } + + // Delete issue index + if err := db.DeleteResourceIndex(ctx, "issue_index", repoID); err != nil { + return err + } + + if repo.IsFork { + if _, err := db.Exec(ctx, "UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkID); err != nil { + return fmt.Errorf("decrease fork count: %w", err) + } + } + + if _, err := db.Exec(ctx, "UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil { + return err + } + + if len(repo.Topics) > 0 { + if err := repo_model.RemoveTopicsFromRepo(ctx, repo.ID); err != nil { + return err + } + } + + if err := project_model.DeleteProjectByRepoID(ctx, repoID); err != nil { + return fmt.Errorf("unable to delete projects for repo[%d]: %w", repoID, err) + } + + // Remove LFS objects + var lfsObjects []*git_model.LFSMetaObject + if err = sess.Where("repository_id=?", repoID).Find(&lfsObjects); err != nil { + return err + } + + lfsPaths := make([]string, 0, len(lfsObjects)) + for _, v := range lfsObjects { + count, err := db.CountByBean(ctx, &git_model.LFSMetaObject{Pointer: lfs.Pointer{Oid: v.Oid}}) + if err != nil { + return err + } + if count > 1 { + continue + } + + lfsPaths = append(lfsPaths, v.RelativePath()) + } + + if _, err := db.DeleteByBean(ctx, &git_model.LFSMetaObject{RepositoryID: repoID}); err != nil { + return err + } + + // Remove archives + var archives []*repo_model.RepoArchiver + if err = sess.Where("repo_id=?", repoID).Find(&archives); err != nil { + return err + } + + archivePaths := make([]string, 0, len(archives)) + for _, v := range archives { + archivePaths = append(archivePaths, v.RelativePath()) + } + + if _, err := db.DeleteByBean(ctx, &repo_model.RepoArchiver{RepoID: repoID}); err != nil { + return err + } + + if repo.NumForks > 0 { + if _, err = sess.Exec("UPDATE `repository` SET fork_id=0,is_fork=? WHERE fork_id=?", false, repo.ID); err != nil { + log.Error("reset 'fork_id' and 'is_fork': %v", err) + } + } + + // Get all attachments with both issue_id and release_id are zero + var newAttachments []*repo_model.Attachment + if err := sess.Where(builder.Eq{ + "repo_id": repo.ID, + "issue_id": 0, + "release_id": 0, + }).Find(&newAttachments); err != nil { + return err + } + + newAttachmentPaths := make([]string, 0, len(newAttachments)) + for _, attach := range newAttachments { + newAttachmentPaths = append(newAttachmentPaths, attach.RelativePath()) + } + + if _, err := sess.Where("repo_id=?", repo.ID).Delete(new(repo_model.Attachment)); err != nil { + return err + } + + if err = committer.Commit(); err != nil { + return err + } + + committer.Close() + + if needRewriteKeysFile { + if err := asymkey_model.RewriteAllPublicKeys(); err != nil { + log.Error("RewriteAllPublicKeys failed: %v", err) + } + } + + // We should always delete the files after the database transaction succeed. If + // we delete the file but the database rollback, the repository will be broken. + + // Remove repository files. + repoPath := repo.RepoPath() + system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath) + + // Remove wiki files + if repo.HasWiki() { + system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath()) + } + + // Remove archives + for _, archive := range archivePaths { + system_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive) + } + + // Remove lfs objects + for _, lfsObj := range lfsPaths { + system_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj) + } + + // Remove issue attachment files. + for _, attachment := range attachmentPaths { + system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment) + } + + // Remove release attachment files. + for _, releaseAttachment := range releaseAttachments { + system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment) + } + + // Remove attachment with no issue_id and release_id. + for _, newAttachment := range newAttachmentPaths { + system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment) + } + + if len(repo.Avatar) > 0 { + if err := storage.RepoAvatars.Delete(repo.CustomAvatarRelativePath()); err != nil { + return fmt.Errorf("Failed to remove %s: %w", repo.Avatar, err) + } + } + + // Finally, delete action logs after the actions have already been deleted to avoid new log files + for _, task := range tasks { + err := actions_module.RemoveLogs(ctx, task.LogInStorage, task.LogFilename) + if err != nil { + log.Error("remove log file %q: %v", task.LogFilename, err) + // go on + } + } + + // delete actions artifacts in ObjectStorage after the repo have already been deleted + for _, art := range artifacts { + if err := storage.ActionsArtifacts.Delete(art.StoragePath); err != nil { + log.Error("remove artifact file %q: %v", art.StoragePath, err) + // go on + } + } + + return nil +} + +// removeRepositoryFromTeam removes a repository from a team and recalculates access +// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted) +func removeRepositoryFromTeam(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) { + e := db.GetEngine(ctx) + if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil { + return err + } + + t.NumRepos-- + if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil { + return err + } + + // Don't need to recalculate when delete a repository from organization. + if recalculate { + if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil { + return err + } + } + + teamUsers, err := organization.GetTeamUsersByTeamID(ctx, t.ID) + if err != nil { + return fmt.Errorf("getTeamUsersByTeamID: %w", err) + } + for _, teamUser := range teamUsers { + has, err := access_model.HasAccess(ctx, teamUser.UID, repo) + if err != nil { + return err + } else if has { + continue + } + + if err = repo_model.WatchRepo(ctx, teamUser.UID, repo.ID, false); err != nil { + return err + } + + // Remove all IssueWatches a user has subscribed to in the repositories + if err := issues_model.RemoveIssueWatchersByRepoID(ctx, teamUser.UID, repo.ID); err != nil { + return err + } + } + + return nil +} + +// HasRepository returns true if given repository belong to team. +func HasRepository(t *organization.Team, repoID int64) bool { + return organization.HasTeamRepo(db.DefaultContext, t.OrgID, t.ID, repoID) +} + +// RemoveRepositoryFromTeam removes repository from team of organization. +// If the team shall include all repositories the request is ignored. +func RemoveRepositoryFromTeam(ctx context.Context, t *organization.Team, repoID int64) error { + if !HasRepository(t, repoID) { + return nil + } + + if t.IncludesAllRepositories { + return nil + } + + repo, err := repo_model.GetRepositoryByID(ctx, repoID) + if err != nil { + return err + } + + ctx, committer, err := db.TxContext(ctx) + if err != nil { + return err + } + defer committer.Close() + + if err = removeRepositoryFromTeam(ctx, t, repo, true); err != nil { + return err + } + + return committer.Commit() +} diff --git a/services/repository/delete_test.go b/services/repository/delete_test.go new file mode 100644 index 0000000000..2905c01868 --- /dev/null +++ b/services/repository/delete_test.go @@ -0,0 +1,45 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/organization" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + + "github.com/stretchr/testify/assert" +) + +func TestTeam_HasRepository(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + test := func(teamID, repoID int64, expected bool) { + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + assert.Equal(t, expected, HasRepository(team, repoID)) + } + test(1, 1, false) + test(1, 3, true) + test(1, 5, true) + test(1, unittest.NonexistentID, false) + + test(2, 3, true) + test(2, 5, false) +} + +func TestTeam_RemoveRepository(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + testSuccess := func(teamID, repoID int64) { + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + assert.NoError(t, RemoveRepositoryFromTeam(db.DefaultContext, team, repoID)) + unittest.AssertNotExistsBean(t, &organization.TeamRepo{TeamID: teamID, RepoID: repoID}) + unittest.CheckConsistencyFor(t, &organization.Team{ID: teamID}, &repo_model.Repository{ID: repoID}) + } + testSuccess(2, 3) + testSuccess(2, 5) + testSuccess(1, unittest.NonexistentID) +} diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go index 3e4c1e2c70..d591c46839 100644 --- a/services/repository/files/content_test.go +++ b/services/repository/files/content_test.go @@ -13,6 +13,8 @@ import ( "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" + _ "code.gitea.io/gitea/models/actions" + "github.com/stretchr/testify/assert" ) diff --git a/services/repository/repository.go b/services/repository/repository.go index db3035f8c0..60f9568b54 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -7,7 +7,6 @@ import ( "context" "fmt" - "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -41,7 +40,7 @@ type WebSearchResults struct { // CreateRepository creates a repository for the user/organization. func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { - repo, err := CreateRepositoryDirectly(doer, owner, opts) + repo, err := CreateRepositoryDirectly(ctx, doer, owner, opts) if err != nil { // No need to rollback here we should do this in CreateRepository... return nil, err @@ -63,7 +62,7 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod notify_service.DeleteRepository(ctx, doer, repo) } - if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { + if err := DeleteRepositoryDirectly(ctx, doer, repo.OwnerID, repo.ID); err != nil { return err } diff --git a/services/task/task.go b/services/task/task.go index 45bc7b990a..3a40faef90 100644 --- a/services/task/task.go +++ b/services/task/task.go @@ -7,6 +7,7 @@ import ( "fmt" admin_model "code.gitea.io/gitea/models/admin" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/graceful" @@ -100,7 +101,7 @@ func CreateMigrateTask(doer, u *user_model.User, opts base.MigrateOptions) (*adm return nil, err } - repo, err := repo_service.CreateRepositoryDirectly(doer, u, repo_service.CreateRepoOptions{ + repo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, doer, u, repo_service.CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, OriginalURL: opts.OriginalURL, diff --git a/services/user/user.go b/services/user/user.go index bb3dd002ea..72bea0b468 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -26,6 +26,7 @@ import ( "code.gitea.io/gitea/services/agit" "code.gitea.io/gitea/services/packages" container_service "code.gitea.io/gitea/services/packages/container" + repo_service "code.gitea.io/gitea/services/repository" ) // RenameUser renames a user @@ -174,7 +175,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error { break } for _, repo := range repos { - if err := models.DeleteRepository(u, u.ID, repo.ID); err != nil { + if err := repo_service.DeleteRepositoryDirectly(ctx, u, u.ID, repo.ID); err != nil { return fmt.Errorf("unable to delete repository %s for %s[%d]. Error: %w", repo.Name, u.Name, u.ID, err) } } diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go index 0189e17840..cd34c02b5c 100644 --- a/services/webhook/main_test.go +++ b/services/webhook/main_test.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/setting" _ "code.gitea.io/gitea/models" + _ "code.gitea.io/gitea/models/actions" ) func TestMain(m *testing.M) { diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index 85d99806fe..0621456f3e 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -14,6 +14,8 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + _ "code.gitea.io/gitea/models/actions" + "github.com/stretchr/testify/assert" ) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 3933298f23..be4135b050 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -9,7 +9,6 @@ import ( "net/url" "testing" - "code.gitea.io/gitea/models" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" access_model "code.gitea.io/gitea/models/perm/access" @@ -18,6 +17,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -541,7 +541,7 @@ func TestAPIRepoTransfer(t *testing.T) { // cleanup repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiRepo.ID}) - _ = models.DeleteRepository(user, repo.OwnerID, repo.ID) + _ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.OwnerID, repo.ID) } func transfer(t *testing.T) *repo_model.Repository { diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 2f79f5113b..964348d6ad 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -39,7 +39,7 @@ func TestMirrorPull(t *testing.T) { Releases: false, } - mirrorRepo, err := repo_service.CreateRepositoryDirectly(user, user, repo_service.CreateRepoOptions{ + mirrorRepo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ Name: opts.RepoName, Description: opts.Description, IsPrivate: opts.Private, diff --git a/tests/integration/mirror_push_test.go b/tests/integration/mirror_push_test.go index ab79db1861..c6f0c85616 100644 --- a/tests/integration/mirror_push_test.go +++ b/tests/integration/mirror_push_test.go @@ -39,7 +39,7 @@ func testMirrorPush(t *testing.T, u *url.URL) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) srcRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - mirrorRepo, err := repo_service.CreateRepositoryDirectly(user, user, repo_service.CreateRepoOptions{ + mirrorRepo, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ Name: "test-push-mirror", }) assert.NoError(t, err)