From 8d43893af2d30e9e1b34cb37ded69278ea6c9726 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 01:15:23 +0000 Subject: [PATCH] Migrate internal/database/ssh_key.go from XORM to GORM Co-authored-by: unknwon <2946214+unknwon@users.noreply.github.com> --- internal/database/ssh_key.go | 268 +++++++++++++++++------------------ 1 file changed, 131 insertions(+), 137 deletions(-) diff --git a/internal/database/ssh_key.go b/internal/database/ssh_key.go index b03a013d1..662c4c5de 100644 --- a/internal/database/ssh_key.go +++ b/internal/database/ssh_key.go @@ -16,8 +16,8 @@ import ( "github.com/cockroachdb/errors" "github.com/unknwon/com" "golang.org/x/crypto/ssh" + "gorm.io/gorm" log "unknwon.dev/clog/v2" - "xorm.io/xorm" "gogs.io/gogs/internal/conf" "gogs.io/gogs/internal/errutil" @@ -40,38 +40,41 @@ const ( // PublicKey represents a user or deploy SSH public key. type PublicKey struct { ID int64 `gorm:"primaryKey"` - OwnerID int64 `xorm:"INDEX NOT NULL" gorm:"index;not null"` - Name string `xorm:"NOT NULL" gorm:"not null"` - Fingerprint string `xorm:"NOT NULL" gorm:"not null"` - Content string `xorm:"TEXT NOT NULL" gorm:"type:TEXT;not null"` - Mode AccessMode `xorm:"NOT NULL DEFAULT 2" gorm:"not null;default:2"` - Type KeyType `xorm:"NOT NULL DEFAULT 1" gorm:"not null;default:1"` + OwnerID int64 `gorm:"index;not null"` + Name string `gorm:"not null"` + Fingerprint string `gorm:"not null"` + Content string `gorm:"type:text;not null"` + Mode AccessMode `gorm:"not null;default:2"` + Type KeyType `gorm:"not null;default:1"` - Created time.Time `xorm:"-" json:"-" gorm:"-"` + Created time.Time `gorm:"-" json:"-"` CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-" gorm:"-"` // Note: Updated must below Created for AfterSet. + Updated time.Time `gorm:"-" json:"-"` // Note: Updated must below Created for AfterFind. UpdatedUnix int64 - HasRecentActivity bool `xorm:"-" json:"-" gorm:"-"` - HasUsed bool `xorm:"-" json:"-" gorm:"-"` + HasRecentActivity bool `gorm:"-" json:"-"` + HasUsed bool `gorm:"-" json:"-"` } -func (k *PublicKey) BeforeInsert() { - k.CreatedUnix = time.Now().Unix() +func (k *PublicKey) BeforeCreate(tx *gorm.DB) error { + if k.CreatedUnix == 0 { + k.CreatedUnix = tx.NowFunc().Unix() + } + return nil } -func (k *PublicKey) BeforeUpdate() { - k.UpdatedUnix = time.Now().Unix() +func (k *PublicKey) BeforeUpdate(tx *gorm.DB) error { + k.UpdatedUnix = tx.NowFunc().Unix() + return nil } -func (k *PublicKey) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - k.Created = time.Unix(k.CreatedUnix, 0).Local() - case "updated_unix": +func (k *PublicKey) AfterFind(tx *gorm.DB) error { + k.Created = time.Unix(k.CreatedUnix, 0).Local() + if k.UpdatedUnix > 0 { k.Updated = time.Unix(k.UpdatedUnix, 0).Local() k.HasUsed = k.Updated.After(k.Created) - k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now()) + k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc()) } + return nil } // OmitEmail returns content of public key without email address. @@ -356,19 +359,16 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error { // checkKeyContent onlys checks if key content has been used as public key, // it is OK to use same key as deploy key for multiple repositories/users. func checkKeyContent(content string) error { - has, err := x.Get(&PublicKey{ - Content: content, - Type: KeyTypeUser, - }) - if err != nil { - return err - } else if has { + err := db.Where("content = ? AND type = ?", content, KeyTypeUser).First(&PublicKey{}).Error + if err == nil { return ErrKeyAlreadyExist{0, content} + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return err } return nil } -func addKey(e Engine, key *PublicKey) (err error) { +func addKey(tx *gorm.DB, key *PublicKey) (err error) { // Calculate fingerprint. tmpPath := strings.ReplaceAll(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()), "id_rsa.pub"), "\\", "/") _ = os.MkdirAll(path.Dir(tmpPath), os.ModePerm) @@ -385,7 +385,7 @@ func addKey(e Engine, key *PublicKey) (err error) { key.Fingerprint = strings.Split(stdout, " ")[1] // Save SSH key. - if _, err = e.Insert(key); err != nil { + if err = tx.Create(key).Error; err != nil { return err } @@ -404,16 +404,10 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { } // Key name of same user cannot be duplicated. - has, err := x.Where("owner_id = ? AND name = ?", ownerID, name).Get(new(PublicKey)) - if err != nil { - return nil, err - } else if has { + err := db.Where("owner_id = ? AND name = ?", ownerID, name).First(new(PublicKey)).Error + if err == nil { return nil, ErrKeyNameAlreadyUsed{ownerID, name} - } - - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { + } else if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } @@ -424,21 +418,25 @@ func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) { Mode: AccessModeWrite, Type: KeyTypeUser, } - if err = addKey(sess, key); err != nil { + err = db.Transaction(func(tx *gorm.DB) error { + return addKey(tx, key) + }) + if err != nil { return nil, errors.Newf("addKey: %v", err) } - return key, sess.Commit() + return key, nil } // GetPublicKeyByID returns public key by given ID. func GetPublicKeyByID(keyID int64) (*PublicKey, error) { key := new(PublicKey) - has, err := x.Id(keyID).Get(key) + err := db.Where("id = ?", keyID).First(key).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrKeyNotExist{keyID} + } return nil, err - } else if !has { - return nil, ErrKeyNotExist{keyID} } return key, nil } @@ -448,11 +446,12 @@ func GetPublicKeyByID(keyID int64) (*PublicKey, error) { // exists. func SearchPublicKeyByContent(content string) (*PublicKey, error) { key := new(PublicKey) - has, err := x.Where("content like ?", content+"%").Get(key) + err := db.Where("content LIKE ?", content+"%").First(key).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrKeyNotExist{} + } return nil, err - } else if !has { - return nil, ErrKeyNotExist{} } return key, nil } @@ -460,23 +459,21 @@ func SearchPublicKeyByContent(content string) (*PublicKey, error) { // ListPublicKeys returns a list of public keys belongs to given user. func ListPublicKeys(uid int64) ([]*PublicKey, error) { keys := make([]*PublicKey, 0, 5) - return keys, x.Where("owner_id = ?", uid).Find(&keys) + return keys, db.Where("owner_id = ?", uid).Find(&keys).Error } // UpdatePublicKey updates given public key. func UpdatePublicKey(key *PublicKey) error { - _, err := x.Id(key.ID).AllCols().Update(key) - return err + return db.Model(key).Where("id = ?", key.ID).Updates(key).Error } // deletePublicKeys does the actual key deletion but does not update authorized_keys file. -func deletePublicKeys(e *xorm.Session, keyIDs ...int64) error { +func deletePublicKeys(tx *gorm.DB, keyIDs ...int64) error { if len(keyIDs) == 0 { return nil } - _, err := e.In("id", keyIDs).Delete(new(PublicKey)) - return err + return tx.Where("id IN ?", keyIDs).Delete(new(PublicKey)).Error } // DeletePublicKey deletes SSH key information both in database and authorized_keys file. @@ -494,17 +491,10 @@ func DeletePublicKey(doer *User, id int64) (err error) { return ErrKeyAccessDenied{doer.ID, key.ID, "public"} } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } - - if err = deletePublicKeys(sess, id); err != nil { - return err - } - - if err = sess.Commit(); err != nil { + err = db.Transaction(func(tx *gorm.DB) error { + return deletePublicKeys(tx, id) + }) + if err != nil { return err } @@ -562,37 +552,40 @@ func RewriteAuthorizedKeys() error { // DeployKey represents deploy key information and its relation with repository. type DeployKey struct { ID int64 - KeyID int64 `xorm:"UNIQUE(s) INDEX"` - RepoID int64 `xorm:"UNIQUE(s) INDEX"` + KeyID int64 `gorm:"uniqueIndex:s;index"` + RepoID int64 `gorm:"uniqueIndex:s;index"` Name string Fingerprint string - Content string `xorm:"-" json:"-" gorm:"-"` + Content string `gorm:"-" json:"-"` - Created time.Time `xorm:"-" json:"-" gorm:"-"` + Created time.Time `gorm:"-" json:"-"` CreatedUnix int64 - Updated time.Time `xorm:"-" json:"-" gorm:"-"` // Note: Updated must below Created for AfterSet. + Updated time.Time `gorm:"-" json:"-"` // Note: Updated must below Created for AfterFind. UpdatedUnix int64 - HasRecentActivity bool `xorm:"-" json:"-" gorm:"-"` - HasUsed bool `xorm:"-" json:"-" gorm:"-"` + HasRecentActivity bool `gorm:"-" json:"-"` + HasUsed bool `gorm:"-" json:"-"` } -func (k *DeployKey) BeforeInsert() { - k.CreatedUnix = time.Now().Unix() +func (k *DeployKey) BeforeCreate(tx *gorm.DB) error { + if k.CreatedUnix == 0 { + k.CreatedUnix = tx.NowFunc().Unix() + } + return nil } -func (k *DeployKey) BeforeUpdate() { - k.UpdatedUnix = time.Now().Unix() +func (k *DeployKey) BeforeUpdate(tx *gorm.DB) error { + k.UpdatedUnix = tx.NowFunc().Unix() + return nil } -func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) { - switch colName { - case "created_unix": - k.Created = time.Unix(k.CreatedUnix, 0).Local() - case "updated_unix": +func (k *DeployKey) AfterFind(tx *gorm.DB) error { + k.Created = time.Unix(k.CreatedUnix, 0).Local() + if k.UpdatedUnix > 0 { k.Updated = time.Unix(k.UpdatedUnix, 0).Local() k.HasUsed = k.Updated.After(k.Created) - k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(time.Now()) + k.HasRecentActivity = k.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc()) } + return nil } // GetContent gets associated public key content. @@ -605,28 +598,28 @@ func (k *DeployKey) GetContent() error { return nil } -func checkDeployKey(e Engine, keyID, repoID int64, name string) error { +func checkDeployKey(tx *gorm.DB, keyID, repoID int64, name string) error { // Note: We want error detail, not just true or false here. - has, err := e.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey)) - if err != nil { - return err - } else if has { + err := tx.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(new(DeployKey)).Error + if err == nil { return ErrDeployKeyAlreadyExist{keyID, repoID} + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return err } - has, err = e.Where("repo_id = ? AND name = ?", repoID, name).Get(new(DeployKey)) - if err != nil { - return err - } else if has { + err = tx.Where("repo_id = ? AND name = ?", repoID, name).First(new(DeployKey)).Error + if err == nil { return ErrDeployKeyNameAlreadyUsed{repoID, name} + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return err } return nil } // addDeployKey adds new key-repo relation. -func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) { - if err := checkDeployKey(e, keyID, repoID, name); err != nil { +func addDeployKey(tx *gorm.DB, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) { + if err := checkDeployKey(tx, keyID, repoID, name); err != nil { return nil, err } @@ -636,14 +629,14 @@ func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string Name: name, Fingerprint: fingerprint, } - _, err := e.Insert(key) + err := tx.Create(key).Error return key, err } // HasDeployKey returns true if public key is a deploy key of given repository. func HasDeployKey(keyID, repoID int64) bool { - has, _ := x.Where("key_id = ? AND repo_id = ?", keyID, repoID).Get(new(DeployKey)) - return has + err := db.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(new(DeployKey)).Error + return err == nil } // AddDeployKey add new deploy key to database and authorized_keys file. @@ -657,30 +650,34 @@ func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) { Mode: AccessModeRead, Type: KeyTypeDeploy, } - has, err := x.Get(pkey) - if err != nil { + err := db.Where("content = ? AND mode = ? AND type = ?", content, AccessModeRead, KeyTypeDeploy).First(pkey).Error + has := err == nil + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return nil, err - } - - // First time use this deploy key. - if !has { - if err = addKey(sess, pkey); err != nil { - return nil, errors.Newf("addKey: %v", err) + var key *DeployKey + err = db.Transaction(func(tx *gorm.DB) error { + // First time use this deploy key. + if !has { + if err := addKey(tx, pkey); err != nil { + return errors.Newf("addKey: %v", err) + } } - } - key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint) + var err error + key, err = addDeployKey(tx, pkey.ID, repoID, name, pkey.Fingerprint) + if err != nil { + return errors.Newf("addDeployKey: %v", err) + } + + return nil + }) if err != nil { - return nil, errors.Newf("addDeployKey: %v", err) + return nil, err } - return key, sess.Commit() + return key, nil } var _ errutil.NotFound = (*ErrDeployKeyNotExist)(nil) @@ -705,11 +702,12 @@ func (ErrDeployKeyNotExist) NotFound() bool { // GetDeployKeyByID returns deploy key by given ID. func GetDeployKeyByID(id int64) (*DeployKey, error) { key := new(DeployKey) - has, err := x.Id(id).Get(key) + err := db.Where("id = ?", id).First(key).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrDeployKeyNotExist{args: map[string]any{"deployKeyID": id}} + } return nil, err - } else if !has { - return nil, ErrDeployKeyNotExist{args: map[string]any{"deployKeyID": id}} } return key, nil } @@ -720,19 +718,19 @@ func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) { KeyID: keyID, RepoID: repoID, } - has, err := x.Get(key) + err := db.Where("key_id = ? AND repo_id = ?", keyID, repoID).First(key).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, ErrDeployKeyNotExist{args: map[string]any{"keyID": keyID, "repoID": repoID}} + } return nil, err - } else if !has { - return nil, ErrDeployKeyNotExist{args: map[string]any{"keyID": keyID, "repoID": repoID}} } return key, nil } // UpdateDeployKey updates deploy key information. func UpdateDeployKey(key *DeployKey) error { - _, err := x.Id(key.ID).AllCols().Update(key) - return err + return db.Model(key).Where("id = ?", key.ID).Updates(key).Error } // DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed. @@ -761,31 +759,27 @@ func DeleteDeployKey(doer *User, id int64) error { } } - sess := x.NewSession() - defer sess.Close() - if err = sess.Begin(); err != nil { - return err - } + return db.Transaction(func(tx *gorm.DB) error { + if err := tx.Where("id = ?", key.ID).Delete(new(DeployKey)).Error; err != nil { + return errors.Newf("delete deploy key [%d]: %v", key.ID, err) + } - if _, err = sess.ID(key.ID).Delete(new(DeployKey)); err != nil { - return errors.Newf("delete deploy key [%d]: %v", key.ID, err) - } - - // Check if this is the last reference to same key content. - has, err := sess.Where("key_id = ?", key.KeyID).Get(new(DeployKey)) - if err != nil { - return err - } else if !has { - if err = deletePublicKeys(sess, key.KeyID); err != nil { + // Check if this is the last reference to same key content. + err := tx.Where("key_id = ?", key.KeyID).First(new(DeployKey)).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + if err = deletePublicKeys(tx, key.KeyID); err != nil { + return err + } + } else if err != nil { return err } - } - return sess.Commit() + return nil + }) } // ListDeployKeys returns all deploy keys by given repository ID. func ListDeployKeys(repoID int64) ([]*DeployKey, error) { keys := make([]*DeployKey, 0, 5) - return keys, x.Where("repo_id = ?", repoID).Find(&keys) + return keys, db.Where("repo_id = ?", repoID).Find(&keys).Error }