v4 migration, merge 'dev', clean code and mirror fix
This commit is contained in:
parent
e90f014e4a
commit
ee68a826a5
2
gogs.go
2
gogs.go
@ -17,7 +17,7 @@ import (
|
|||||||
"github.com/gogits/gogs/modules/setting"
|
"github.com/gogits/gogs/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APP_VER = "0.5.14.0222 Beta"
|
const APP_VER = "0.5.15.0223 Beta"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
|
@ -24,21 +24,19 @@ type Access struct {
|
|||||||
Mode AccessMode
|
Mode AccessMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the Access a user has to a repository. Will return NoneAccess if the
|
func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
|
||||||
// user does not have access. User can be nil!
|
|
||||||
func AccessLevel(u *User, r *Repository) (AccessMode, error) {
|
|
||||||
mode := ACCESS_MODE_NONE
|
mode := ACCESS_MODE_NONE
|
||||||
if !r.IsPrivate {
|
if !repo.IsPrivate {
|
||||||
mode = ACCESS_MODE_READ
|
mode = ACCESS_MODE_READ
|
||||||
}
|
}
|
||||||
|
|
||||||
if u != nil {
|
if u != nil {
|
||||||
if u.Id == r.OwnerId {
|
if u.Id == repo.OwnerId {
|
||||||
return ACCESS_MODE_OWNER, nil
|
return ACCESS_MODE_OWNER, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &Access{UserID: u.Id, RepoID: r.Id}
|
a := &Access{UserID: u.Id, RepoID: repo.Id}
|
||||||
if has, err := x.Get(a); !has || err != nil {
|
if has, err := e.Get(a); !has || err != nil {
|
||||||
return mode, err
|
return mode, err
|
||||||
}
|
}
|
||||||
return a.Mode, nil
|
return a.Mode, nil
|
||||||
@ -47,12 +45,22 @@ func AccessLevel(u *User, r *Repository) (AccessMode, error) {
|
|||||||
return mode, nil
|
return mode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAccess returns true if someone has the request access level. User can be nil!
|
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
|
||||||
func HasAccess(u *User, r *Repository, testMode AccessMode) (bool, error) {
|
// user does not have access. User can be nil!
|
||||||
mode, err := AccessLevel(u, r)
|
func AccessLevel(u *User, repo *Repository) (AccessMode, error) {
|
||||||
|
return accessLevel(x, u, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||||
|
mode, err := accessLevel(e, u, repo)
|
||||||
return testMode <= mode, err
|
return testMode <= mode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasAccess returns true if someone has the request access level. User can be nil!
|
||||||
|
func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||||
|
return hasAccess(x, u, repo, testMode)
|
||||||
|
}
|
||||||
|
|
||||||
// GetAccessibleRepositories finds all repositories where a user has access to,
|
// GetAccessibleRepositories finds all repositories where a user has access to,
|
||||||
// besides his own.
|
// besides his own.
|
||||||
func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
||||||
@ -96,7 +104,8 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, mode AccessMode) error
|
|||||||
func (repo *Repository) recalculateAccesses(e Engine) error {
|
func (repo *Repository) recalculateAccesses(e Engine) error {
|
||||||
accessMap := make(map[int64]AccessMode, 20)
|
accessMap := make(map[int64]AccessMode, 20)
|
||||||
|
|
||||||
// Give all collaborators write access
|
// FIXME: should be able to have read-only access.
|
||||||
|
// Give all collaborators write access.
|
||||||
collaborators, err := repo.getCollaborators(e)
|
collaborators, err := repo.getCollaborators(e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -114,9 +123,7 @@ func (repo *Repository) recalculateAccesses(e Engine) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, team := range repo.Owner.Teams {
|
for _, team := range repo.Owner.Teams {
|
||||||
if !(team.IsOwnerTeam() || team.HasRepository(repo)) {
|
if team.IsOwnerTeam() {
|
||||||
continue
|
|
||||||
} else if team.IsOwnerTeam() {
|
|
||||||
team.Authorize = ACCESS_MODE_OWNER
|
team.Authorize = ACCESS_MODE_OWNER
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +136,8 @@ func (repo *Repository) recalculateAccesses(e Engine) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: do corss-comparison so reduce deletions and additions to the minimum?
|
||||||
|
|
||||||
minMode := ACCESS_MODE_READ
|
minMode := ACCESS_MODE_READ
|
||||||
if !repo.IsPrivate {
|
if !repo.IsPrivate {
|
||||||
minMode = ACCESS_MODE_WRITE
|
minMode = ACCESS_MODE_WRITE
|
||||||
|
@ -434,20 +434,20 @@ func NewRepoAction(u *User, repo *Repository) (err error) {
|
|||||||
return newRepoAction(x, u, repo)
|
return newRepoAction(x, u, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferRepoAction(e Engine, u, newUser *User, repo *Repository) (err error) {
|
func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
|
||||||
action := &Action{
|
action := &Action{
|
||||||
ActUserId: u.Id,
|
ActUserId: actUser.Id,
|
||||||
ActUserName: u.Name,
|
ActUserName: actUser.Name,
|
||||||
ActEmail: u.Email,
|
ActEmail: actUser.Email,
|
||||||
OpType: TRANSFER_REPO,
|
OpType: TRANSFER_REPO,
|
||||||
RepoId: repo.Id,
|
RepoId: repo.Id,
|
||||||
RepoUserName: newUser.Name,
|
RepoUserName: newOwner.Name,
|
||||||
RepoName: repo.Name,
|
RepoName: repo.Name,
|
||||||
IsPrivate: repo.IsPrivate,
|
IsPrivate: repo.IsPrivate,
|
||||||
Content: path.Join(repo.Owner.LowerName, repo.LowerName),
|
Content: path.Join(oldOwner.LowerName, repo.LowerName),
|
||||||
}
|
}
|
||||||
if err = notifyWatchers(e, action); err != nil {
|
if err = notifyWatchers(e, action); err != nil {
|
||||||
return fmt.Errorf("notify watchers '%d/%s'", u.Id, repo.Id)
|
return fmt.Errorf("notify watchers '%d/%s'", actUser.Id, repo.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove watch for organization.
|
// Remove watch for organization.
|
||||||
@ -457,13 +457,13 @@ func transferRepoAction(e Engine, u, newUser *User, repo *Repository) (err error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("action.TransferRepoAction: %s/%s", u.Name, repo.Name)
|
log.Trace("action.TransferRepoAction: %s/%s", actUser.Name, repo.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransferRepoAction adds new action for transferring repository.
|
// TransferRepoAction adds new action for transferring repository.
|
||||||
func TransferRepoAction(u, newUser *User, repo *Repository) (err error) {
|
func TransferRepoAction(actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
|
||||||
return transferRepoAction(x, u, newUser, repo)
|
return transferRepoAction(x, actUser, oldOwner, newOwner, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFeeds returns action list of given user in given context.
|
// GetFeeds returns action list of given user in given context.
|
||||||
|
@ -53,6 +53,7 @@ var migrations = []Migration{
|
|||||||
NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1
|
NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1
|
||||||
NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2
|
NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2
|
||||||
NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3
|
NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3
|
||||||
|
NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate database to current version
|
// Migrate database to current version
|
||||||
@ -214,8 +215,8 @@ func accessToCollaboration(x *xorm.Engine) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ownerTeamUpdate(x *xorm.Engine) (err error) {
|
func ownerTeamUpdate(x *xorm.Engine) (err error) {
|
||||||
if _, err := x.Exec("UPDATE team SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
|
if _, err := x.Exec("UPDATE `team` SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
|
||||||
return fmt.Errorf("drop table: %v", err)
|
return fmt.Errorf("update owner team table: %v", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -239,9 +240,9 @@ func accessRefactor(x *xorm.Engine) (err error) {
|
|||||||
|
|
||||||
accessMap := make(map[UserRepo]AccessMode, 50)
|
accessMap := make(map[UserRepo]AccessMode, 50)
|
||||||
|
|
||||||
results, err := x.Query("SELECT r.id as `repo_id`, r.is_private as `is_private`, r.owner_id as `owner_id`, u.type as `owner_type` FROM `repository` r LEFT JOIN user u ON r.owner_id=u.id")
|
results, err := x.Query("SELECT r.id AS `repo_id`, r.is_private AS `is_private`, r.owner_id AS `owner_id`, u.type AS `owner_type` FROM `repository` r LEFT JOIN `user` u ON r.owner_id=u.id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("select repositories: %v", err)
|
||||||
}
|
}
|
||||||
for _, repo := range results {
|
for _, repo := range results {
|
||||||
repoID := com.StrTo(repo["repo_id"]).MustInt64()
|
repoID := com.StrTo(repo["repo_id"]).MustInt64()
|
||||||
@ -249,9 +250,9 @@ func accessRefactor(x *xorm.Engine) (err error) {
|
|||||||
ownerID := com.StrTo(repo["owner_id"]).MustInt64()
|
ownerID := com.StrTo(repo["owner_id"]).MustInt64()
|
||||||
ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0
|
ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0
|
||||||
|
|
||||||
results, err := x.Query("SELECT user_id FROM collaboration WHERE repo_id=?", repoID)
|
results, err := x.Query("SELECT `user_id` FROM `collaboration` WHERE repo_id=?", repoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("select repos: %v", err)
|
return fmt.Errorf("select collaborators: %v", err)
|
||||||
}
|
}
|
||||||
for _, user := range results {
|
for _, user := range results {
|
||||||
userID := com.StrTo(user["user_id"]).MustInt64()
|
userID := com.StrTo(user["user_id"]).MustInt64()
|
||||||
@ -262,6 +263,8 @@ func accessRefactor(x *xorm.Engine) (err error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The minimum level to add a new access record,
|
||||||
|
// because public repository has implicit open access.
|
||||||
minAccessLevel := AccessMode(0)
|
minAccessLevel := AccessMode(0)
|
||||||
if !isPrivate {
|
if !isPrivate {
|
||||||
minAccessLevel = 1
|
minAccessLevel = 1
|
||||||
@ -269,7 +272,7 @@ func accessRefactor(x *xorm.Engine) (err error) {
|
|||||||
|
|
||||||
repoString := "$" + string(repo["repo_id"]) + "|"
|
repoString := "$" + string(repo["repo_id"]) + "|"
|
||||||
|
|
||||||
results, err = x.Query("SELECT id, authorize, repo_ids FROM team WHERE org_id=? AND authorize > ? ORDER BY authorize ASC", ownerID, int(minAccessLevel))
|
results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("select teams from org: %v", err)
|
return fmt.Errorf("select teams from org: %v", err)
|
||||||
}
|
}
|
||||||
@ -281,20 +284,20 @@ func accessRefactor(x *xorm.Engine) (err error) {
|
|||||||
teamID := com.StrTo(team["id"]).MustInt64()
|
teamID := com.StrTo(team["id"]).MustInt64()
|
||||||
mode := AccessMode(com.StrTo(team["authorize"]).MustInt())
|
mode := AccessMode(com.StrTo(team["authorize"]).MustInt())
|
||||||
|
|
||||||
results, err := x.Query("SELECT uid FROM team_user WHERE team_id=?", teamID)
|
results, err := x.Query("SELECT `uid` FROM `team_user` WHERE team_id=?", teamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("select users from team: %v", err)
|
return fmt.Errorf("select users from team: %v", err)
|
||||||
}
|
}
|
||||||
for _, user := range results {
|
for _, user := range results {
|
||||||
userID := com.StrTo(user["uid"]).MustInt64()
|
userID := com.StrTo(user["user_id"]).MustInt64()
|
||||||
accessMap[UserRepo{userID, repoID}] = mode
|
accessMap[UserRepo{userID, repoID}] = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop table can't be in a session (at least not in sqlite)
|
// Drop table can't be in a session (at least not in sqlite)
|
||||||
if _, err = x.Exec("DROP TABLE access"); err != nil {
|
if _, err = x.Exec("DROP TABLE `access`"); err != nil {
|
||||||
return fmt.Errorf("drop table: %v", err)
|
return fmt.Errorf("drop access table: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we start writing so we make a session
|
// Now we start writing so we make a session
|
||||||
@ -313,7 +316,56 @@ func accessRefactor(x *xorm.Engine) (err error) {
|
|||||||
accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
|
accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = sess.Insert(accesses)
|
if _, err = sess.Insert(accesses); err != nil {
|
||||||
|
return fmt.Errorf("insert accesses: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func teamToTeamRepo(x *xorm.Engine) error {
|
||||||
|
type TeamRepo struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
OrgID int64 `xorm:"INDEX"`
|
||||||
|
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
teamRepos := make([]*TeamRepo, 0, 50)
|
||||||
|
|
||||||
|
results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("select teams: %v", err)
|
||||||
|
}
|
||||||
|
for _, team := range results {
|
||||||
|
orgID := com.StrTo(team["org_id"]).MustInt64()
|
||||||
|
teamID := com.StrTo(team["id"]).MustInt64()
|
||||||
|
|
||||||
|
for _, idStr := range strings.Split(string(team["repo_ids"]), "|") {
|
||||||
|
repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
|
||||||
|
if repoID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
teamRepos = append(teamRepos, &TeamRepo{
|
||||||
|
OrgID: orgID,
|
||||||
|
TeamID: teamID,
|
||||||
|
RepoID: repoID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := x.NewSession()
|
||||||
|
defer sessionRelease(sess)
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = sess.Sync2(new(TeamRepo)); err != nil {
|
||||||
|
return fmt.Errorf("sync: %v", err)
|
||||||
|
} else if _, err = sess.Insert(teamRepos); err != nil {
|
||||||
|
return fmt.Errorf("insert team-repos: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
|
|
||||||
// "github.com/gogits/gogs/models/migrations"
|
"github.com/gogits/gogs/models/migrations"
|
||||||
"github.com/gogits/gogs/modules/setting"
|
"github.com/gogits/gogs/modules/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,7 +59,8 @@ func init() {
|
|||||||
new(Watch), new(Star), new(Follow), new(Action),
|
new(Watch), new(Star), new(Follow), new(Action),
|
||||||
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
|
new(Issue), new(Comment), new(Attachment), new(IssueUser), new(Label), new(Milestone),
|
||||||
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
new(Mirror), new(Release), new(LoginSource), new(Webhook),
|
||||||
new(UpdateTask), new(HookTask), new(Team), new(OrgUser), new(TeamUser),
|
new(UpdateTask), new(HookTask),
|
||||||
|
new(Team), new(OrgUser), new(TeamUser), new(TeamRepo),
|
||||||
new(Notice), new(EmailAddress))
|
new(Notice), new(EmailAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
306
models/org.go
306
models/org.go
@ -6,11 +6,10 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Unknwon/com"
|
|
||||||
|
|
||||||
"github.com/gogits/gogs/modules/base"
|
"github.com/gogits/gogs/modules/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -126,53 +125,48 @@ func CreateOrganization(org, owner *User) (*User, error) {
|
|||||||
org.NumMembers = 1
|
org.NumMembers = 1
|
||||||
|
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
defer sess.Close()
|
defer sessionRelease(sess)
|
||||||
if err = sess.Begin(); err != nil {
|
if err = sess.Begin(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = sess.Insert(org); err != nil {
|
if _, err = sess.Insert(org); err != nil {
|
||||||
sess.Rollback()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil {
|
|
||||||
sess.Rollback()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create default owner team.
|
// Create default owner team.
|
||||||
t := &Team{
|
t := &Team{
|
||||||
OrgId: org.Id,
|
OrgID: org.Id,
|
||||||
LowerName: strings.ToLower(OWNER_TEAM),
|
LowerName: strings.ToLower(OWNER_TEAM),
|
||||||
Name: OWNER_TEAM,
|
Name: OWNER_TEAM,
|
||||||
Authorize: ACCESS_MODE_OWNER,
|
Authorize: ACCESS_MODE_OWNER,
|
||||||
NumMembers: 1,
|
NumMembers: 1,
|
||||||
}
|
}
|
||||||
if _, err = sess.Insert(t); err != nil {
|
if _, err = sess.Insert(t); err != nil {
|
||||||
sess.Rollback()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add initial creator to organization and owner team.
|
// Add initial creator to organization and owner team.
|
||||||
ou := &OrgUser{
|
ou := &OrgUser{
|
||||||
Uid: owner.Id,
|
Uid: owner.Id,
|
||||||
OrgId: org.Id,
|
OrgID: org.Id,
|
||||||
IsOwner: true,
|
IsOwner: true,
|
||||||
NumTeams: 1,
|
NumTeams: 1,
|
||||||
}
|
}
|
||||||
if _, err = sess.Insert(ou); err != nil {
|
if _, err = sess.Insert(ou); err != nil {
|
||||||
sess.Rollback()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tu := &TeamUser{
|
tu := &TeamUser{
|
||||||
Uid: owner.Id,
|
Uid: owner.Id,
|
||||||
OrgId: org.Id,
|
OrgID: org.Id,
|
||||||
TeamId: t.Id,
|
TeamID: t.ID,
|
||||||
}
|
}
|
||||||
if _, err = sess.Insert(tu); err != nil {
|
if _, err = sess.Insert(tu); err != nil {
|
||||||
sess.Rollback()
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.MkdirAll(UserPath(org.Name), os.ModePerm); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,15 +217,15 @@ func DeleteOrganization(org *User) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = sess.Delete(&Team{OrgId: org.Id}); err != nil {
|
if _, err = sess.Delete(&Team{OrgID: org.Id}); err != nil {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = sess.Delete(&OrgUser{OrgId: org.Id}); err != nil {
|
if _, err = sess.Delete(&OrgUser{OrgID: org.Id}); err != nil {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err = sess.Delete(&TeamUser{OrgId: org.Id}); err != nil {
|
if _, err = sess.Delete(&TeamUser{OrgID: org.Id}); err != nil {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -247,9 +241,9 @@ func DeleteOrganization(org *User) (err error) {
|
|||||||
|
|
||||||
// OrgUser represents an organization-user relation.
|
// OrgUser represents an organization-user relation.
|
||||||
type OrgUser struct {
|
type OrgUser struct {
|
||||||
Id int64
|
ID int64 `xorm:"pk autoincr"`
|
||||||
Uid int64 `xorm:"INDEX UNIQUE(s)"`
|
Uid int64 `xorm:"INDEX UNIQUE(s)"`
|
||||||
OrgId int64 `xorm:"INDEX UNIQUE(s)"`
|
OrgID int64 `xorm:"INDEX UNIQUE(s)"`
|
||||||
IsPublic bool
|
IsPublic bool
|
||||||
IsOwner bool
|
IsOwner bool
|
||||||
NumTeams int
|
NumTeams int
|
||||||
@ -298,7 +292,7 @@ func ChangeOrgUserStatus(orgId, uid int64, public bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ou.IsPublic = public
|
ou.IsPublic = public
|
||||||
_, err = x.Id(ou.Id).AllCols().Update(ou)
|
_, err = x.Id(ou.ID).AllCols().Update(ou)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +310,7 @@ func AddOrgUser(orgId, uid int64) error {
|
|||||||
|
|
||||||
ou := &OrgUser{
|
ou := &OrgUser{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
OrgId: orgId,
|
OrgID: orgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sess.Insert(ou); err != nil {
|
if _, err := sess.Insert(ou); err != nil {
|
||||||
@ -367,7 +361,7 @@ func RemoveOrgUser(orgId, uid int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sess.Id(ou.Id).Delete(ou); err != nil {
|
if _, err := sess.Id(ou.ID).Delete(ou); err != nil {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members - 1 WHERE id = ?", orgId); err != nil {
|
} else if _, err = sess.Exec("UPDATE `user` SET num_members = num_members - 1 WHERE id = ?", orgId); err != nil {
|
||||||
@ -400,7 +394,7 @@ func RemoveOrgUser(orgId, uid int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, t := range ts {
|
for _, t := range ts {
|
||||||
if err = removeTeamMember(sess, org.Id, t.Id, u.Id); err != nil {
|
if err = removeTeamMember(sess, org.Id, t.ID, u.Id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -419,13 +413,12 @@ const OWNER_TEAM = "Owners"
|
|||||||
|
|
||||||
// Team represents a organization team.
|
// Team represents a organization team.
|
||||||
type Team struct {
|
type Team struct {
|
||||||
Id int64
|
ID int64 `xorm:"pk autoincr"`
|
||||||
OrgId int64 `xorm:"INDEX"`
|
OrgID int64 `xorm:"INDEX"`
|
||||||
LowerName string
|
LowerName string
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Authorize AccessMode
|
Authorize AccessMode
|
||||||
RepoIds string `xorm:"TEXT"`
|
|
||||||
Repos []*Repository `xorm:"-"`
|
Repos []*Repository `xorm:"-"`
|
||||||
Members []*User `xorm:"-"`
|
Members []*User `xorm:"-"`
|
||||||
NumRepos int
|
NumRepos int
|
||||||
@ -439,23 +432,20 @@ func (t *Team) IsOwnerTeam() bool {
|
|||||||
|
|
||||||
// IsTeamMember returns true if given user is a member of team.
|
// IsTeamMember returns true if given user is a member of team.
|
||||||
func (t *Team) IsMember(uid int64) bool {
|
func (t *Team) IsMember(uid int64) bool {
|
||||||
return IsTeamMember(t.OrgId, t.Id, uid)
|
return IsTeamMember(t.OrgID, t.ID, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Team) getRepositories(e Engine) error {
|
func (t *Team) getRepositories(e Engine) (err error) {
|
||||||
idStrs := strings.Split(t.RepoIds, "|")
|
teamRepos := make([]*TeamRepo, 0, t.NumRepos)
|
||||||
t.Repos = make([]*Repository, 0, len(idStrs))
|
if err = x.Where("team_id=?", t.ID).Find(&teamRepos); err != nil {
|
||||||
for _, str := range idStrs {
|
return fmt.Errorf("get team-repos: %v", err)
|
||||||
if len(str) == 0 {
|
}
|
||||||
continue
|
|
||||||
}
|
t.Repos = make([]*Repository, 0, len(teamRepos))
|
||||||
id := com.StrTo(str[1:]).MustInt64()
|
for i := range teamRepos {
|
||||||
if id == 0 {
|
repo, err := getRepositoryById(e, teamRepos[i].RepoID)
|
||||||
continue
|
|
||||||
}
|
|
||||||
repo, err := getRepositoryById(e, id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("getRepositoryById(%d): %v", teamRepos[i].RepoID, err)
|
||||||
}
|
}
|
||||||
t.Repos = append(t.Repos, repo)
|
t.Repos = append(t.Repos, repo)
|
||||||
}
|
}
|
||||||
@ -468,7 +458,7 @@ func (t *Team) GetRepositories() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *Team) getMembers(e Engine) (err error) {
|
func (t *Team) getMembers(e Engine) (err error) {
|
||||||
t.Members, err = getTeamMembers(e, t.Id)
|
t.Members, err = getTeamMembers(e, t.ID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -479,73 +469,110 @@ func (t *Team) GetMembers() (err error) {
|
|||||||
|
|
||||||
// AddMember adds new member to team of organization.
|
// AddMember adds new member to team of organization.
|
||||||
func (t *Team) AddMember(uid int64) error {
|
func (t *Team) AddMember(uid int64) error {
|
||||||
return AddTeamMember(t.OrgId, t.Id, uid)
|
return AddTeamMember(t.OrgID, t.ID, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveMember removes member from team of organization.
|
// RemoveMember removes member from team of organization.
|
||||||
func (t *Team) RemoveMember(uid int64) error {
|
func (t *Team) RemoveMember(uid int64) error {
|
||||||
return RemoveTeamMember(t.OrgId, t.Id, uid)
|
return RemoveTeamMember(t.OrgID, t.ID, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRepository adds new repository to team of organization.
|
func (t *Team) hasRepository(e Engine, repoID int64) bool {
|
||||||
func (t *Team) AddRepository(repo *Repository) (err error) {
|
return hasTeamRepo(e, t.OrgID, t.ID, repoID)
|
||||||
idStr := "$" + com.ToStr(repo.Id) + "|"
|
}
|
||||||
if repo.OwnerId != t.OrgId {
|
|
||||||
return errors.New("Repository not belong to organization")
|
|
||||||
} else if strings.Contains(t.RepoIds, idStr) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = repo.GetOwner(); err != nil {
|
// HasRepository returns true if given repository belong to team.
|
||||||
return err
|
func (t *Team) HasRepository(repoID int64) bool {
|
||||||
} else if err = t.GetMembers(); err != nil {
|
return HasTeamRepo(t.OrgID, t.ID, repoID)
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sess := x.NewSession()
|
func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
|
||||||
defer sessionRelease(sess)
|
if err = addTeamRepo(e, t.OrgID, t.ID, repo.Id); err != nil {
|
||||||
if err = sess.Begin(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.NumRepos++
|
t.NumRepos++
|
||||||
t.RepoIds += idStr
|
if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
|
||||||
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo.recalculateAccesses(sess); err != nil {
|
if err = repo.recalculateAccesses(e); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = t.getMembers(e); err != nil {
|
||||||
|
return fmt.Errorf("get team members: %v", err)
|
||||||
|
}
|
||||||
for _, u := range t.Members {
|
for _, u := range t.Members {
|
||||||
if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
|
if err = watchRepo(e, u.Id, repo.Id, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sess.Commit()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Team) HasRepository(repo *Repository) bool {
|
// AddRepository adds new repository to team of organization.
|
||||||
idStr := "$" + com.ToStr(repo.Id) + "|"
|
func (t *Team) AddRepository(repo *Repository) (err error) {
|
||||||
return strings.Contains(t.RepoIds, idStr)
|
if repo.OwnerId != t.OrgID {
|
||||||
}
|
return errors.New("Repository does not belong to organization")
|
||||||
|
} else if t.HasRepository(repo.Id) {
|
||||||
// RemoveRepository removes repository from team of organization.
|
|
||||||
func (t *Team) RemoveRepository(repoId int64) error {
|
|
||||||
idStr := "$" + com.ToStr(repoId) + "|"
|
|
||||||
if !strings.Contains(t.RepoIds, idStr) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := GetRepositoryById(repoId)
|
sess := x.NewSession()
|
||||||
if err != nil {
|
defer sessionRelease(sess)
|
||||||
|
if err = sess.Begin(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo.GetOwner(); err != nil {
|
if err = t.addRepository(sess, repo); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = t.GetMembers(); err != nil {
|
}
|
||||||
|
|
||||||
|
return sess.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Team) removeRepository(e Engine, repo *Repository) (err error) {
|
||||||
|
if err = removeTeamRepo(e, t.ID, repo.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumRepos--
|
||||||
|
if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = repo.recalculateAccesses(e); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = t.getMembers(e); err != nil {
|
||||||
|
return fmt.Errorf("get team members: %v", err)
|
||||||
|
}
|
||||||
|
for _, u := range t.Members {
|
||||||
|
has, err := hasAccess(e, u, repo, ACCESS_MODE_READ)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = watchRepo(e, u.Id, repo.Id, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRepository removes repository from team of organization.
|
||||||
|
func (t *Team) RemoveRepository(repoID int64) error {
|
||||||
|
if !t.HasRepository(repoID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := GetRepositoryById(repoID)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,22 +582,10 @@ func (t *Team) RemoveRepository(repoId int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.NumRepos--
|
if err = t.removeRepository(sess, repo); err != nil {
|
||||||
t.RepoIds = strings.Replace(t.RepoIds, idStr, "", 1)
|
|
||||||
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo.recalculateAccesses(sess); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, u := range t.Members {
|
|
||||||
if err = watchRepo(sess, u.Id, repo.Id, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,7 +596,7 @@ func NewTeam(t *Team) error {
|
|||||||
return ErrTeamNameIllegal
|
return ErrTeamNameIllegal
|
||||||
}
|
}
|
||||||
|
|
||||||
has, err := x.Id(t.OrgId).Get(new(User))
|
has, err := x.Id(t.OrgID).Get(new(User))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
@ -589,7 +604,7 @@ func NewTeam(t *Team) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.LowerName = strings.ToLower(t.Name)
|
t.LowerName = strings.ToLower(t.Name)
|
||||||
has, err = x.Where("org_id=?", t.OrgId).And("lower_name=?", t.LowerName).Get(new(Team))
|
has, err = x.Where("org_id=?", t.OrgID).And("lower_name=?", t.LowerName).Get(new(Team))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if has {
|
} else if has {
|
||||||
@ -608,7 +623,7 @@ func NewTeam(t *Team) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update organization number of teams.
|
// Update organization number of teams.
|
||||||
if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams + 1 WHERE id = ?", t.OrgId); err != nil {
|
if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams + 1 WHERE id = ?", t.OrgID); err != nil {
|
||||||
sess.Rollback()
|
sess.Rollback()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -617,7 +632,7 @@ func NewTeam(t *Team) error {
|
|||||||
|
|
||||||
func getTeam(e Engine, orgId int64, name string) (*Team, error) {
|
func getTeam(e Engine, orgId int64, name string) (*Team, error) {
|
||||||
t := &Team{
|
t := &Team{
|
||||||
OrgId: orgId,
|
OrgID: orgId,
|
||||||
LowerName: strings.ToLower(name),
|
LowerName: strings.ToLower(name),
|
||||||
}
|
}
|
||||||
has, err := e.Get(t)
|
has, err := e.Get(t)
|
||||||
@ -667,7 +682,7 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.LowerName = strings.ToLower(t.Name)
|
t.LowerName = strings.ToLower(t.Name)
|
||||||
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
|
if _, err = sess.Id(t.ID).AllCols().Update(t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -697,7 +712,7 @@ func DeleteTeam(t *Team) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get organization.
|
// Get organization.
|
||||||
org, err := GetUserById(t.OrgId)
|
org, err := GetUserById(t.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -716,16 +731,16 @@ func DeleteTeam(t *Team) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete team-user.
|
// Delete team-user.
|
||||||
if _, err = sess.Where("org_id=?", org.Id).Where("team_id=?", t.Id).Delete(new(TeamUser)); err != nil {
|
if _, err = sess.Where("org_id=?", org.Id).Where("team_id=?", t.ID).Delete(new(TeamUser)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete team.
|
// Delete team.
|
||||||
if _, err = sess.Id(t.Id).Delete(new(Team)); err != nil {
|
if _, err = sess.Id(t.ID).Delete(new(Team)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Update organization number of teams.
|
// Update organization number of teams.
|
||||||
if _, err = sess.Exec("UPDATE `user` SET num_teams = num_teams - 1 WHERE id = ?", t.OrgId); err != nil {
|
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -741,20 +756,20 @@ func DeleteTeam(t *Team) error {
|
|||||||
|
|
||||||
// TeamUser represents an team-user relation.
|
// TeamUser represents an team-user relation.
|
||||||
type TeamUser struct {
|
type TeamUser struct {
|
||||||
Id int64
|
ID int64 `xorm:"pk autoincr"`
|
||||||
Uid int64
|
OrgID int64 `xorm:"INDEX"`
|
||||||
OrgId int64 `xorm:"INDEX"`
|
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||||
TeamId int64
|
Uid int64 `xorm:"UNIQUE(s)"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTeamMember(e Engine, orgId, teamId, uid int64) bool {
|
func isTeamMember(e Engine, orgID, teamID, uid int64) bool {
|
||||||
has, _ := e.Where("uid=?", uid).And("org_id=?", orgId).And("team_id=?", teamId).Get(new(TeamUser))
|
has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("uid=?", uid).Get(new(TeamUser))
|
||||||
return has
|
return has
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTeamMember returns true if given user is a member of team.
|
// IsTeamMember returns true if given user is a member of team.
|
||||||
func IsTeamMember(orgId, teamId, uid int64) bool {
|
func IsTeamMember(orgID, teamID, uid int64) bool {
|
||||||
return isTeamMember(x, orgId, teamId, uid)
|
return isTeamMember(x, orgID, teamID, uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTeamMembers(e Engine, teamID int64) ([]*User, error) {
|
func getTeamMembers(e Engine, teamID int64) ([]*User, error) {
|
||||||
@ -777,7 +792,7 @@ func getUserTeams(e Engine, orgId, uid int64) ([]*Team, error) {
|
|||||||
ts := make([]*Team, len(tus))
|
ts := make([]*Team, len(tus))
|
||||||
for i, tu := range tus {
|
for i, tu := range tus {
|
||||||
t := new(Team)
|
t := new(Team)
|
||||||
has, err := e.Id(tu.TeamId).Get(t)
|
has, err := e.Id(tu.TeamID).Get(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
@ -822,13 +837,13 @@ func AddTeamMember(orgId, teamId, uid int64) error {
|
|||||||
|
|
||||||
tu := &TeamUser{
|
tu := &TeamUser{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
OrgId: orgId,
|
OrgID: orgId,
|
||||||
TeamId: teamId,
|
TeamID: teamId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = sess.Insert(tu); err != nil {
|
if _, err = sess.Insert(tu); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Id(t.Id).Update(t); err != nil {
|
} else if _, err = sess.Id(t.ID).Update(t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -848,7 +863,7 @@ func AddTeamMember(orgId, teamId, uid int64) error {
|
|||||||
if t.IsOwnerTeam() {
|
if t.IsOwnerTeam() {
|
||||||
ou.IsOwner = true
|
ou.IsOwner = true
|
||||||
}
|
}
|
||||||
if _, err = sess.Id(ou.Id).AllCols().Update(ou); err != nil {
|
if _, err = sess.Id(ou.ID).AllCols().Update(ou); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -885,13 +900,13 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error {
|
|||||||
|
|
||||||
tu := &TeamUser{
|
tu := &TeamUser{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
OrgId: orgId,
|
OrgID: orgId,
|
||||||
TeamId: teamId,
|
TeamID: teamId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := e.Delete(tu); err != nil {
|
if _, err := e.Delete(tu); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = e.Id(t.Id).AllCols().Update(t); err != nil {
|
} else if _, err = e.Id(t.ID).AllCols().Update(t); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -912,7 +927,7 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error {
|
|||||||
if t.IsOwnerTeam() {
|
if t.IsOwnerTeam() {
|
||||||
ou.IsOwner = false
|
ou.IsOwner = false
|
||||||
}
|
}
|
||||||
if _, err = e.Id(ou.Id).AllCols().Update(ou); err != nil {
|
if _, err = e.Id(ou.ID).AllCols().Update(ou); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -921,13 +936,64 @@ func removeTeamMember(e Engine, orgId, teamId, uid int64) error {
|
|||||||
// RemoveTeamMember removes member from given team of given organization.
|
// RemoveTeamMember removes member from given team of given organization.
|
||||||
func RemoveTeamMember(orgId, teamId, uid int64) error {
|
func RemoveTeamMember(orgId, teamId, uid int64) error {
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
defer sess.Close()
|
defer sessionRelease(sess)
|
||||||
if err := sess.Begin(); err != nil {
|
if err := sess.Begin(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := removeTeamMember(sess, orgId, teamId, uid); err != nil {
|
if err := removeTeamMember(sess, orgId, teamId, uid); err != nil {
|
||||||
sess.Rollback()
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ___________ __________
|
||||||
|
// \__ ___/___ _____ _____\______ \ ____ ______ ____
|
||||||
|
// | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \
|
||||||
|
// | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> )
|
||||||
|
// |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/
|
||||||
|
// \/ \/ \/ \/ \/|__|
|
||||||
|
|
||||||
|
// TeamRepo represents an team-repository relation.
|
||||||
|
type TeamRepo struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
OrgID int64 `xorm:"INDEX"`
|
||||||
|
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool {
|
||||||
|
has, _ := e.Where("org_id=?", orgID).And("team_id=?", teamID).And("repo_id=?", repoID).Get(new(TeamRepo))
|
||||||
|
return has
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasTeamRepo returns true if given repository belongs to team.
|
||||||
|
func HasTeamRepo(orgID, teamID, repoID int64) bool {
|
||||||
|
return hasTeamRepo(x, orgID, teamID, repoID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTeamRepo(e Engine, orgID, teamID, repoID int64) error {
|
||||||
|
_, err := e.InsertOne(&TeamRepo{
|
||||||
|
OrgID: orgID,
|
||||||
|
TeamID: teamID,
|
||||||
|
RepoID: repoID,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTeamRepo adds new repository relation to team.
|
||||||
|
func AddTeamRepo(orgID, teamID, repoID int64) error {
|
||||||
|
return addTeamRepo(x, orgID, teamID, repoID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTeamRepo(e Engine, teamID, repoID int64) error {
|
||||||
|
_, err := e.Delete(&TeamRepo{
|
||||||
|
TeamID: teamID,
|
||||||
|
RepoID: repoID,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTeamRepo deletes repository relation to team.
|
||||||
|
func RemoveTeamRepo(teamID, repoID int64) error {
|
||||||
|
return removeTeamRepo(x, teamID, repoID)
|
||||||
|
}
|
||||||
|
134
models/repo.go
134
models/repo.go
@ -514,7 +514,7 @@ func initRepository(e Engine, f string, u *User, repo *Repository, initReadme bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateRepository creates a repository for given user or organization.
|
// CreateRepository creates a repository for given user or organization.
|
||||||
func CreateRepository(u *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) {
|
func CreateRepository(u *User, name, desc, lang, license string, isPrivate, isMirror, initReadme bool) (*Repository, error) {
|
||||||
if !IsLegalName(name) {
|
if !IsLegalName(name) {
|
||||||
return nil, ErrRepoNameIllegal
|
return nil, ErrRepoNameIllegal
|
||||||
}
|
}
|
||||||
@ -532,7 +532,7 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror
|
|||||||
Name: name,
|
Name: name,
|
||||||
LowerName: strings.ToLower(name),
|
LowerName: strings.ToLower(name),
|
||||||
Description: desc,
|
Description: desc,
|
||||||
IsPrivate: private,
|
IsPrivate: isPrivate,
|
||||||
}
|
}
|
||||||
|
|
||||||
sess := x.NewSession()
|
sess := x.NewSession()
|
||||||
@ -558,34 +558,22 @@ func CreateRepository(u *User, name, desc, lang, license string, private, mirror
|
|||||||
// Update owner team info and count.
|
// Update owner team info and count.
|
||||||
t, err := u.getOwnerTeam(sess)
|
t, err := u.getOwnerTeam(sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get owner team: %v", err)
|
return nil, fmt.Errorf("getOwnerTeam: %v", err)
|
||||||
} else if err = t.getMembers(sess); err != nil {
|
} else if err = t.addRepository(sess, repo); err != nil {
|
||||||
return nil, fmt.Errorf("get team members: %v", err)
|
return nil, fmt.Errorf("addRepository: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
for _, u := range t.Members {
|
|
||||||
if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
|
|
||||||
return nil, fmt.Errorf("watch repository: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.RepoIds += "$" + com.ToStr(repo.Id) + "|"
|
|
||||||
t.NumRepos++
|
|
||||||
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
|
if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
|
||||||
return nil, fmt.Errorf("watch repository 2: %v", err)
|
return nil, fmt.Errorf("watchRepo: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = newRepoAction(sess, u, repo); err != nil {
|
if err = newRepoAction(sess, u, repo); err != nil {
|
||||||
return nil, fmt.Errorf("new repository action: %v", err)
|
return nil, fmt.Errorf("newRepoAction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need for init mirror.
|
// No need for init mirror.
|
||||||
if !mirror {
|
if !isMirror {
|
||||||
repoPath := RepoPath(u.Name, repo.Name)
|
repoPath := RepoPath(u.Name, repo.Name)
|
||||||
if err = initRepository(sess, repoPath, u, repo, initReadme, lang, license); err != nil {
|
if err = initRepository(sess, repoPath, u, repo, initReadme, lang, license); err != nil {
|
||||||
if err2 := os.RemoveAll(repoPath); err2 != nil {
|
if err2 := os.RemoveAll(repoPath); err2 != nil {
|
||||||
@ -640,14 +628,14 @@ func RepoPath(userName, repoName string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransferOwnership transfers all corresponding setting from old user to new one.
|
// TransferOwnership transfers all corresponding setting from old user to new one.
|
||||||
func TransferOwnership(u *User, newOwner string, repo *Repository) error {
|
func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||||
newUser, err := GetUserByName(newOwner)
|
newOwner, err := GetUserByName(newOwnerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("fail to get new owner(%s): %v", newOwner, err)
|
return fmt.Errorf("get new owner(%s): %v", newOwnerName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if new owner has repository with same name.
|
// Check if new owner has repository with same name.
|
||||||
has, err := IsRepositoryExist(newUser, repo.Name)
|
has, err := IsRepositoryExist(newOwner, repo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if has {
|
} else if has {
|
||||||
@ -663,8 +651,8 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) error {
|
|||||||
owner := repo.Owner
|
owner := repo.Owner
|
||||||
|
|
||||||
// Update repository.
|
// Update repository.
|
||||||
repo.OwnerId = newUser.Id
|
repo.OwnerId = newOwner.Id
|
||||||
repo.Owner = newUser
|
repo.Owner = newOwner
|
||||||
if _, err := sess.Id(repo.Id).Update(repo); err != nil {
|
if _, err := sess.Id(repo.Id).Update(repo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -675,28 +663,38 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, c := range collaborators {
|
for _, c := range collaborators {
|
||||||
if c.Id == newUser.Id || newUser.IsOrgMember(c.Id) {
|
if c.Id == newOwner.Id || newOwner.IsOrgMember(c.Id) {
|
||||||
if _, err = sess.Delete(&Collaboration{RepoID: repo.Id, UserID: c.Id}); err != nil {
|
if _, err = sess.Delete(&Collaboration{RepoID: repo.Id, UserID: c.Id}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if newOwner.IsOrganization() {
|
||||||
|
// Update owner team info and count.
|
||||||
|
t, err := newOwner.GetOwnerTeam()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if err = t.addRepository(sess, repo); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update user repository number.
|
// Update user repository number.
|
||||||
if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", newUser.Id); err != nil {
|
if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", newOwner.Id); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", owner.Id); err != nil {
|
} else if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", owner.Id); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = repo.recalculateAccesses(sess); err != nil {
|
} else if err = repo.recalculateAccesses(sess); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = watchRepo(sess, newUser.Id, repo.Id, true); err != nil {
|
} else if err = watchRepo(sess, newOwner.Id, repo.Id, true); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if err = transferRepoAction(sess, u, newUser, repo); err != nil {
|
} else if err = transferRepoAction(sess, u, owner, newOwner, repo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change repository directory name.
|
// Change repository directory name.
|
||||||
if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil {
|
if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -734,8 +732,8 @@ func UpdateRepository(repo *Repository) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRepository deletes a repository for a user or organization.
|
// DeleteRepository deletes a repository for a user or organization.
|
||||||
func DeleteRepository(uid, repoId int64, userName string) error {
|
func DeleteRepository(uid, repoID int64, userName string) error {
|
||||||
repo := &Repository{Id: repoId, OwnerId: uid}
|
repo := &Repository{Id: repoID, OwnerId: uid}
|
||||||
has, err := x.Get(repo)
|
has, err := x.Get(repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -761,70 +759,66 @@ func DeleteRepository(uid, repoId int64, userName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if org.IsOrganization() {
|
if org.IsOrganization() {
|
||||||
idStr := "$" + com.ToStr(repoId) + "|"
|
|
||||||
for _, t := range org.Teams {
|
for _, t := range org.Teams {
|
||||||
if !strings.Contains(t.RepoIds, idStr) {
|
if !t.hasRepository(sess, repoID) {
|
||||||
continue
|
continue
|
||||||
}
|
} else if err = t.removeRepository(sess, repo); err != nil {
|
||||||
t.NumRepos--
|
|
||||||
t.RepoIds = strings.Replace(t.RepoIds, idStr, "", 1)
|
|
||||||
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = sess.Delete(&Repository{Id: repoId}); err != nil {
|
if _, err = sess.Delete(&Repository{Id: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err := sess.Delete(&Access{RepoID: repo.Id}); err != nil {
|
} else if _, err := sess.Delete(&Access{RepoID: repo.Id}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil {
|
} else if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Delete(&Watch{RepoId: repoId}); err != nil {
|
} else if _, err = sess.Delete(&Watch{RepoId: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Delete(&Mirror{RepoId: repoId}); err != nil {
|
} else if _, err = sess.Delete(&Mirror{RepoId: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Delete(&IssueUser{RepoId: repoId}); err != nil {
|
} else if _, err = sess.Delete(&IssueUser{RepoId: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Delete(&Milestone{RepoId: repoId}); err != nil {
|
} else if _, err = sess.Delete(&Milestone{RepoId: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Delete(&Release{RepoId: repoId}); err != nil {
|
} else if _, err = sess.Delete(&Release{RepoId: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if _, err = sess.Delete(&Collaboration{RepoID: repoId}); err != nil {
|
} else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete comments.
|
// Delete comments.
|
||||||
if err = x.Iterate(&Issue{RepoId: repoId}, func(idx int, bean interface{}) error {
|
issues := make([]*Issue, 0, 25)
|
||||||
issue := bean.(*Issue)
|
if err = sess.Where("repo_id=?", repoID).Find(&issues); err != nil {
|
||||||
if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for i := range issues {
|
||||||
|
if _, err = sess.Delete(&Comment{IssueId: issues[i].Id}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err = sess.Delete(&Issue{RepoId: repoId}); err != nil {
|
if _, err = sess.Delete(&Issue{RepoId: repoID}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.IsFork {
|
if repo.IsFork {
|
||||||
if _, err = sess.Exec("UPDATE `repository` SET num_forks = num_forks - 1 WHERE id = ?", repo.ForkId); err != nil {
|
if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks-1 WHERE id=?", repo.ForkId); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", uid); err != nil {
|
if _, err = sess.Exec("UPDATE `user` SET num_repos=num_repos-1 WHERE id=?", uid); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove repository files.
|
// Remove repository files.
|
||||||
if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
|
if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
|
||||||
desc := fmt.Sprintf("Fail to delete repository files(%s/%s): %v", userName, repo.Name, err)
|
desc := fmt.Sprintf("delete repository files(%s/%s): %v", userName, repo.Name, err)
|
||||||
log.Warn(desc)
|
log.Warn(desc)
|
||||||
if err = CreateRepositoryNotice(desc); err != nil {
|
if err = CreateRepositoryNotice(desc); err != nil {
|
||||||
log.Error(4, "Fail to add notice: %v", err)
|
log.Error(4, "add notice: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1328,33 +1322,21 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (*Repositor
|
|||||||
// Update owner team info and count.
|
// Update owner team info and count.
|
||||||
t, err := u.getOwnerTeam(sess)
|
t, err := u.getOwnerTeam(sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get owner team: %v", err)
|
return nil, fmt.Errorf("getOwnerTeam: %v", err)
|
||||||
} else if err = t.getMembers(sess); err != nil {
|
} else if err = t.addRepository(sess, repo); err != nil {
|
||||||
return nil, fmt.Errorf("get team members: %v", err)
|
return nil, fmt.Errorf("addRepository: %v", err)
|
||||||
}
|
|
||||||
|
|
||||||
for _, u := range t.Members {
|
|
||||||
if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
|
|
||||||
return nil, fmt.Errorf("watch repository: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.RepoIds += "$" + com.ToStr(repo.Id) + "|"
|
|
||||||
t.NumRepos++
|
|
||||||
if _, err = sess.Id(t.Id).AllCols().Update(t); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
|
if err = watchRepo(sess, u.Id, repo.Id, true); err != nil {
|
||||||
return nil, fmt.Errorf("watch repository 2: %v", err)
|
return nil, fmt.Errorf("watchRepo: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = newRepoAction(sess, u, repo); err != nil {
|
if err = newRepoAction(sess, u, repo); err != nil {
|
||||||
return nil, fmt.Errorf("new repository action: %v", err)
|
return nil, fmt.Errorf("newRepoAction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = sess.Exec("UPDATE `repository` SET num_forks = num_forks + 1 WHERE id = ?", oldRepo.Id); err != nil {
|
if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.Id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,7 +231,7 @@ func (u *User) GetOrganizations() error {
|
|||||||
|
|
||||||
u.Orgs = make([]*User, len(ous))
|
u.Orgs = make([]*User, len(ous))
|
||||||
for i, ou := range ous {
|
for i, ou := range ous {
|
||||||
u.Orgs[i], err = GetUserById(ou.OrgId)
|
u.Orgs[i], err = GetUserById(ou.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -860,7 +860,7 @@ func UpdateMentions(userNames []string, issueId int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, orgUser := range orgUsers {
|
for _, orgUser := range orgUsers {
|
||||||
tempIds = append(tempIds, orgUser.Id)
|
tempIds = append(tempIds, orgUser.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
ids = append(ids, tempIds...)
|
ids = append(ids, tempIds...)
|
||||||
|
@ -181,7 +181,7 @@ func NewTeamPost(ctx *middleware.Context, form auth.CreateTeamForm) {
|
|||||||
org := ctx.Org.Organization
|
org := ctx.Org.Organization
|
||||||
|
|
||||||
t := &models.Team{
|
t := &models.Team{
|
||||||
OrgId: org.Id,
|
OrgID: org.Id,
|
||||||
Name: form.TeamName,
|
Name: form.TeamName,
|
||||||
Description: form.Description,
|
Description: form.Description,
|
||||||
Authorize: auth,
|
Authorize: auth,
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
0.5.14.0222 Beta
|
0.5.15.0223 Beta
|
Loading…
Reference in New Issue
Block a user