mirror of
https://github.com/gogs/gogs.git
synced 2025-05-22 23:40:20 +00:00
refactor(db): migrate ChangeUsername
off user.go
(#7261)
This commit is contained in:
parent
93736cbc12
commit
644a3a9d78
@ -5,6 +5,7 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,11 +25,15 @@ func SetMockAuth(t *testing.T, otps AuthOpts) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mockServer sync.Mutex
|
||||||
|
|
||||||
func SetMockServer(t *testing.T, opts ServerOpts) {
|
func SetMockServer(t *testing.T, opts ServerOpts) {
|
||||||
|
mockServer.Lock()
|
||||||
before := Server
|
before := Server
|
||||||
Server = opts
|
Server = opts
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
Server = before
|
Server = before
|
||||||
|
mockServer.Unlock()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,11 +45,15 @@ func SetMockSSH(t *testing.T, opts SSHOpts) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mockRepository sync.Mutex
|
||||||
|
|
||||||
func SetMockRepository(t *testing.T, opts RepositoryOpts) {
|
func SetMockRepository(t *testing.T, opts RepositoryOpts) {
|
||||||
|
mockRepository.Lock()
|
||||||
before := Repository
|
before := Repository
|
||||||
Repository = opts
|
Repository = opts
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
Repository = before
|
Repository = before
|
||||||
|
mockRepository.Unlock()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
"github.com/unknwon/com"
|
||||||
@ -45,28 +44,28 @@ const (
|
|||||||
|
|
||||||
// PullRequest represents relation between pull request and repositories.
|
// PullRequest represents relation between pull request and repositories.
|
||||||
type PullRequest struct {
|
type PullRequest struct {
|
||||||
ID int64
|
ID int64 `gorm:"primaryKey"`
|
||||||
Type PullRequestType
|
Type PullRequestType
|
||||||
Status PullRequestStatus
|
Status PullRequestStatus
|
||||||
|
|
||||||
IssueID int64 `xorm:"INDEX"`
|
IssueID int64 `xorm:"INDEX" gorm:"index"`
|
||||||
Issue *Issue `xorm:"-" json:"-"`
|
Issue *Issue `xorm:"-" json:"-" gorm:"-"`
|
||||||
Index int64
|
Index int64
|
||||||
|
|
||||||
HeadRepoID int64
|
HeadRepoID int64
|
||||||
HeadRepo *Repository `xorm:"-" json:"-"`
|
HeadRepo *Repository `xorm:"-" json:"-" gorm:"-"`
|
||||||
BaseRepoID int64
|
BaseRepoID int64
|
||||||
BaseRepo *Repository `xorm:"-" json:"-"`
|
BaseRepo *Repository `xorm:"-" json:"-" gorm:"-"`
|
||||||
HeadUserName string
|
HeadUserName string
|
||||||
HeadBranch string
|
HeadBranch string
|
||||||
BaseBranch string
|
BaseBranch string
|
||||||
MergeBase string `xorm:"VARCHAR(40)"`
|
MergeBase string `xorm:"VARCHAR(40)" gorm:"type:VARCHAR(40)"`
|
||||||
|
|
||||||
HasMerged bool
|
HasMerged bool
|
||||||
MergedCommitID string `xorm:"VARCHAR(40)"`
|
MergedCommitID string `xorm:"VARCHAR(40)" gorm:"type:VARCHAR(40)"`
|
||||||
MergerID int64
|
MergerID int64
|
||||||
Merger *User `xorm:"-" json:"-"`
|
Merger *User `xorm:"-" json:"-" gorm:"-"`
|
||||||
Merged time.Time `xorm:"-" json:"-"`
|
Merged time.Time `xorm:"-" json:"-" gorm:"-"`
|
||||||
MergedUnix int64
|
MergedUnix int64
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -823,14 +822,6 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ChangeUsernameInPullRequests(oldUserName, newUserName string) error {
|
|
||||||
pr := PullRequest{
|
|
||||||
HeadUserName: strings.ToLower(newUserName),
|
|
||||||
}
|
|
||||||
_, err := x.Cols("head_user_name").Where("head_user_name = ?", strings.ToLower(oldUserName)).Update(pr)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkAndUpdateStatus checks if pull request is possible to leaving checking status,
|
// checkAndUpdateStatus checks if pull request is possible to leaving checking status,
|
||||||
// and set to be either conflict or mergeable.
|
// and set to be either conflict or mergeable.
|
||||||
func (pr *PullRequest) checkAndUpdateStatus() {
|
func (pr *PullRequest) checkAndUpdateStatus() {
|
||||||
|
@ -1444,7 +1444,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
|||||||
return fmt.Errorf("rename repository directory: %v", err)
|
return fmt.Errorf("rename repository directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRepoLocalCopy(repo)
|
deleteRepoLocalCopy(repo.ID)
|
||||||
|
|
||||||
// Rename remote wiki repository to new path and delete local copy.
|
// Rename remote wiki repository to new path and delete local copy.
|
||||||
wikiPath := WikiPath(owner.Name, repo.Name)
|
wikiPath := WikiPath(owner.Name, repo.Name)
|
||||||
@ -1458,10 +1458,10 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
|||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteRepoLocalCopy(repo *Repository) {
|
func deleteRepoLocalCopy(repoID int64) {
|
||||||
repoWorkingPool.CheckIn(com.ToStr(repo.ID))
|
repoWorkingPool.CheckIn(com.ToStr(repoID))
|
||||||
defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
|
defer repoWorkingPool.CheckOut(com.ToStr(repoID))
|
||||||
RemoveAllWithNotice("Delete repository local copy", repo.LocalCopyPath())
|
RemoveAllWithNotice(fmt.Sprintf("Delete repository %d local copy", repoID), repoutil.RepositoryLocalPath(repoID))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
|
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
|
||||||
@ -1497,7 +1497,7 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error)
|
|||||||
RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath())
|
RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteRepoLocalCopy(repo)
|
deleteRepoLocalCopy(repo.ID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/unknwon/com"
|
|
||||||
log "unknwon.dev/clog/v2"
|
log "unknwon.dev/clog/v2"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
||||||
@ -57,40 +56,6 @@ func (u *User) getOrganizationCount(e Engine) (int64, error) {
|
|||||||
return e.Where("uid=?", u.ID).Count(new(OrgUser))
|
return e.Where("uid=?", u.ID).Count(new(OrgUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeUserName changes all corresponding setting from old user name to new one.
|
|
||||||
func ChangeUserName(u *User, newUserName string) (err error) {
|
|
||||||
if err = isUsernameAllowed(newUserName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if Users.IsUsernameUsed(context.TODO(), newUserName) {
|
|
||||||
return ErrUserAlreadyExist{args: errutil.Args{"name": newUserName}}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = ChangeUsernameInPullRequests(u.Name, newUserName); err != nil {
|
|
||||||
return fmt.Errorf("ChangeUsernameInPullRequests: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all local copies of repositories and wikis the user owns.
|
|
||||||
if err = x.Where("owner_id=?", u.ID).Iterate(new(Repository), func(idx int, bean interface{}) error {
|
|
||||||
repo := bean.(*Repository)
|
|
||||||
deleteRepoLocalCopy(repo)
|
|
||||||
// TODO: By the same reasoning, shouldn't we also sync access to the local wiki path?
|
|
||||||
RemoveAllWithNotice("Delete repository wiki local copy", repo.LocalWikiPath())
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return fmt.Errorf("delete repository and wiki local copy: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rename or create user base directory
|
|
||||||
baseDir := repoutil.UserPath(u.Name)
|
|
||||||
newBaseDir := repoutil.UserPath(newUserName)
|
|
||||||
if com.IsExist(baseDir) {
|
|
||||||
return os.Rename(baseDir, newBaseDir)
|
|
||||||
}
|
|
||||||
return os.MkdirAll(newBaseDir, os.ModePerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUser(e Engine, u *User) error {
|
func updateUser(e Engine, u *User) error {
|
||||||
// Organization does not need email
|
// Organization does not need email
|
||||||
if !u.IsOrganization() {
|
if !u.IsOrganization() {
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"gogs.io/gogs/internal/dbutil"
|
"gogs.io/gogs/internal/dbutil"
|
||||||
"gogs.io/gogs/internal/errutil"
|
"gogs.io/gogs/internal/errutil"
|
||||||
"gogs.io/gogs/internal/osutil"
|
"gogs.io/gogs/internal/osutil"
|
||||||
|
"gogs.io/gogs/internal/repoutil"
|
||||||
"gogs.io/gogs/internal/strutil"
|
"gogs.io/gogs/internal/strutil"
|
||||||
"gogs.io/gogs/internal/tool"
|
"gogs.io/gogs/internal/tool"
|
||||||
"gogs.io/gogs/internal/userutil"
|
"gogs.io/gogs/internal/userutil"
|
||||||
@ -45,11 +46,17 @@ type UsersStore interface {
|
|||||||
// When the "loginSourceID" is positive, it tries to authenticate via given
|
// When the "loginSourceID" is positive, it tries to authenticate via given
|
||||||
// login source and creates a new user when not yet exists in the database.
|
// login source and creates a new user when not yet exists in the database.
|
||||||
Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error)
|
Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error)
|
||||||
|
// ChangeUsername changes the username of the given user and updates all
|
||||||
|
// references to the old username. It returns ErrNameNotAllowed if the given
|
||||||
|
// name or pattern of the name is not allowed as a username, or
|
||||||
|
// ErrUserAlreadyExist when another user with same name already exists.
|
||||||
|
ChangeUsername(ctx context.Context, userID int64, newUsername string) error
|
||||||
// Count returns the total number of users.
|
// Count returns the total number of users.
|
||||||
Count(ctx context.Context) int64
|
Count(ctx context.Context) int64
|
||||||
// Create creates a new user and persists to database. It returns
|
// Create creates a new user and persists to database. It returns
|
||||||
// ErrUserAlreadyExist when a user with same name already exists, or
|
// ErrNameNotAllowed if the given name or pattern of the name is not allowed as
|
||||||
// ErrEmailAlreadyUsed if the email has been used by another user.
|
// a username, or ErrUserAlreadyExist when a user with same name already exists,
|
||||||
|
// or ErrEmailAlreadyUsed if the email has been used by another user.
|
||||||
Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error)
|
Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error)
|
||||||
// DeleteCustomAvatar deletes the current user custom avatar and falls back to
|
// DeleteCustomAvatar deletes the current user custom avatar and falls back to
|
||||||
// use look up avatar by email.
|
// use look up avatar by email.
|
||||||
@ -188,6 +195,81 @@ func (db *users) Authenticate(ctx context.Context, login, password string, login
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *users) ChangeUsername(ctx context.Context, userID int64, newUsername string) error {
|
||||||
|
err := isUsernameAllowed(newUsername)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if db.IsUsernameUsed(ctx, newUsername) {
|
||||||
|
return ErrUserAlreadyExist{
|
||||||
|
args: errutil.Args{
|
||||||
|
"name": newUsername,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := db.GetByID(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "get user")
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Model(&User{}).
|
||||||
|
Where("id = ?", user.ID).
|
||||||
|
Updates(map[string]any{
|
||||||
|
"lower_name": strings.ToLower(newUsername),
|
||||||
|
"name": newUsername,
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "update user name")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all references to the user name in pull requests
|
||||||
|
err = tx.Model(&PullRequest{}).
|
||||||
|
Where("head_user_name = ?", user.LowerName).
|
||||||
|
Update("head_user_name", strings.ToLower(newUsername)).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, `update "pull_request.head_user_name"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete local copies of repositories and their wikis that are owned by the user
|
||||||
|
rows, err := tx.Model(&Repository{}).Where("owner_id = ?", user.ID).Rows()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "iterate repositories")
|
||||||
|
}
|
||||||
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var repo struct {
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
err = tx.ScanRows(rows, &repo)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "scan rows")
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRepoLocalCopy(repo.ID)
|
||||||
|
RemoveAllWithNotice(fmt.Sprintf("Delete repository %d wiki local copy", repo.ID), repoutil.RepositoryLocalWikiPath(repo.ID))
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return errors.Wrap(err, "check rows.Err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename user directory if exists
|
||||||
|
userPath := repoutil.UserPath(user.Name)
|
||||||
|
if osutil.IsExist(userPath) {
|
||||||
|
newUserPath := repoutil.UserPath(newUsername)
|
||||||
|
err = os.Rename(userPath, newUserPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "rename user directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (db *users) Count(ctx context.Context) int64 {
|
func (db *users) Count(ctx context.Context) int64 {
|
||||||
var count int64
|
var count int64
|
||||||
db.WithContext(ctx).Model(&User{}).Where("type = ?", UserTypeIndividual).Count(&count)
|
db.WithContext(ctx).Model(&User{}).Where("type = ?", UserTypeIndividual).Count(&count)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -17,10 +18,12 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"gogs.io/gogs/internal/auth"
|
"gogs.io/gogs/internal/auth"
|
||||||
|
"gogs.io/gogs/internal/conf"
|
||||||
"gogs.io/gogs/internal/dbtest"
|
"gogs.io/gogs/internal/dbtest"
|
||||||
"gogs.io/gogs/internal/dbutil"
|
"gogs.io/gogs/internal/dbutil"
|
||||||
"gogs.io/gogs/internal/errutil"
|
"gogs.io/gogs/internal/errutil"
|
||||||
"gogs.io/gogs/internal/osutil"
|
"gogs.io/gogs/internal/osutil"
|
||||||
|
"gogs.io/gogs/internal/repoutil"
|
||||||
"gogs.io/gogs/internal/userutil"
|
"gogs.io/gogs/internal/userutil"
|
||||||
"gogs.io/gogs/public"
|
"gogs.io/gogs/public"
|
||||||
)
|
)
|
||||||
@ -79,7 +82,7 @@ func TestUsers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tables := []interface{}{new(User), new(EmailAddress), new(Repository), new(Follow)}
|
tables := []interface{}{new(User), new(EmailAddress), new(Repository), new(Follow), new(PullRequest)}
|
||||||
db := &users{
|
db := &users{
|
||||||
DB: dbtest.NewDB(t, "users", tables...),
|
DB: dbtest.NewDB(t, "users", tables...),
|
||||||
}
|
}
|
||||||
@ -89,6 +92,7 @@ func TestUsers(t *testing.T) {
|
|||||||
test func(t *testing.T, db *users)
|
test func(t *testing.T, db *users)
|
||||||
}{
|
}{
|
||||||
{"Authenticate", usersAuthenticate},
|
{"Authenticate", usersAuthenticate},
|
||||||
|
{"ChangeUsername", usersChangeUsername},
|
||||||
{"Count", usersCount},
|
{"Count", usersCount},
|
||||||
{"Create", usersCreate},
|
{"Create", usersCreate},
|
||||||
{"DeleteCustomAvatar", usersDeleteCustomAvatar},
|
{"DeleteCustomAvatar", usersDeleteCustomAvatar},
|
||||||
@ -212,6 +216,107 @@ func usersAuthenticate(t *testing.T, db *users) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func usersChangeUsername(t *testing.T, db *users) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
alice, err := db.Create(
|
||||||
|
ctx,
|
||||||
|
"alice",
|
||||||
|
"alice@example.com",
|
||||||
|
CreateUserOptions{
|
||||||
|
Activated: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("name not allowed", func(t *testing.T) {
|
||||||
|
err := db.ChangeUsername(ctx, alice.ID, "-")
|
||||||
|
wantErr := ErrNameNotAllowed{
|
||||||
|
args: errutil.Args{
|
||||||
|
"reason": "reserved",
|
||||||
|
"name": "-",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, wantErr, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("name already exists", func(t *testing.T) {
|
||||||
|
err := db.ChangeUsername(ctx, alice.ID, alice.Name)
|
||||||
|
wantErr := ErrUserAlreadyExist{
|
||||||
|
args: errutil.Args{
|
||||||
|
"name": alice.Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.Equal(t, wantErr, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
tempRepositoryRoot := filepath.Join(os.TempDir(), "usersChangeUsername-tempRepositoryRoot")
|
||||||
|
conf.SetMockRepository(
|
||||||
|
t,
|
||||||
|
conf.RepositoryOpts{
|
||||||
|
Root: tempRepositoryRoot,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
err = os.RemoveAll(tempRepositoryRoot)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() { _ = os.RemoveAll(tempRepositoryRoot) }()
|
||||||
|
|
||||||
|
tempServerAppDataPath := filepath.Join(os.TempDir(), "usersChangeUsername-tempServerAppDataPath")
|
||||||
|
conf.SetMockServer(
|
||||||
|
t,
|
||||||
|
conf.ServerOpts{
|
||||||
|
AppDataPath: tempServerAppDataPath,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
err = os.RemoveAll(tempServerAppDataPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() { _ = os.RemoveAll(tempServerAppDataPath) }()
|
||||||
|
|
||||||
|
repo, err := NewReposStore(db.DB).Create(
|
||||||
|
ctx,
|
||||||
|
alice.ID,
|
||||||
|
CreateRepoOptions{
|
||||||
|
Name: "test-repo-1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// TODO: Use PullRequests.Create to replace SQL hack when the method is available.
|
||||||
|
err = db.Exec(`INSERT INTO pull_request (head_user_name) VALUES (?)`, alice.Name).Error
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = os.MkdirAll(repoutil.UserPath(alice.Name), os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = os.MkdirAll(repoutil.RepositoryLocalPath(repo.ID), os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = os.MkdirAll(repoutil.RepositoryLocalWikiPath(repo.ID), os.ModePerm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make sure mock data is set up correctly
|
||||||
|
assert.True(t, osutil.IsExist(repoutil.UserPath(alice.Name)))
|
||||||
|
assert.True(t, osutil.IsExist(repoutil.RepositoryLocalPath(repo.ID)))
|
||||||
|
assert.True(t, osutil.IsExist(repoutil.RepositoryLocalWikiPath(repo.ID)))
|
||||||
|
|
||||||
|
const newUsername = "alice-new"
|
||||||
|
err = db.ChangeUsername(ctx, alice.ID, newUsername)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// TODO: Use PullRequests.GetByID to replace SQL hack when the method is available.
|
||||||
|
var headUserName string
|
||||||
|
err = db.Select("head_user_name").Table("pull_request").Row().Scan(&headUserName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, headUserName, newUsername)
|
||||||
|
|
||||||
|
assert.True(t, osutil.IsExist(repoutil.UserPath(newUsername)))
|
||||||
|
assert.False(t, osutil.IsExist(repoutil.UserPath(alice.Name)))
|
||||||
|
assert.False(t, osutil.IsExist(repoutil.RepositoryLocalPath(repo.ID)))
|
||||||
|
assert.False(t, osutil.IsExist(repoutil.RepositoryLocalWikiPath(repo.ID)))
|
||||||
|
|
||||||
|
alice, err = db.GetByID(ctx, alice.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, newUsername, alice.Name)
|
||||||
|
}
|
||||||
|
|
||||||
func usersCount(t *testing.T, db *users) {
|
func usersCount(t *testing.T, db *users) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ package repoutil
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gogs.io/gogs/internal/conf"
|
"gogs.io/gogs/internal/conf"
|
||||||
@ -60,3 +61,15 @@ func UserPath(user string) string {
|
|||||||
func RepositoryPath(owner, repo string) string {
|
func RepositoryPath(owner, repo string) string {
|
||||||
return filepath.Join(UserPath(owner), strings.ToLower(repo)+".git")
|
return filepath.Join(UserPath(owner), strings.ToLower(repo)+".git")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RepositoryLocalPath returns the absolute path of the repository local copy
|
||||||
|
// with the given ID.
|
||||||
|
func RepositoryLocalPath(repoID int64) string {
|
||||||
|
return filepath.Join(conf.Server.AppDataPath, "tmp", "local-repo", strconv.FormatInt(repoID, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepositoryLocalWikiPath returns the absolute path of the repository local
|
||||||
|
// wiki copy with the given ID.
|
||||||
|
func RepositoryLocalWikiPath(repoID int64) string {
|
||||||
|
return filepath.Join(conf.Server.AppDataPath, "tmp", "local-wiki", strconv.FormatInt(repoID, 10))
|
||||||
|
}
|
||||||
|
@ -125,3 +125,39 @@ func TestRepositoryPath(t *testing.T) {
|
|||||||
want := "/home/git/gogs-repositories/alice/example.git"
|
want := "/home/git/gogs-repositories/alice/example.git"
|
||||||
assert.Equal(t, want, got)
|
assert.Equal(t, want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRepositoryLocalPath(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("Skipping testing on Windows")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.SetMockServer(
|
||||||
|
t,
|
||||||
|
conf.ServerOpts{
|
||||||
|
AppDataPath: "data",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
got := RepositoryLocalPath(1)
|
||||||
|
want := "data/tmp/local-repo/1"
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRepositoryLocalWikiPath(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("Skipping testing on Windows")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.SetMockServer(
|
||||||
|
t,
|
||||||
|
conf.ServerOpts{
|
||||||
|
AppDataPath: "data",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
got := RepositoryLocalWikiPath(1)
|
||||||
|
want := "data/tmp/local-wiki/1"
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
}
|
||||||
|
@ -2295,6 +2295,9 @@ type MockUsersStore struct {
|
|||||||
// AuthenticateFunc is an instance of a mock function object controlling
|
// AuthenticateFunc is an instance of a mock function object controlling
|
||||||
// the behavior of the method Authenticate.
|
// the behavior of the method Authenticate.
|
||||||
AuthenticateFunc *UsersStoreAuthenticateFunc
|
AuthenticateFunc *UsersStoreAuthenticateFunc
|
||||||
|
// ChangeUsernameFunc is an instance of a mock function object
|
||||||
|
// controlling the behavior of the method ChangeUsername.
|
||||||
|
ChangeUsernameFunc *UsersStoreChangeUsernameFunc
|
||||||
// CountFunc is an instance of a mock function object controlling the
|
// CountFunc is an instance of a mock function object controlling the
|
||||||
// behavior of the method Count.
|
// behavior of the method Count.
|
||||||
CountFunc *UsersStoreCountFunc
|
CountFunc *UsersStoreCountFunc
|
||||||
@ -2342,6 +2345,11 @@ func NewMockUsersStore() *MockUsersStore {
|
|||||||
return
|
return
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ChangeUsernameFunc: &UsersStoreChangeUsernameFunc{
|
||||||
|
defaultHook: func(context.Context, int64, string) (r0 error) {
|
||||||
|
return
|
||||||
|
},
|
||||||
|
},
|
||||||
CountFunc: &UsersStoreCountFunc{
|
CountFunc: &UsersStoreCountFunc{
|
||||||
defaultHook: func(context.Context) (r0 int64) {
|
defaultHook: func(context.Context) (r0 int64) {
|
||||||
return
|
return
|
||||||
@ -2414,6 +2422,11 @@ func NewStrictMockUsersStore() *MockUsersStore {
|
|||||||
panic("unexpected invocation of MockUsersStore.Authenticate")
|
panic("unexpected invocation of MockUsersStore.Authenticate")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ChangeUsernameFunc: &UsersStoreChangeUsernameFunc{
|
||||||
|
defaultHook: func(context.Context, int64, string) error {
|
||||||
|
panic("unexpected invocation of MockUsersStore.ChangeUsername")
|
||||||
|
},
|
||||||
|
},
|
||||||
CountFunc: &UsersStoreCountFunc{
|
CountFunc: &UsersStoreCountFunc{
|
||||||
defaultHook: func(context.Context) int64 {
|
defaultHook: func(context.Context) int64 {
|
||||||
panic("unexpected invocation of MockUsersStore.Count")
|
panic("unexpected invocation of MockUsersStore.Count")
|
||||||
@ -2484,6 +2497,9 @@ func NewMockUsersStoreFrom(i db.UsersStore) *MockUsersStore {
|
|||||||
AuthenticateFunc: &UsersStoreAuthenticateFunc{
|
AuthenticateFunc: &UsersStoreAuthenticateFunc{
|
||||||
defaultHook: i.Authenticate,
|
defaultHook: i.Authenticate,
|
||||||
},
|
},
|
||||||
|
ChangeUsernameFunc: &UsersStoreChangeUsernameFunc{
|
||||||
|
defaultHook: i.ChangeUsername,
|
||||||
|
},
|
||||||
CountFunc: &UsersStoreCountFunc{
|
CountFunc: &UsersStoreCountFunc{
|
||||||
defaultHook: i.Count,
|
defaultHook: i.Count,
|
||||||
},
|
},
|
||||||
@ -2637,6 +2653,114 @@ func (c UsersStoreAuthenticateFuncCall) Results() []interface{} {
|
|||||||
return []interface{}{c.Result0, c.Result1}
|
return []interface{}{c.Result0, c.Result1}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsersStoreChangeUsernameFunc describes the behavior when the
|
||||||
|
// ChangeUsername method of the parent MockUsersStore instance is invoked.
|
||||||
|
type UsersStoreChangeUsernameFunc struct {
|
||||||
|
defaultHook func(context.Context, int64, string) error
|
||||||
|
hooks []func(context.Context, int64, string) error
|
||||||
|
history []UsersStoreChangeUsernameFuncCall
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeUsername delegates to the next hook function in the queue and
|
||||||
|
// stores the parameter and result values of this invocation.
|
||||||
|
func (m *MockUsersStore) ChangeUsername(v0 context.Context, v1 int64, v2 string) error {
|
||||||
|
r0 := m.ChangeUsernameFunc.nextHook()(v0, v1, v2)
|
||||||
|
m.ChangeUsernameFunc.appendCall(UsersStoreChangeUsernameFuncCall{v0, v1, v2, r0})
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultHook sets function that is called when the ChangeUsername
|
||||||
|
// method of the parent MockUsersStore instance is invoked and the hook
|
||||||
|
// queue is empty.
|
||||||
|
func (f *UsersStoreChangeUsernameFunc) SetDefaultHook(hook func(context.Context, int64, string) error) {
|
||||||
|
f.defaultHook = hook
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||||
|
// ChangeUsername method of the parent MockUsersStore instance invokes the
|
||||||
|
// hook at the front of the queue and discards it. After the queue is empty,
|
||||||
|
// the default hook function is invoked for any future action.
|
||||||
|
func (f *UsersStoreChangeUsernameFunc) PushHook(hook func(context.Context, int64, string) error) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.hooks = append(f.hooks, hook)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||||
|
// given values.
|
||||||
|
func (f *UsersStoreChangeUsernameFunc) SetDefaultReturn(r0 error) {
|
||||||
|
f.SetDefaultHook(func(context.Context, int64, string) error {
|
||||||
|
return r0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushReturn calls PushHook with a function that returns the given values.
|
||||||
|
func (f *UsersStoreChangeUsernameFunc) PushReturn(r0 error) {
|
||||||
|
f.PushHook(func(context.Context, int64, string) error {
|
||||||
|
return r0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UsersStoreChangeUsernameFunc) nextHook() func(context.Context, int64, string) error {
|
||||||
|
f.mutex.Lock()
|
||||||
|
defer f.mutex.Unlock()
|
||||||
|
|
||||||
|
if len(f.hooks) == 0 {
|
||||||
|
return f.defaultHook
|
||||||
|
}
|
||||||
|
|
||||||
|
hook := f.hooks[0]
|
||||||
|
f.hooks = f.hooks[1:]
|
||||||
|
return hook
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *UsersStoreChangeUsernameFunc) appendCall(r0 UsersStoreChangeUsernameFuncCall) {
|
||||||
|
f.mutex.Lock()
|
||||||
|
f.history = append(f.history, r0)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// History returns a sequence of UsersStoreChangeUsernameFuncCall objects
|
||||||
|
// describing the invocations of this function.
|
||||||
|
func (f *UsersStoreChangeUsernameFunc) History() []UsersStoreChangeUsernameFuncCall {
|
||||||
|
f.mutex.Lock()
|
||||||
|
history := make([]UsersStoreChangeUsernameFuncCall, len(f.history))
|
||||||
|
copy(history, f.history)
|
||||||
|
f.mutex.Unlock()
|
||||||
|
|
||||||
|
return history
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsersStoreChangeUsernameFuncCall is an object that describes an
|
||||||
|
// invocation of method ChangeUsername on an instance of MockUsersStore.
|
||||||
|
type UsersStoreChangeUsernameFuncCall struct {
|
||||||
|
// Arg0 is the value of the 1st argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg0 context.Context
|
||||||
|
// Arg1 is the value of the 2nd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg1 int64
|
||||||
|
// Arg2 is the value of the 3rd argument passed to this method
|
||||||
|
// invocation.
|
||||||
|
Arg2 string
|
||||||
|
// Result0 is the value of the 1st result returned from this method
|
||||||
|
// invocation.
|
||||||
|
Result0 error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns an interface slice containing the arguments of this
|
||||||
|
// invocation.
|
||||||
|
func (c UsersStoreChangeUsernameFuncCall) Args() []interface{} {
|
||||||
|
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Results returns an interface slice containing the results of this
|
||||||
|
// invocation.
|
||||||
|
func (c UsersStoreChangeUsernameFuncCall) Results() []interface{} {
|
||||||
|
return []interface{}{c.Result0}
|
||||||
|
}
|
||||||
|
|
||||||
// UsersStoreCountFunc describes the behavior when the Count method of the
|
// UsersStoreCountFunc describes the behavior when the Count method of the
|
||||||
// parent MockUsersStore instance is invoked.
|
// parent MockUsersStore instance is invoked.
|
||||||
type UsersStoreCountFunc struct {
|
type UsersStoreCountFunc struct {
|
||||||
|
@ -7,6 +7,7 @@ package org
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
log "unknwon.dev/clog/v2"
|
log "unknwon.dev/clog/v2"
|
||||||
|
|
||||||
"gogs.io/gogs/internal/auth"
|
"gogs.io/gogs/internal/auth"
|
||||||
@ -45,13 +46,13 @@ func SettingsPost(c *context.Context, f form.UpdateOrgSetting) {
|
|||||||
c.Data["OrgName"] = true
|
c.Data["OrgName"] = true
|
||||||
c.RenderWithErr(c.Tr("form.username_been_taken"), SETTINGS_OPTIONS, &f)
|
c.RenderWithErr(c.Tr("form.username_been_taken"), SETTINGS_OPTIONS, &f)
|
||||||
return
|
return
|
||||||
} else if err := db.ChangeUserName(org, f.Name); err != nil {
|
} else if err := db.Users.ChangeUsername(c.Req.Context(), org.ID, f.Name); err != nil {
|
||||||
c.Data["OrgName"] = true
|
c.Data["OrgName"] = true
|
||||||
switch {
|
switch {
|
||||||
case db.IsErrNameNotAllowed(err):
|
case db.IsErrNameNotAllowed(errors.Cause(err)):
|
||||||
c.RenderWithErr(c.Tr("user.form.name_not_allowed", err.(db.ErrNameNotAllowed).Value()), SETTINGS_OPTIONS, &f)
|
c.RenderWithErr(c.Tr("user.form.name_not_allowed", err.(db.ErrNameNotAllowed).Value()), SETTINGS_OPTIONS, &f)
|
||||||
default:
|
default:
|
||||||
c.Error(err, "change user name")
|
c.Error(err, "change organization name")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -71,13 +71,13 @@ func SettingsPost(c *context.Context, f form.UpdateProfile) {
|
|||||||
if c.User.IsLocal() {
|
if c.User.IsLocal() {
|
||||||
// Check if username characters have been changed
|
// Check if username characters have been changed
|
||||||
if c.User.LowerName != strings.ToLower(f.Name) {
|
if c.User.LowerName != strings.ToLower(f.Name) {
|
||||||
if err := db.ChangeUserName(c.User, f.Name); err != nil {
|
if err := db.Users.ChangeUsername(c.Req.Context(), c.User.ID, f.Name); err != nil {
|
||||||
c.FormErr("Name")
|
c.FormErr("Name")
|
||||||
var msg string
|
var msg string
|
||||||
switch {
|
switch {
|
||||||
case db.IsErrUserAlreadyExist(err):
|
case db.IsErrUserAlreadyExist(errors.Cause(err)):
|
||||||
msg = c.Tr("form.username_been_taken")
|
msg = c.Tr("form.username_been_taken")
|
||||||
case db.IsErrNameNotAllowed(err):
|
case db.IsErrNameNotAllowed(errors.Cause(err)):
|
||||||
msg = c.Tr("user.form.name_not_allowed", err.(db.ErrNameNotAllowed).Value())
|
msg = c.Tr("user.form.name_not_allowed", err.(db.ErrNameNotAllowed).Value())
|
||||||
default:
|
default:
|
||||||
c.Error(err, "change user name")
|
c.Error(err, "change user name")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user