mirror of https://github.com/go-gitea/gitea.git
Browse Source
There are still some functions under `models` after last big refactor about `models`. This change will move all team related functions to service layer with no code change.pull/32661/head
26 changed files with 452 additions and 483 deletions
@ -1,27 +0,0 @@ |
|||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package org |
|
||||||
|
|
||||||
import ( |
|
||||||
"context" |
|
||||||
"errors" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/models" |
|
||||||
"code.gitea.io/gitea/models/db" |
|
||||||
"code.gitea.io/gitea/models/organization" |
|
||||||
repo_model "code.gitea.io/gitea/models/repo" |
|
||||||
) |
|
||||||
|
|
||||||
// TeamAddRepository adds new repository to team of organization.
|
|
||||||
func TeamAddRepository(ctx context.Context, 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 organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) { |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error { |
|
||||||
return models.AddRepository(ctx, t, repo) |
|
||||||
}) |
|
||||||
} |
|
||||||
@ -1,7 +1,7 @@ |
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package models |
package org |
||||||
|
|
||||||
import ( |
import ( |
||||||
"testing" |
"testing" |
||||||
@ -1,148 +0,0 @@ |
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repository |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"testing" |
|
||||||
|
|
||||||
"code.gitea.io/gitea/models" |
|
||||||
"code.gitea.io/gitea/models/db" |
|
||||||
"code.gitea.io/gitea/models/organization" |
|
||||||
"code.gitea.io/gitea/models/perm" |
|
||||||
"code.gitea.io/gitea/models/unittest" |
|
||||||
user_model "code.gitea.io/gitea/models/user" |
|
||||||
"code.gitea.io/gitea/modules/structs" |
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert" |
|
||||||
) |
|
||||||
|
|
||||||
func TestIncludesAllRepositoriesTeams(t *testing.T) { |
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase()) |
|
||||||
|
|
||||||
testTeamRepositories := func(teamID int64, repoIDs []int64) { |
|
||||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) |
|
||||||
assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name) |
|
||||||
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name) |
|
||||||
assert.Len(t, team.Repos, len(repoIDs), "%s: repo count", team.Name) |
|
||||||
for i, rid := range repoIDs { |
|
||||||
if rid > 0 { |
|
||||||
assert.True(t, HasRepository(db.DefaultContext, team, rid), "%s: HasRepository(%d) %d", rid, i) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Get an admin user.
|
|
||||||
user, err := user_model.GetUserByID(db.DefaultContext, 1) |
|
||||||
assert.NoError(t, err, "GetUserByID") |
|
||||||
|
|
||||||
// Create org.
|
|
||||||
org := &organization.Organization{ |
|
||||||
Name: "All_repo", |
|
||||||
IsActive: true, |
|
||||||
Type: user_model.UserTypeOrganization, |
|
||||||
Visibility: structs.VisibleTypePublic, |
|
||||||
} |
|
||||||
assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, user), "CreateOrganization") |
|
||||||
|
|
||||||
// Check Owner team.
|
|
||||||
ownerTeam, err := org.GetOwnerTeam(db.DefaultContext) |
|
||||||
assert.NoError(t, err, "GetOwnerTeam") |
|
||||||
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories") |
|
||||||
|
|
||||||
// Create repos.
|
|
||||||
repoIDs := make([]int64, 0) |
|
||||||
for i := 0; i < 3; 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) |
|
||||||
} |
|
||||||
} |
|
||||||
// Get fresh copy of Owner team after creating repos.
|
|
||||||
ownerTeam, err = org.GetOwnerTeam(db.DefaultContext) |
|
||||||
assert.NoError(t, err, "GetOwnerTeam") |
|
||||||
|
|
||||||
// Create teams and check repositories.
|
|
||||||
teams := []*organization.Team{ |
|
||||||
ownerTeam, |
|
||||||
{ |
|
||||||
OrgID: org.ID, |
|
||||||
Name: "team one", |
|
||||||
AccessMode: perm.AccessModeRead, |
|
||||||
IncludesAllRepositories: true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
OrgID: org.ID, |
|
||||||
Name: "team 2", |
|
||||||
AccessMode: perm.AccessModeRead, |
|
||||||
IncludesAllRepositories: false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
OrgID: org.ID, |
|
||||||
Name: "team three", |
|
||||||
AccessMode: perm.AccessModeWrite, |
|
||||||
IncludesAllRepositories: true, |
|
||||||
}, |
|
||||||
{ |
|
||||||
OrgID: org.ID, |
|
||||||
Name: "team 4", |
|
||||||
AccessMode: perm.AccessModeWrite, |
|
||||||
IncludesAllRepositories: false, |
|
||||||
}, |
|
||||||
} |
|
||||||
teamRepos := [][]int64{ |
|
||||||
repoIDs, |
|
||||||
repoIDs, |
|
||||||
{}, |
|
||||||
repoIDs, |
|
||||||
{}, |
|
||||||
} |
|
||||||
for i, team := range teams { |
|
||||||
if i > 0 { // first team is Owner.
|
|
||||||
assert.NoError(t, models.NewTeam(db.DefaultContext, team), "%s: NewTeam", team.Name) |
|
||||||
} |
|
||||||
testTeamRepositories(team.ID, teamRepos[i]) |
|
||||||
} |
|
||||||
|
|
||||||
// Update teams and check repositories.
|
|
||||||
teams[3].IncludesAllRepositories = false |
|
||||||
teams[4].IncludesAllRepositories = true |
|
||||||
teamRepos[4] = repoIDs |
|
||||||
for i, team := range teams { |
|
||||||
assert.NoError(t, models.UpdateTeam(db.DefaultContext, team, false, true), "%s: UpdateTeam", team.Name) |
|
||||||
testTeamRepositories(team.ID, teamRepos[i]) |
|
||||||
} |
|
||||||
|
|
||||||
// Create repo and check teams repositories.
|
|
||||||
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) |
|
||||||
} |
|
||||||
teamRepos[0] = repoIDs |
|
||||||
teamRepos[1] = repoIDs |
|
||||||
teamRepos[4] = repoIDs |
|
||||||
for i, team := range teams { |
|
||||||
testTeamRepositories(team.ID, teamRepos[i]) |
|
||||||
} |
|
||||||
|
|
||||||
// Remove repo and check teams repositories.
|
|
||||||
assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, repoIDs[0]), "DeleteRepository") |
|
||||||
teamRepos[0] = repoIDs[1:] |
|
||||||
teamRepos[1] = repoIDs[1:] |
|
||||||
teamRepos[3] = repoIDs[1:3] |
|
||||||
teamRepos[4] = repoIDs[1:] |
|
||||||
for i, team := range teams { |
|
||||||
testTeamRepositories(team.ID, teamRepos[i]) |
|
||||||
} |
|
||||||
|
|
||||||
// Wipe created items.
|
|
||||||
for i, rid := range repoIDs { |
|
||||||
if i > 0 { // first repo already deleted.
|
|
||||||
assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, rid), "DeleteRepository %d", i) |
|
||||||
} |
|
||||||
} |
|
||||||
assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization") |
|
||||||
} |
|
||||||
@ -0,0 +1,226 @@ |
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repository |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db" |
||||||
|
issues_model "code.gitea.io/gitea/models/issues" |
||||||
|
"code.gitea.io/gitea/models/organization" |
||||||
|
access_model "code.gitea.io/gitea/models/perm/access" |
||||||
|
repo_model "code.gitea.io/gitea/models/repo" |
||||||
|
"code.gitea.io/gitea/modules/setting" |
||||||
|
) |
||||||
|
|
||||||
|
// TeamAddRepository adds new repository to team of organization.
|
||||||
|
func TeamAddRepository(ctx context.Context, 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 organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error { |
||||||
|
return addRepositoryToTeam(ctx, t, repo) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func addRepositoryToTeam(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) { |
||||||
|
if err = organization.AddTeamRepo(ctx, t.OrgID, t.ID, repo.ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if err = organization.IncrTeamRepoNum(ctx, t.ID); err != nil { |
||||||
|
return fmt.Errorf("update team: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
t.NumRepos++ |
||||||
|
|
||||||
|
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { |
||||||
|
return fmt.Errorf("recalculateAccesses: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
// Make all team members watch this repo if enabled in global settings
|
||||||
|
if setting.Service.AutoWatchNewRepos { |
||||||
|
if err = t.LoadMembers(ctx); err != nil { |
||||||
|
return fmt.Errorf("getMembers: %w", err) |
||||||
|
} |
||||||
|
for _, u := range t.Members { |
||||||
|
if err = repo_model.WatchRepo(ctx, u, repo, true); err != nil { |
||||||
|
return fmt.Errorf("watchRepo: %w", err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// AddAllRepositoriesToTeam adds all repositories to the team.
|
||||||
|
// If the team already has some repositories they will be left unchanged.
|
||||||
|
func AddAllRepositoriesToTeam(ctx context.Context, t *organization.Team) error { |
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error { |
||||||
|
orgRepos, err := organization.GetOrgRepositories(ctx, t.OrgID) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("get org repos: %w", err) |
||||||
|
} |
||||||
|
|
||||||
|
for _, repo := range orgRepos { |
||||||
|
if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) { |
||||||
|
if err := addRepositoryToTeam(ctx, t, repo); err != nil { |
||||||
|
return fmt.Errorf("AddRepository: %w", err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// RemoveAllRepositoriesFromTeam removes all repositories from team and recalculates access
|
||||||
|
func RemoveAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (err error) { |
||||||
|
if t.IncludesAllRepositories { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
ctx, committer, err := db.TxContext(ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer committer.Close() |
||||||
|
|
||||||
|
if err = removeAllRepositoriesFromTeam(ctx, t); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return committer.Commit() |
||||||
|
} |
||||||
|
|
||||||
|
// removeAllRepositoriesFromTeam removes all repositories from team and recalculates access
|
||||||
|
// Note: Shall not be called if team includes all repositories
|
||||||
|
func removeAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (err error) { |
||||||
|
e := db.GetEngine(ctx) |
||||||
|
// Delete all accesses.
|
||||||
|
for _, repo := range t.Repos { |
||||||
|
if err := access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Remove watches from all users and now unaccessible repos
|
||||||
|
for _, user := range t.Members { |
||||||
|
has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} else if has { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err = issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Delete team-repo
|
||||||
|
if _, err := e. |
||||||
|
Where("team_id=?", t.ID). |
||||||
|
Delete(new(organization.TeamRepo)); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
t.NumRepos = 0 |
||||||
|
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// 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(ctx, 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() |
||||||
|
} |
||||||
|
|
||||||
|
// 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 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ |
||||||
|
TeamID: t.ID, |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
return fmt.Errorf("GetTeamMembers: %w", err) |
||||||
|
} |
||||||
|
for _, member := range teamMembers { |
||||||
|
has, err := access_model.HasAnyUnitAccess(ctx, member.ID, repo) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} else if has { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if err = repo_model.WatchRepo(ctx, member, repo, false); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err := issues_model.RemoveIssueWatchersByRepoID(ctx, member.ID, repo.ID); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// HasRepository returns true if given repository belong to team.
|
||||||
|
func HasRepository(ctx context.Context, t *organization.Team, repoID int64) bool { |
||||||
|
return organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID) |
||||||
|
} |
||||||
@ -1,7 +1,7 @@ |
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package org |
package repository |
||||||
|
|
||||||
import ( |
import ( |
||||||
"testing" |
"testing" |
||||||
Loading…
Reference in new issue