mirror of
https://github.com/gogs/gogs.git
synced 2026-01-30 11:09:17 +01:00
db: refactor "action" table to use GORM (#7054)
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
This commit is contained in:
44
.github/workflows/go.yml
vendored
44
.github/workflows/go.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.16.x, 1.17.x, 1.18.x ]
|
||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
platform: [ ubuntu-latest, macos-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
@@ -89,6 +89,46 @@ jobs:
|
||||
|
||||
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
# Running tests with race detection consumes too much memory on Windows,
|
||||
# see https://github.com/golang/go/issues/46099 for details.
|
||||
test-windows:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.16.x, 1.17.x, 1.18.x ]
|
||||
platform: [ windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run tests with coverage
|
||||
run: go test -v -coverprofile=coverage -covermode=atomic ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v1.5.0
|
||||
with:
|
||||
file: ./coverage
|
||||
flags: unittests
|
||||
- name: Send email on failure
|
||||
uses: dawidd6/action-send-mail@v3
|
||||
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
with:
|
||||
server_address: smtp.mailgun.org
|
||||
server_port: 465
|
||||
username: ${{ secrets.SMTP_USERNAME }}
|
||||
password: ${{ secrets.SMTP_PASSWORD }}
|
||||
subject: GitHub Actions (${{ github.repository }}) job result
|
||||
to: github-actions-8ce6454@unknwon.io
|
||||
from: GitHub Actions (${{ github.repository }})
|
||||
reply_to: noreply@unknwon.io
|
||||
body: |
|
||||
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
|
||||
|
||||
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
postgres:
|
||||
name: Postgres
|
||||
strategy:
|
||||
@@ -151,7 +191,7 @@ jobs:
|
||||
MYSQL_PORT: 3306
|
||||
|
||||
sqlite-go:
|
||||
name: SQLite (Go)
|
||||
name: SQLite - Go
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.16.x, 1.17.x, 1.18.x ]
|
||||
|
||||
@@ -31,6 +31,30 @@ Indexes:
|
||||
"idx_access_token_user_id" (uid)
|
||||
```
|
||||
|
||||
# Table "action"
|
||||
|
||||
```
|
||||
FIELD | COLUMN | POSTGRESQL | MYSQL | SQLITE3
|
||||
---------------+----------------+--------------------------------+--------------------------------+---------------------------------
|
||||
ID | id | BIGSERIAL | BIGINT AUTO_INCREMENT | INTEGER
|
||||
UserID | user_id | BIGINT | BIGINT | INTEGER
|
||||
OpType | op_type | BIGINT | BIGINT | INTEGER
|
||||
ActUserID | act_user_id | BIGINT | BIGINT | INTEGER
|
||||
ActUserName | act_user_name | TEXT | LONGTEXT | TEXT
|
||||
RepoID | repo_id | BIGINT | BIGINT | INTEGER
|
||||
RepoUserName | repo_user_name | TEXT | LONGTEXT | TEXT
|
||||
RepoName | repo_name | TEXT | LONGTEXT | TEXT
|
||||
RefName | ref_name | TEXT | LONGTEXT | TEXT
|
||||
IsPrivate | is_private | BOOLEAN NOT NULL DEFAULT FALSE | BOOLEAN NOT NULL DEFAULT FALSE | NUMERIC NOT NULL DEFAULT FALSE
|
||||
Content | content | TEXT | LONGTEXT | TEXT
|
||||
CreatedUnix | created_unix | BIGINT | BIGINT | INTEGER
|
||||
|
||||
Primary keys: id
|
||||
Indexes:
|
||||
"idx_action_repo_id" (repo_id)
|
||||
"idx_action_user_id" (user_id)
|
||||
```
|
||||
|
||||
# Table "lfs_object"
|
||||
|
||||
```
|
||||
|
||||
@@ -8,6 +8,14 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func SetMockApp(t *testing.T, opts AppOpts) {
|
||||
before := App
|
||||
App = opts
|
||||
t.Cleanup(func() {
|
||||
App = before
|
||||
})
|
||||
}
|
||||
|
||||
func SetMockServer(t *testing.T, opts ServerOpts) {
|
||||
before := Server
|
||||
Server = opts
|
||||
@@ -15,3 +23,27 @@ func SetMockServer(t *testing.T, opts ServerOpts) {
|
||||
Server = before
|
||||
})
|
||||
}
|
||||
|
||||
func SetMockSSH(t *testing.T, opts SSHOpts) {
|
||||
before := SSH
|
||||
SSH = opts
|
||||
t.Cleanup(func() {
|
||||
SSH = before
|
||||
})
|
||||
}
|
||||
|
||||
func SetMockRepository(t *testing.T, opts RepositoryOpts) {
|
||||
before := Repository
|
||||
Repository = opts
|
||||
t.Cleanup(func() {
|
||||
Repository = before
|
||||
})
|
||||
}
|
||||
|
||||
func SetMockUI(t *testing.T, opts UIOpts) {
|
||||
before := UI
|
||||
UI = opts
|
||||
t.Cleanup(func() {
|
||||
UI = before
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ import (
|
||||
)
|
||||
|
||||
// ℹ️ README: This file contains static values that should only be set at initialization time.
|
||||
//
|
||||
// ⚠️ WARNING: After changing any options, do not forget to update template of
|
||||
// "/admin/config" page as well.
|
||||
|
||||
// HasMinWinSvc is whether the application is built with Windows Service support.
|
||||
//
|
||||
@@ -30,67 +33,7 @@ var (
|
||||
// CustomConf returns the absolute path of custom configuration file that is used.
|
||||
var CustomConf string
|
||||
|
||||
// ⚠️ WARNING: After changing the following section, do not forget to update template of
|
||||
// "/admin/config" page as well.
|
||||
var (
|
||||
// Application settings
|
||||
App struct {
|
||||
// ⚠️ WARNING: Should only be set by the main package (i.e. "gogs.go").
|
||||
Version string `ini:"-"`
|
||||
|
||||
BrandName string
|
||||
RunUser string
|
||||
RunMode string
|
||||
}
|
||||
|
||||
// SSH settings
|
||||
SSH struct {
|
||||
Disabled bool `ini:"DISABLE_SSH"`
|
||||
Domain string `ini:"SSH_DOMAIN"`
|
||||
Port int `ini:"SSH_PORT"`
|
||||
RootPath string `ini:"SSH_ROOT_PATH"`
|
||||
KeygenPath string `ini:"SSH_KEYGEN_PATH"`
|
||||
KeyTestPath string `ini:"SSH_KEY_TEST_PATH"`
|
||||
MinimumKeySizeCheck bool
|
||||
MinimumKeySizes map[string]int `ini:"-"` // Load from [ssh.minimum_key_sizes]
|
||||
RewriteAuthorizedKeysAtStart bool
|
||||
|
||||
StartBuiltinServer bool `ini:"START_SSH_SERVER"`
|
||||
ListenHost string `ini:"SSH_LISTEN_HOST"`
|
||||
ListenPort int `ini:"SSH_LISTEN_PORT"`
|
||||
ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"`
|
||||
ServerMACs []string `ini:"SSH_SERVER_MACS"`
|
||||
}
|
||||
|
||||
// Repository settings
|
||||
Repository struct {
|
||||
Root string
|
||||
ScriptType string
|
||||
ANSICharset string `ini:"ANSI_CHARSET"`
|
||||
ForcePrivate bool
|
||||
MaxCreationLimit int
|
||||
PreferredLicenses []string
|
||||
DisableHTTPGit bool `ini:"DISABLE_HTTP_GIT"`
|
||||
EnableLocalPathMigration bool
|
||||
EnableRawFileRenderMode bool
|
||||
CommitsFetchConcurrency int
|
||||
|
||||
// Repository editor settings
|
||||
Editor struct {
|
||||
LineWrapExtensions []string
|
||||
PreviewableFileModes []string
|
||||
} `ini:"repository.editor"`
|
||||
|
||||
// Repository upload settings
|
||||
Upload struct {
|
||||
Enabled bool
|
||||
TempPath string
|
||||
AllowedTypes []string `delim:"|"`
|
||||
FileMaxSize int64
|
||||
MaxFiles int
|
||||
} `ini:"repository.upload"`
|
||||
}
|
||||
|
||||
// Security settings
|
||||
Security struct {
|
||||
InstallLock bool
|
||||
@@ -295,27 +238,6 @@ var (
|
||||
MaxResponseItems int
|
||||
}
|
||||
|
||||
// UI settings
|
||||
UI struct {
|
||||
ExplorePagingNum int
|
||||
IssuePagingNum int
|
||||
FeedMaxCommitNum int
|
||||
ThemeColorMetaTag string
|
||||
MaxDisplayFileSize int64
|
||||
|
||||
Admin struct {
|
||||
UserPagingNum int
|
||||
RepoPagingNum int
|
||||
NoticePagingNum int
|
||||
OrgPagingNum int
|
||||
} `ini:"ui.admin"`
|
||||
User struct {
|
||||
RepoPagingNum int
|
||||
NewsFeedPagingNum int
|
||||
CommitsPagingNum int
|
||||
} `ini:"ui.user"`
|
||||
}
|
||||
|
||||
// Prometheus settings
|
||||
Prometheus struct {
|
||||
Enabled bool
|
||||
@@ -334,6 +256,18 @@ var (
|
||||
HasRobotsTxt bool
|
||||
)
|
||||
|
||||
type AppOpts struct {
|
||||
// ⚠️ WARNING: Should only be set by the main package (i.e. "gogs.go").
|
||||
Version string `ini:"-"`
|
||||
|
||||
BrandName string
|
||||
RunUser string
|
||||
RunMode string
|
||||
}
|
||||
|
||||
// Application settings
|
||||
var App AppOpts
|
||||
|
||||
type ServerOpts struct {
|
||||
ExternalURL string `ini:"EXTERNAL_URL"`
|
||||
Domain string
|
||||
@@ -365,6 +299,58 @@ type ServerOpts struct {
|
||||
// Server settings
|
||||
var Server ServerOpts
|
||||
|
||||
type SSHOpts struct {
|
||||
Disabled bool `ini:"DISABLE_SSH"`
|
||||
Domain string `ini:"SSH_DOMAIN"`
|
||||
Port int `ini:"SSH_PORT"`
|
||||
RootPath string `ini:"SSH_ROOT_PATH"`
|
||||
KeygenPath string `ini:"SSH_KEYGEN_PATH"`
|
||||
KeyTestPath string `ini:"SSH_KEY_TEST_PATH"`
|
||||
MinimumKeySizeCheck bool
|
||||
MinimumKeySizes map[string]int `ini:"-"` // Load from [ssh.minimum_key_sizes]
|
||||
RewriteAuthorizedKeysAtStart bool
|
||||
|
||||
StartBuiltinServer bool `ini:"START_SSH_SERVER"`
|
||||
ListenHost string `ini:"SSH_LISTEN_HOST"`
|
||||
ListenPort int `ini:"SSH_LISTEN_PORT"`
|
||||
ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"`
|
||||
ServerMACs []string `ini:"SSH_SERVER_MACS"`
|
||||
}
|
||||
|
||||
// SSH settings
|
||||
var SSH SSHOpts
|
||||
|
||||
type RepositoryOpts struct {
|
||||
Root string
|
||||
ScriptType string
|
||||
ANSICharset string `ini:"ANSI_CHARSET"`
|
||||
ForcePrivate bool
|
||||
MaxCreationLimit int
|
||||
PreferredLicenses []string
|
||||
DisableHTTPGit bool `ini:"DISABLE_HTTP_GIT"`
|
||||
EnableLocalPathMigration bool
|
||||
EnableRawFileRenderMode bool
|
||||
CommitsFetchConcurrency int
|
||||
|
||||
// Repository editor settings
|
||||
Editor struct {
|
||||
LineWrapExtensions []string
|
||||
PreviewableFileModes []string
|
||||
} `ini:"repository.editor"`
|
||||
|
||||
// Repository upload settings
|
||||
Upload struct {
|
||||
Enabled bool
|
||||
TempPath string
|
||||
AllowedTypes []string `delim:"|"`
|
||||
FileMaxSize int64
|
||||
MaxFiles int
|
||||
} `ini:"repository.upload"`
|
||||
}
|
||||
|
||||
// Repository settings
|
||||
var Repository RepositoryOpts
|
||||
|
||||
type DatabaseOpts struct {
|
||||
Type string
|
||||
Host string
|
||||
@@ -389,6 +375,31 @@ type LFSOpts struct {
|
||||
// LFS settings
|
||||
var LFS LFSOpts
|
||||
|
||||
type UIUserOpts struct {
|
||||
RepoPagingNum int
|
||||
NewsFeedPagingNum int
|
||||
CommitsPagingNum int
|
||||
}
|
||||
|
||||
type UIOpts struct {
|
||||
ExplorePagingNum int
|
||||
IssuePagingNum int
|
||||
FeedMaxCommitNum int
|
||||
ThemeColorMetaTag string
|
||||
MaxDisplayFileSize int64
|
||||
|
||||
Admin struct {
|
||||
UserPagingNum int
|
||||
RepoPagingNum int
|
||||
NoticePagingNum int
|
||||
OrgPagingNum int
|
||||
} `ini:"ui.admin"`
|
||||
User UIUserOpts `ini:"ui.user"`
|
||||
}
|
||||
|
||||
// UI settings
|
||||
var UI UIOpts
|
||||
|
||||
type i18nConf struct {
|
||||
Langs []string `delim:","`
|
||||
Names []string `delim:","`
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/repoutil"
|
||||
)
|
||||
|
||||
// ServeGoGet does quick responses for appropriate go-get meta with status OK
|
||||
@@ -52,7 +53,7 @@ func ServeGoGet() macaron.Handler {
|
||||
`,
|
||||
map[string]string{
|
||||
"GoGetImport": path.Join(conf.Server.URL.Host, conf.Server.Subpath, ownerName, repoName),
|
||||
"CloneLink": db.ComposeHTTPSCloneURL(ownerName, repoName),
|
||||
"CloneLink": repoutil.HTTPSCloneURL(ownerName, repoName),
|
||||
"GoDocDirectory": prefix + "{/dir}",
|
||||
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
|
||||
"InsecureFlag": insecureFlag,
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/repoutil"
|
||||
)
|
||||
|
||||
type PullRequest struct {
|
||||
@@ -43,7 +44,7 @@ type Repository struct {
|
||||
TreePath string
|
||||
CommitID string
|
||||
RepoLink string
|
||||
CloneLink db.CloneLink
|
||||
CloneLink repoutil.CloneLink
|
||||
CommitsCount int64
|
||||
Mirror *db.Mirror
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ var AccessTokens AccessTokensStore
|
||||
|
||||
// AccessToken is a personal access token.
|
||||
type AccessToken struct {
|
||||
ID int64
|
||||
ID int64 `gorm:"primarykey"`
|
||||
UserID int64 `xorm:"uid" gorm:"column:uid;index"`
|
||||
Name string
|
||||
Sha1 string `gorm:"type:VARCHAR(40);unique"`
|
||||
@@ -67,9 +67,11 @@ func (t *AccessToken) BeforeCreate(tx *gorm.DB) error {
|
||||
// AfterFind implements the GORM query hook.
|
||||
func (t *AccessToken) AfterFind(tx *gorm.DB) error {
|
||||
t.Created = time.Unix(t.CreatedUnix, 0).Local()
|
||||
t.Updated = time.Unix(t.UpdatedUnix, 0).Local()
|
||||
t.HasUsed = t.Updated.After(t.Created)
|
||||
t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc())
|
||||
if t.UpdatedUnix > 0 {
|
||||
t.Updated = time.Unix(t.UpdatedUnix, 0).Local()
|
||||
t.HasUsed = t.Updated.After(t.Created)
|
||||
t.HasRecentActivity = t.Updated.Add(7 * 24 * time.Hour).After(tx.NowFunc())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,6 @@ func TestAccessTokens(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(AccessToken)}
|
||||
@@ -95,7 +94,12 @@ func accessTokensCreate(t *testing.T, db *accessTokens) {
|
||||
|
||||
// Try create second access token with same name should fail
|
||||
_, err = db.Create(ctx, token.UserID, token.Name)
|
||||
wantErr := ErrAccessTokenAlreadyExist{args: errutil.Args{"userID": token.UserID, "name": token.Name}}
|
||||
wantErr := ErrAccessTokenAlreadyExist{
|
||||
args: errutil.Args{
|
||||
"userID": token.UserID,
|
||||
"name": token.Name,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantErr, err)
|
||||
}
|
||||
|
||||
@@ -113,8 +117,6 @@ func accessTokensDeleteByID(t *testing.T, db *accessTokens) {
|
||||
// We should be able to get it back
|
||||
_, err = db.GetBySHA1(ctx, token.Sha1)
|
||||
require.NoError(t, err)
|
||||
_, err = db.GetBySHA1(ctx, token.Sha1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now delete this token with correct user ID
|
||||
err = db.DeleteByID(ctx, token.UserID, token.ID)
|
||||
@@ -122,7 +124,11 @@ func accessTokensDeleteByID(t *testing.T, db *accessTokens) {
|
||||
|
||||
// We should get token not found error
|
||||
_, err = db.GetBySHA1(ctx, token.Sha1)
|
||||
wantErr := ErrAccessTokenNotExist{args: errutil.Args{"sha": token.Sha1}}
|
||||
wantErr := ErrAccessTokenNotExist{
|
||||
args: errutil.Args{
|
||||
"sha": token.Sha1,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantErr, err)
|
||||
}
|
||||
|
||||
@@ -139,7 +145,11 @@ func accessTokensGetBySHA(t *testing.T, db *accessTokens) {
|
||||
|
||||
// Try to get a non-existent token
|
||||
_, err = db.GetBySHA1(ctx, "bad_sha")
|
||||
wantErr := ErrAccessTokenNotExist{args: errutil.Args{"sha": "bad_sha"}}
|
||||
wantErr := ErrAccessTokenNotExist{
|
||||
args: errutil.Args{
|
||||
"sha": "bad_sha",
|
||||
},
|
||||
}
|
||||
assert.Equal(t, wantErr, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,766 +0,0 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/unknwon/com"
|
||||
log "unknwon.dev/clog/v2"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/lazyregexp"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
type ActionType int
|
||||
|
||||
// Note: To maintain backward compatibility only append to the end of list
|
||||
const (
|
||||
ACTION_CREATE_REPO ActionType = iota + 1 // 1
|
||||
ACTION_RENAME_REPO // 2
|
||||
ACTION_STAR_REPO // 3
|
||||
ACTION_WATCH_REPO // 4
|
||||
ACTION_COMMIT_REPO // 5
|
||||
ACTION_CREATE_ISSUE // 6
|
||||
ACTION_CREATE_PULL_REQUEST // 7
|
||||
ACTION_TRANSFER_REPO // 8
|
||||
ACTION_PUSH_TAG // 9
|
||||
ACTION_COMMENT_ISSUE // 10
|
||||
ACTION_MERGE_PULL_REQUEST // 11
|
||||
ACTION_CLOSE_ISSUE // 12
|
||||
ACTION_REOPEN_ISSUE // 13
|
||||
ACTION_CLOSE_PULL_REQUEST // 14
|
||||
ACTION_REOPEN_PULL_REQUEST // 15
|
||||
ACTION_CREATE_BRANCH // 16
|
||||
ACTION_DELETE_BRANCH // 17
|
||||
ACTION_DELETE_TAG // 18
|
||||
ACTION_FORK_REPO // 19
|
||||
ACTION_MIRROR_SYNC_PUSH // 20
|
||||
ACTION_MIRROR_SYNC_CREATE // 21
|
||||
ACTION_MIRROR_SYNC_DELETE // 22
|
||||
)
|
||||
|
||||
var (
|
||||
// Same as Github. See https://help.github.com/articles/closing-issues-via-commit-messages
|
||||
IssueCloseKeywords = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"}
|
||||
IssueReopenKeywords = []string{"reopen", "reopens", "reopened"}
|
||||
|
||||
IssueCloseKeywordsPat = lazyregexp.New(assembleKeywordsPattern(IssueCloseKeywords))
|
||||
IssueReopenKeywordsPat = lazyregexp.New(assembleKeywordsPattern(IssueReopenKeywords))
|
||||
issueReferencePattern = lazyregexp.New(`(?i)(?:)(^| )\S*#\d+`)
|
||||
)
|
||||
|
||||
func assembleKeywordsPattern(words []string) string {
|
||||
return fmt.Sprintf(`(?i)(?:%s) \S+`, strings.Join(words, "|"))
|
||||
}
|
||||
|
||||
// Action represents user operation type and other information to repository,
|
||||
// it implemented interface base.Actioner so that can be used in template render.
|
||||
type Action struct {
|
||||
ID int64
|
||||
UserID int64 // Receiver user ID
|
||||
OpType ActionType
|
||||
ActUserID int64 // Doer user ID
|
||||
ActUserName string // Doer user name
|
||||
ActAvatar string `xorm:"-" json:"-"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
RepoUserName string
|
||||
RepoName string
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"-" json:"-"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (a *Action) BeforeInsert() {
|
||||
a.CreatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (a *Action) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "created_unix":
|
||||
a.Created = time.Unix(a.CreatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Action) GetOpType() int {
|
||||
return int(a.OpType)
|
||||
}
|
||||
|
||||
func (a *Action) GetActUserName() string {
|
||||
return a.ActUserName
|
||||
}
|
||||
|
||||
func (a *Action) ShortActUserName() string {
|
||||
return tool.EllipsisString(a.ActUserName, 20)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoUserName() string {
|
||||
return a.RepoUserName
|
||||
}
|
||||
|
||||
func (a *Action) ShortRepoUserName() string {
|
||||
return tool.EllipsisString(a.RepoUserName, 20)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoName() string {
|
||||
return a.RepoName
|
||||
}
|
||||
|
||||
func (a *Action) ShortRepoName() string {
|
||||
return tool.EllipsisString(a.RepoName, 33)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoPath() string {
|
||||
return path.Join(a.RepoUserName, a.RepoName)
|
||||
}
|
||||
|
||||
func (a *Action) ShortRepoPath() string {
|
||||
return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoLink() string {
|
||||
if conf.Server.Subpath != "" {
|
||||
return path.Join(conf.Server.Subpath, a.GetRepoPath())
|
||||
}
|
||||
return "/" + a.GetRepoPath()
|
||||
}
|
||||
|
||||
func (a *Action) GetBranch() string {
|
||||
return a.RefName
|
||||
}
|
||||
|
||||
func (a *Action) GetContent() string {
|
||||
return a.Content
|
||||
}
|
||||
|
||||
func (a *Action) GetCreate() time.Time {
|
||||
return a.Created
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
return strings.SplitN(a.Content, "|", 2)
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueTitle() string {
|
||||
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
}
|
||||
return issue.Title
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueContent() string {
|
||||
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
}
|
||||
return issue.Content
|
||||
}
|
||||
|
||||
func newRepoAction(e Engine, doer, _ *User, repo *Repository) (err error) {
|
||||
opType := ACTION_CREATE_REPO
|
||||
if repo.IsFork {
|
||||
opType = ACTION_FORK_REPO
|
||||
}
|
||||
|
||||
return notifyWatchers(e, &Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: opType,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
})
|
||||
}
|
||||
|
||||
// NewRepoAction adds new action for creating repository.
|
||||
func NewRepoAction(doer, owner *User, repo *Repository) (err error) {
|
||||
return newRepoAction(x, doer, owner, repo)
|
||||
}
|
||||
|
||||
func renameRepoAction(e Engine, actUser *User, oldRepoName string, repo *Repository) (err error) {
|
||||
if err = notifyWatchers(e, &Action{
|
||||
ActUserID: actUser.ID,
|
||||
ActUserName: actUser.Name,
|
||||
OpType: ACTION_RENAME_REPO,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
Content: oldRepoName,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notify watchers: %v", err)
|
||||
}
|
||||
|
||||
log.Trace("action.renameRepoAction: %s/%s", actUser.Name, repo.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameRepoAction adds new action for renaming a repository.
|
||||
func RenameRepoAction(actUser *User, oldRepoName string, repo *Repository) error {
|
||||
return renameRepoAction(x, actUser, oldRepoName, repo)
|
||||
}
|
||||
|
||||
func issueIndexTrimRight(c rune) bool {
|
||||
return !unicode.IsDigit(c)
|
||||
}
|
||||
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
CommitterEmail string
|
||||
CommitterName string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
CompareURL string
|
||||
|
||||
avatars map[string]string
|
||||
}
|
||||
|
||||
func NewPushCommits() *PushCommits {
|
||||
return &PushCommits{
|
||||
avatars: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PushCommits) ToApiPayloadCommits(repoPath, repoURL string) ([]*api.PayloadCommit, error) {
|
||||
commits := make([]*api.PayloadCommit, len(pc.Commits))
|
||||
for i, commit := range pc.Commits {
|
||||
authorUsername := ""
|
||||
author, err := GetUserByEmail(commit.AuthorEmail)
|
||||
if err == nil {
|
||||
authorUsername = author.Name
|
||||
} else if !IsErrUserNotExist(err) {
|
||||
return nil, fmt.Errorf("get user by email: %v", err)
|
||||
}
|
||||
|
||||
committerUsername := ""
|
||||
committer, err := GetUserByEmail(commit.CommitterEmail)
|
||||
if err == nil {
|
||||
committerUsername = committer.Name
|
||||
} else if !IsErrUserNotExist(err) {
|
||||
return nil, fmt.Errorf("get user by email: %v", err)
|
||||
}
|
||||
|
||||
nameStatus, err := git.ShowNameStatus(repoPath, commit.Sha1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("show name status [commit_sha1: %s]: %v", commit.Sha1, err)
|
||||
}
|
||||
|
||||
commits[i] = &api.PayloadCommit{
|
||||
ID: commit.Sha1,
|
||||
Message: commit.Message,
|
||||
URL: fmt.Sprintf("%s/commit/%s", repoURL, commit.Sha1),
|
||||
Author: &api.PayloadUser{
|
||||
Name: commit.AuthorName,
|
||||
Email: commit.AuthorEmail,
|
||||
UserName: authorUsername,
|
||||
},
|
||||
Committer: &api.PayloadUser{
|
||||
Name: commit.CommitterName,
|
||||
Email: commit.CommitterEmail,
|
||||
UserName: committerUsername,
|
||||
},
|
||||
Added: nameStatus.Added,
|
||||
Removed: nameStatus.Removed,
|
||||
Modified: nameStatus.Modified,
|
||||
Timestamp: commit.Timestamp,
|
||||
}
|
||||
}
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
// AvatarLink tries to match user in database with e-mail
|
||||
// in order to show custom avatar, and falls back to general avatar link.
|
||||
func (pcs *PushCommits) AvatarLink(email string) string {
|
||||
_, ok := pcs.avatars[email]
|
||||
if !ok {
|
||||
u, err := GetUserByEmail(email)
|
||||
if err != nil {
|
||||
pcs.avatars[email] = tool.AvatarLink(email)
|
||||
if !IsErrUserNotExist(err) {
|
||||
log.Error("get user by email: %v", err)
|
||||
}
|
||||
} else {
|
||||
pcs.avatars[email] = u.RelAvatarLink()
|
||||
}
|
||||
}
|
||||
|
||||
return pcs.avatars[email]
|
||||
}
|
||||
|
||||
// UpdateIssuesCommit checks if issues are manipulated by commit message.
|
||||
func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) error {
|
||||
// Commits are appended in the reverse order.
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
c := commits[i]
|
||||
|
||||
refMarked := make(map[int64]bool)
|
||||
for _, ref := range issueReferencePattern.FindAllString(c.Message, -1) {
|
||||
ref = strings.TrimSpace(ref)
|
||||
ref = strings.TrimRightFunc(ref, issueIndexTrimRight)
|
||||
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// FIXME: We don't support User#ID syntax yet
|
||||
// return ErrNotImplemented
|
||||
continue
|
||||
}
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
msgLines := strings.Split(c.Message, "\n")
|
||||
shortMsg := msgLines[0]
|
||||
if len(msgLines) > 2 {
|
||||
shortMsg += "..."
|
||||
}
|
||||
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, shortMsg)
|
||||
if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
refMarked = make(map[int64]bool)
|
||||
// FIXME: can merge this one and next one to a common function.
|
||||
for _, ref := range IssueCloseKeywordsPat.FindAllString(c.Message, -1) {
|
||||
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
|
||||
ref = strings.TrimRightFunc(ref, issueIndexTrimRight)
|
||||
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// FIXME: We don't support User#ID syntax yet
|
||||
continue
|
||||
}
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
if issue.RepoID != repo.ID || issue.IsClosed {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(doer, repo, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// It is conflict to have close and reopen at same time, so refsMarkd doesn't need to reinit here.
|
||||
for _, ref := range IssueReopenKeywordsPat.FindAllString(c.Message, -1) {
|
||||
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
|
||||
ref = strings.TrimRightFunc(ref, issueIndexTrimRight)
|
||||
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// We don't support User#ID syntax yet
|
||||
// return ErrNotImplemented
|
||||
continue
|
||||
}
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
if issue.RepoID != repo.ID || !issue.IsClosed {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(doer, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CommitRepoActionOptions struct {
|
||||
PusherName string
|
||||
RepoOwnerID int64
|
||||
RepoName string
|
||||
RefFullName string
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
Commits *PushCommits
|
||||
}
|
||||
|
||||
// CommitRepoAction adds new commit action to the repository, and prepare corresponding webhooks.
|
||||
func CommitRepoAction(opts CommitRepoActionOptions) error {
|
||||
pusher, err := GetUserByName(opts.PusherName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err)
|
||||
}
|
||||
|
||||
repo, err := GetRepositoryByName(opts.RepoOwnerID, opts.RepoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err)
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo, false); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
|
||||
isNewRef := opts.OldCommitID == git.EmptyID
|
||||
isDelRef := opts.NewCommitID == git.EmptyID
|
||||
|
||||
opType := ACTION_COMMIT_REPO
|
||||
// Check if it's tag push or branch.
|
||||
if strings.HasPrefix(opts.RefFullName, git.RefsTags) {
|
||||
opType = ACTION_PUSH_TAG
|
||||
} else {
|
||||
// if not the first commit, set the compare URL.
|
||||
if !isNewRef && !isDelRef {
|
||||
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
|
||||
}
|
||||
|
||||
// Only update issues via commits when internal issue tracker is enabled
|
||||
if repo.EnableIssues && !repo.EnableExternalTracker {
|
||||
if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil {
|
||||
log.Error("UpdateIssuesCommit: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(opts.Commits.Commits) > conf.UI.FeedMaxCommitNum {
|
||||
opts.Commits.Commits = opts.Commits.Commits[:conf.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
data, err := jsoniter.Marshal(opts.Commits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Marshal: %v", err)
|
||||
}
|
||||
|
||||
refName := git.RefShortName(opts.RefFullName)
|
||||
action := &Action{
|
||||
ActUserID: pusher.ID,
|
||||
ActUserName: pusher.Name,
|
||||
Content: string(data),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.MustOwner().Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: refName,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
}
|
||||
|
||||
apiRepo := repo.APIFormat(nil)
|
||||
apiPusher := pusher.APIFormat()
|
||||
switch opType {
|
||||
case ACTION_COMMIT_REPO: // Push
|
||||
if isDelRef {
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_DELETE, &api.DeletePayload{
|
||||
Ref: refName,
|
||||
RefType: "branch",
|
||||
PusherType: api.PUSHER_TYPE_USER,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err)
|
||||
}
|
||||
|
||||
action.OpType = ACTION_DELETE_BRANCH
|
||||
if err = NotifyWatchers(action); err != nil {
|
||||
return fmt.Errorf("NotifyWatchers.(delete branch): %v", err)
|
||||
}
|
||||
|
||||
// Delete branch doesn't have anything to push or compare
|
||||
return nil
|
||||
}
|
||||
|
||||
compareURL := conf.Server.ExternalURL + opts.Commits.CompareURL
|
||||
if isNewRef {
|
||||
compareURL = ""
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
RefType: "branch",
|
||||
DefaultBranch: repo.DefaultBranch,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks.(new branch): %v", err)
|
||||
}
|
||||
|
||||
action.OpType = ACTION_CREATE_BRANCH
|
||||
if err = NotifyWatchers(action); err != nil {
|
||||
return fmt.Errorf("NotifyWatchers.(new branch): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
commits, err := opts.Commits.ToApiPayloadCommits(repo.RepoPath(), repo.HTMLURL())
|
||||
if err != nil {
|
||||
return fmt.Errorf("ToApiPayloadCommits: %v", err)
|
||||
}
|
||||
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, &api.PushPayload{
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: compareURL,
|
||||
Commits: commits,
|
||||
Repo: apiRepo,
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks.(new commit): %v", err)
|
||||
}
|
||||
|
||||
action.OpType = ACTION_COMMIT_REPO
|
||||
if err = NotifyWatchers(action); err != nil {
|
||||
return fmt.Errorf("NotifyWatchers.(new commit): %v", err)
|
||||
}
|
||||
|
||||
case ACTION_PUSH_TAG: // Tag
|
||||
if isDelRef {
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_DELETE, &api.DeletePayload{
|
||||
Ref: refName,
|
||||
RefType: "tag",
|
||||
PusherType: api.PUSHER_TYPE_USER,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err)
|
||||
}
|
||||
|
||||
action.OpType = ACTION_DELETE_TAG
|
||||
if err = NotifyWatchers(action); err != nil {
|
||||
return fmt.Errorf("NotifyWatchers.(delete tag): %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
RefType: "tag",
|
||||
Sha: opts.NewCommitID,
|
||||
DefaultBranch: repo.DefaultBranch,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks.(new tag): %v", err)
|
||||
}
|
||||
|
||||
action.OpType = ACTION_PUSH_TAG
|
||||
if err = NotifyWatchers(action); err != nil {
|
||||
return fmt.Errorf("NotifyWatchers.(new tag): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transferRepoAction(e Engine, doer, oldOwner *User, repo *Repository) (err error) {
|
||||
if err = notifyWatchers(e, &Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ACTION_TRANSFER_REPO,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
Content: path.Join(oldOwner.Name, repo.Name),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notifyWatchers: %v", err)
|
||||
}
|
||||
|
||||
// Remove watch for organization.
|
||||
if oldOwner.IsOrganization() {
|
||||
if err = watchRepo(e, oldOwner.ID, repo.ID, false); err != nil {
|
||||
return fmt.Errorf("watchRepo [false]: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransferRepoAction adds new action for transferring repository,
|
||||
// the Owner field of repository is assumed to be new owner.
|
||||
func TransferRepoAction(doer, oldOwner *User, repo *Repository) error {
|
||||
return transferRepoAction(x, doer, oldOwner, repo)
|
||||
}
|
||||
|
||||
func mergePullRequestAction(e Engine, doer *User, repo *Repository, issue *Issue) error {
|
||||
return notifyWatchers(e, &Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ACTION_MERGE_PULL_REQUEST,
|
||||
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
})
|
||||
}
|
||||
|
||||
// MergePullRequestAction adds new action for merging pull request.
|
||||
func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error {
|
||||
return mergePullRequestAction(x, actUser, repo, pull)
|
||||
}
|
||||
|
||||
func mirrorSyncAction(opType ActionType, repo *Repository, refName string, data []byte) error {
|
||||
return NotifyWatchers(&Action{
|
||||
ActUserID: repo.OwnerID,
|
||||
ActUserName: repo.MustOwner().Name,
|
||||
OpType: opType,
|
||||
Content: string(data),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.MustOwner().Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: refName,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
})
|
||||
}
|
||||
|
||||
type MirrorSyncPushActionOptions struct {
|
||||
RefName string
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
Commits *PushCommits
|
||||
}
|
||||
|
||||
// MirrorSyncPushAction adds new action for mirror synchronization of pushed commits.
|
||||
func MirrorSyncPushAction(repo *Repository, opts MirrorSyncPushActionOptions) error {
|
||||
if len(opts.Commits.Commits) > conf.UI.FeedMaxCommitNum {
|
||||
opts.Commits.Commits = opts.Commits.Commits[:conf.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
apiCommits, err := opts.Commits.ToApiPayloadCommits(repo.RepoPath(), repo.HTMLURL())
|
||||
if err != nil {
|
||||
return fmt.Errorf("ToApiPayloadCommits: %v", err)
|
||||
}
|
||||
|
||||
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
|
||||
apiPusher := repo.MustOwner().APIFormat()
|
||||
if err := PrepareWebhooks(repo, HOOK_EVENT_PUSH, &api.PushPayload{
|
||||
Ref: opts.RefName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: conf.Server.ExternalURL + opts.Commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
Repo: repo.APIFormat(nil),
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks: %v", err)
|
||||
}
|
||||
|
||||
data, err := jsoniter.Marshal(opts.Commits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return mirrorSyncAction(ACTION_MIRROR_SYNC_PUSH, repo, opts.RefName, data)
|
||||
}
|
||||
|
||||
// MirrorSyncCreateAction adds new action for mirror synchronization of new reference.
|
||||
func MirrorSyncCreateAction(repo *Repository, refName string) error {
|
||||
return mirrorSyncAction(ACTION_MIRROR_SYNC_CREATE, repo, refName, nil)
|
||||
}
|
||||
|
||||
// MirrorSyncCreateAction adds new action for mirror synchronization of delete reference.
|
||||
func MirrorSyncDeleteAction(repo *Repository, refName string) error {
|
||||
return mirrorSyncAction(ACTION_MIRROR_SYNC_DELETE, repo, refName, nil)
|
||||
}
|
||||
|
||||
// GetFeeds returns action list of given user in given context.
|
||||
// actorID is the user who's requesting, ctxUserID is the user/org that is requested.
|
||||
// actorID can be -1 when isProfile is true or to skip the permission check.
|
||||
func GetFeeds(ctxUser *User, actorID, afterID int64, isProfile bool) ([]*Action, error) {
|
||||
actions := make([]*Action, 0, conf.UI.User.NewsFeedPagingNum)
|
||||
sess := x.Limit(conf.UI.User.NewsFeedPagingNum).Where("user_id = ?", ctxUser.ID).Desc("id")
|
||||
if afterID > 0 {
|
||||
sess.And("id < ?", afterID)
|
||||
}
|
||||
if isProfile {
|
||||
sess.And("is_private = ?", false).And("act_user_id = ?", ctxUser.ID)
|
||||
} else if actorID != -1 && ctxUser.IsOrganization() {
|
||||
// FIXME: only need to get IDs here, not all fields of repository.
|
||||
repos, _, err := ctxUser.GetUserRepositories(actorID, 1, ctxUser.NumRepos)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepositories: %v", err)
|
||||
}
|
||||
|
||||
var repoIDs []int64
|
||||
for _, repo := range repos {
|
||||
repoIDs = append(repoIDs, repo.ID)
|
||||
}
|
||||
|
||||
if len(repoIDs) > 0 {
|
||||
sess.In("repo_id", repoIDs)
|
||||
}
|
||||
}
|
||||
|
||||
err := sess.Find(&actions)
|
||||
return actions, err
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright 2020 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_issueReferencePattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
expStrings []string
|
||||
}{
|
||||
{
|
||||
name: "no match",
|
||||
message: "Hello world!",
|
||||
expStrings: nil,
|
||||
},
|
||||
{
|
||||
name: "contains issue numbers",
|
||||
message: "#123 is fixed, and #456 is WIP",
|
||||
expStrings: []string{"#123", " #456"},
|
||||
},
|
||||
{
|
||||
name: "contains full issue references",
|
||||
message: "#123 is fixed, and user/repo#456 is WIP",
|
||||
expStrings: []string{"#123", " user/repo#456"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
strs := issueReferencePattern.FindAllString(test.message, -1)
|
||||
assert.Equal(t, test.expStrings, strs)
|
||||
})
|
||||
}
|
||||
}
|
||||
962
internal/db/actions.go
Normal file
962
internal/db/actions.go
Normal file
@@ -0,0 +1,962 @@
|
||||
// Copyright 2020 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
log "unknwon.dev/clog/v2"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/lazyregexp"
|
||||
"gogs.io/gogs/internal/repoutil"
|
||||
"gogs.io/gogs/internal/strutil"
|
||||
"gogs.io/gogs/internal/testutil"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
// ActionsStore is the persistent interface for actions.
|
||||
//
|
||||
// NOTE: All methods are sorted in alphabetical order.
|
||||
type ActionsStore interface {
|
||||
// CommitRepo creates actions for pushing commits to the repository. An action
|
||||
// with the type ActionDeleteBranch is created if the push deletes a branch; an
|
||||
// action with the type ActionCommitRepo is created for a regular push. If the
|
||||
// regular push also creates a new branch, then another action with type
|
||||
// ActionCreateBranch is created.
|
||||
CommitRepo(ctx context.Context, opts CommitRepoOptions) error
|
||||
// ListByOrganization returns actions of the organization viewable by the actor.
|
||||
// Results are paginated if `afterID` is given.
|
||||
ListByOrganization(ctx context.Context, orgID, actorID, afterID int64) ([]*Action, error)
|
||||
// ListByUser returns actions of the user viewable by the actor. Results are
|
||||
// paginated if `afterID` is given. The `isProfile` indicates whether repository
|
||||
// permissions should be considered.
|
||||
ListByUser(ctx context.Context, userID, actorID, afterID int64, isProfile bool) ([]*Action, error)
|
||||
// MergePullRequest creates an action for merging a pull request.
|
||||
MergePullRequest(ctx context.Context, doer, owner *User, repo *Repository, pull *Issue) error
|
||||
// MirrorSyncCreate creates an action for mirror synchronization of a new
|
||||
// reference.
|
||||
MirrorSyncCreate(ctx context.Context, owner *User, repo *Repository, refName string) error
|
||||
// MirrorSyncDelete creates an action for mirror synchronization of a reference
|
||||
// deletion.
|
||||
MirrorSyncDelete(ctx context.Context, owner *User, repo *Repository, refName string) error
|
||||
// MirrorSyncPush creates an action for mirror synchronization of pushed
|
||||
// commits.
|
||||
MirrorSyncPush(ctx context.Context, opts MirrorSyncPushOptions) error
|
||||
// NewRepo creates an action for creating a new repository. The action type
|
||||
// could be ActionCreateRepo or ActionForkRepo based on whether the repository
|
||||
// is a fork.
|
||||
NewRepo(ctx context.Context, doer, owner *User, repo *Repository) error
|
||||
// PushTag creates an action for pushing tags to the repository. An action with
|
||||
// the type ActionDeleteTag is created if the push deletes a tag. Otherwise, an
|
||||
// action with the type ActionPushTag is created for a regular push.
|
||||
PushTag(ctx context.Context, opts PushTagOptions) error
|
||||
// RenameRepo creates an action for renaming a repository.
|
||||
RenameRepo(ctx context.Context, doer, owner *User, oldRepoName string, repo *Repository) error
|
||||
// TransferRepo creates an action for transferring a repository to a new owner.
|
||||
TransferRepo(ctx context.Context, doer, oldOwner, newOwner *User, repo *Repository) error
|
||||
}
|
||||
|
||||
var Actions ActionsStore
|
||||
|
||||
var _ ActionsStore = (*actions)(nil)
|
||||
|
||||
type actions struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
// NewActionsStore returns a persistent interface for actions with given
|
||||
// database connection.
|
||||
func NewActionsStore(db *gorm.DB) ActionsStore {
|
||||
return &actions{DB: db}
|
||||
}
|
||||
|
||||
func (db *actions) listByOrganization(ctx context.Context, orgID, actorID, afterID int64) *gorm.DB {
|
||||
/*
|
||||
Equivalent SQL for PostgreSQL:
|
||||
|
||||
SELECT * FROM "action"
|
||||
WHERE
|
||||
user_id = @userID
|
||||
AND (@skipAfter OR id < @afterID)
|
||||
AND repo_id IN (
|
||||
SELECT repository.id FROM "repository"
|
||||
JOIN team_repo ON repository.id = team_repo.repo_id
|
||||
WHERE team_repo.team_id IN (
|
||||
SELECT team_id FROM "team_user"
|
||||
WHERE
|
||||
team_user.org_id = @orgID AND uid = @actorID)
|
||||
OR (repository.is_private = FALSE AND repository.is_unlisted = FALSE)
|
||||
)
|
||||
ORDER BY id DESC
|
||||
LIMIT @limit
|
||||
*/
|
||||
return db.WithContext(ctx).
|
||||
Where("user_id = ?", orgID).
|
||||
Where(db.
|
||||
// Not apply when afterID is not given
|
||||
Where("?", afterID <= 0).
|
||||
Or("id < ?", afterID),
|
||||
).
|
||||
Where("repo_id IN (?)",
|
||||
db.Select("repository.id").
|
||||
Table("repository").
|
||||
Joins("JOIN team_repo ON repository.id = team_repo.repo_id").
|
||||
Where("team_repo.team_id IN (?)",
|
||||
db.Select("team_id").
|
||||
Table("team_user").
|
||||
Where("team_user.org_id = ? AND uid = ?", orgID, actorID),
|
||||
).
|
||||
Or("repository.is_private = ? AND repository.is_unlisted = ?", false, false),
|
||||
).
|
||||
Limit(conf.UI.User.NewsFeedPagingNum).
|
||||
Order("id DESC")
|
||||
}
|
||||
|
||||
func (db *actions) ListByOrganization(ctx context.Context, orgID, actorID, afterID int64) ([]*Action, error) {
|
||||
actions := make([]*Action, 0, conf.UI.User.NewsFeedPagingNum)
|
||||
return actions, db.listByOrganization(ctx, orgID, actorID, afterID).Find(&actions).Error
|
||||
}
|
||||
|
||||
func (db *actions) listByUser(ctx context.Context, userID, actorID, afterID int64, isProfile bool) *gorm.DB {
|
||||
/*
|
||||
Equivalent SQL for PostgreSQL:
|
||||
|
||||
SELECT * FROM "action"
|
||||
WHERE
|
||||
user_id = @userID
|
||||
AND (@skipAfter OR id < @afterID)
|
||||
AND (@includePrivate OR (is_private = FALSE AND act_user_id = @actorID))
|
||||
ORDER BY id DESC
|
||||
LIMIT @limit
|
||||
*/
|
||||
return db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Where(db.
|
||||
// Not apply when afterID is not given
|
||||
Where("?", afterID <= 0).
|
||||
Or("id < ?", afterID),
|
||||
).
|
||||
Where(db.
|
||||
// Not apply when in not profile page or the user is viewing own profile
|
||||
Where("?", !isProfile || actorID == userID).
|
||||
Or("is_private = ? AND act_user_id = ?", false, userID),
|
||||
).
|
||||
Limit(conf.UI.User.NewsFeedPagingNum).
|
||||
Order("id DESC")
|
||||
}
|
||||
|
||||
func (db *actions) ListByUser(ctx context.Context, userID, actorID, afterID int64, isProfile bool) ([]*Action, error) {
|
||||
actions := make([]*Action, 0, conf.UI.User.NewsFeedPagingNum)
|
||||
return actions, db.listByUser(ctx, userID, actorID, afterID, isProfile).Find(&actions).Error
|
||||
}
|
||||
|
||||
// notifyWatchers creates rows in action table for watchers who are able to see the action.
|
||||
func (db *actions) notifyWatchers(ctx context.Context, act *Action) error {
|
||||
watches, err := NewWatchesStore(db.DB).ListByRepo(ctx, act.RepoID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "list watches")
|
||||
}
|
||||
|
||||
// Clone returns a deep copy of the action with UserID assigned
|
||||
clone := func(userID int64) *Action {
|
||||
tmp := *act
|
||||
tmp.UserID = userID
|
||||
return &tmp
|
||||
}
|
||||
|
||||
// Plus one for the actor
|
||||
actions := make([]*Action, 0, len(watches)+1)
|
||||
actions = append(actions, clone(act.ActUserID))
|
||||
|
||||
for _, watch := range watches {
|
||||
if act.ActUserID == watch.UserID {
|
||||
continue
|
||||
}
|
||||
actions = append(actions, clone(watch.UserID))
|
||||
}
|
||||
|
||||
return db.Create(actions).Error
|
||||
}
|
||||
|
||||
func (db *actions) NewRepo(ctx context.Context, doer, owner *User, repo *Repository) error {
|
||||
opType := ActionCreateRepo
|
||||
if repo.IsFork {
|
||||
opType = ActionForkRepo
|
||||
}
|
||||
|
||||
return db.notifyWatchers(ctx,
|
||||
&Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: opType,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (db *actions) RenameRepo(ctx context.Context, doer, owner *User, oldRepoName string, repo *Repository) error {
|
||||
return db.notifyWatchers(ctx,
|
||||
&Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ActionRenameRepo,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
Content: oldRepoName,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (db *actions) mirrorSyncAction(ctx context.Context, opType ActionType, owner *User, repo *Repository, refName string, content []byte) error {
|
||||
return db.notifyWatchers(ctx,
|
||||
&Action{
|
||||
ActUserID: owner.ID,
|
||||
ActUserName: owner.Name,
|
||||
OpType: opType,
|
||||
Content: string(content),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: owner.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: refName,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type MirrorSyncPushOptions struct {
|
||||
Owner *User
|
||||
Repo *Repository
|
||||
RefName string
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
Commits *PushCommits
|
||||
}
|
||||
|
||||
func (db *actions) MirrorSyncPush(ctx context.Context, opts MirrorSyncPushOptions) error {
|
||||
if conf.UI.FeedMaxCommitNum > 0 && len(opts.Commits.Commits) > conf.UI.FeedMaxCommitNum {
|
||||
opts.Commits.Commits = opts.Commits.Commits[:conf.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
apiCommits, err := opts.Commits.APIFormat(ctx,
|
||||
NewUsersStore(db.DB),
|
||||
repoutil.RepositoryPath(opts.Owner.Name, opts.Repo.Name),
|
||||
repoutil.HTMLURL(opts.Owner.Name, opts.Repo.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "convert commits to API format")
|
||||
}
|
||||
|
||||
opts.Commits.CompareURL = repoutil.CompareCommitsPath(opts.Owner.Name, opts.Repo.Name, opts.OldCommitID, opts.NewCommitID)
|
||||
apiPusher := opts.Owner.APIFormat()
|
||||
err = PrepareWebhooks(
|
||||
opts.Repo,
|
||||
HOOK_EVENT_PUSH,
|
||||
&api.PushPayload{
|
||||
Ref: opts.RefName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: conf.Server.ExternalURL + opts.Commits.CompareURL,
|
||||
Commits: apiCommits,
|
||||
Repo: opts.Repo.APIFormat(opts.Owner),
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "prepare webhooks")
|
||||
}
|
||||
|
||||
data, err := jsoniter.Marshal(opts.Commits)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal JSON")
|
||||
}
|
||||
|
||||
return db.mirrorSyncAction(ctx, ActionMirrorSyncPush, opts.Owner, opts.Repo, opts.RefName, data)
|
||||
}
|
||||
|
||||
func (db *actions) MirrorSyncCreate(ctx context.Context, owner *User, repo *Repository, refName string) error {
|
||||
return db.mirrorSyncAction(ctx, ActionMirrorSyncCreate, owner, repo, refName, nil)
|
||||
}
|
||||
|
||||
func (db *actions) MirrorSyncDelete(ctx context.Context, owner *User, repo *Repository, refName string) error {
|
||||
return db.mirrorSyncAction(ctx, ActionMirrorSyncDelete, owner, repo, refName, nil)
|
||||
}
|
||||
|
||||
func (db *actions) MergePullRequest(ctx context.Context, doer, owner *User, repo *Repository, pull *Issue) error {
|
||||
return db.notifyWatchers(ctx,
|
||||
&Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ActionMergePullRequest,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (db *actions) TransferRepo(ctx context.Context, doer, oldOwner, newOwner *User, repo *Repository) error {
|
||||
return db.notifyWatchers(ctx,
|
||||
&Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ActionTransferRepo,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: newOwner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
Content: oldOwner.Name + "/" + repo.Name,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
// Same as GitHub, see https://docs.github.com/en/free-pro-team@latest/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue
|
||||
issueCloseKeywords = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"}
|
||||
issueReopenKeywords = []string{"reopen", "reopens", "reopened"}
|
||||
|
||||
issueCloseKeywordsPattern = lazyregexp.New(assembleKeywordsPattern(issueCloseKeywords))
|
||||
issueReopenKeywordsPattern = lazyregexp.New(assembleKeywordsPattern(issueReopenKeywords))
|
||||
issueReferencePattern = lazyregexp.New(`(?i)(?:)(^| )\S*#\d+`)
|
||||
)
|
||||
|
||||
func assembleKeywordsPattern(words []string) string {
|
||||
return fmt.Sprintf(`(?i)(?:%s) \S+`, strings.Join(words, "|"))
|
||||
}
|
||||
|
||||
// updateCommitReferencesToIssues checks if issues are manipulated by commit message.
|
||||
func updateCommitReferencesToIssues(doer *User, repo *Repository, commits []*PushCommit) error {
|
||||
trimRightNonDigits := func(c rune) bool {
|
||||
return !unicode.IsDigit(c)
|
||||
}
|
||||
|
||||
// Commits are appended in the reverse order.
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
c := commits[i]
|
||||
|
||||
refMarked := make(map[int64]bool)
|
||||
for _, ref := range issueReferencePattern.FindAllString(c.Message, -1) {
|
||||
ref = strings.TrimSpace(ref)
|
||||
ref = strings.TrimRightFunc(ref, trimRightNonDigits)
|
||||
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// FIXME: We don't support User#ID syntax yet
|
||||
continue
|
||||
}
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
msgLines := strings.Split(c.Message, "\n")
|
||||
shortMsg := msgLines[0]
|
||||
if len(msgLines) > 2 {
|
||||
shortMsg += "..."
|
||||
}
|
||||
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, shortMsg)
|
||||
if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
refMarked = make(map[int64]bool)
|
||||
// FIXME: Can merge this and the next for loop to a common function.
|
||||
for _, ref := range issueCloseKeywordsPattern.FindAllString(c.Message, -1) {
|
||||
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
|
||||
ref = strings.TrimRightFunc(ref, trimRightNonDigits)
|
||||
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// FIXME: We don't support User#ID syntax yet
|
||||
continue
|
||||
}
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
if issue.RepoID != repo.ID || issue.IsClosed {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(doer, repo, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// It is conflict to have close and reopen at same time, so refsMarkd doesn't need to reinit here.
|
||||
for _, ref := range issueReopenKeywordsPattern.FindAllString(c.Message, -1) {
|
||||
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
|
||||
ref = strings.TrimRightFunc(ref, trimRightNonDigits)
|
||||
|
||||
if ref == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// We don't support User#ID syntax yet
|
||||
// return ErrNotImplemented
|
||||
continue
|
||||
}
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if refMarked[issue.ID] {
|
||||
continue
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
if issue.RepoID != repo.ID || !issue.IsClosed {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(doer, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CommitRepoOptions struct {
|
||||
Owner *User
|
||||
Repo *Repository
|
||||
PusherName string
|
||||
RefFullName string
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
Commits *PushCommits
|
||||
}
|
||||
|
||||
func (db *actions) CommitRepo(ctx context.Context, opts CommitRepoOptions) error {
|
||||
err := NewReposStore(db.DB).Touch(ctx, opts.Repo.ID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "touch repository")
|
||||
}
|
||||
|
||||
pusher, err := NewUsersStore(db.DB).GetByUsername(ctx, opts.PusherName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get pusher [name: %s]", opts.PusherName)
|
||||
}
|
||||
|
||||
isNewRef := opts.OldCommitID == git.EmptyID
|
||||
isDelRef := opts.NewCommitID == git.EmptyID
|
||||
|
||||
// If not the first commit, set the compare URL.
|
||||
if !isNewRef && !isDelRef {
|
||||
opts.Commits.CompareURL = repoutil.CompareCommitsPath(opts.Owner.Name, opts.Repo.Name, opts.OldCommitID, opts.NewCommitID)
|
||||
}
|
||||
|
||||
refName := git.RefShortName(opts.RefFullName)
|
||||
action := &Action{
|
||||
ActUserID: pusher.ID,
|
||||
ActUserName: pusher.Name,
|
||||
RepoID: opts.Repo.ID,
|
||||
RepoUserName: opts.Owner.Name,
|
||||
RepoName: opts.Repo.Name,
|
||||
RefName: refName,
|
||||
IsPrivate: opts.Repo.IsPrivate || opts.Repo.IsUnlisted,
|
||||
}
|
||||
|
||||
apiRepo := opts.Repo.APIFormat(opts.Owner)
|
||||
apiPusher := pusher.APIFormat()
|
||||
if isDelRef {
|
||||
err = PrepareWebhooks(
|
||||
opts.Repo,
|
||||
HOOK_EVENT_DELETE,
|
||||
&api.DeletePayload{
|
||||
Ref: refName,
|
||||
RefType: "branch",
|
||||
PusherType: api.PUSHER_TYPE_USER,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "prepare webhooks for delete branch")
|
||||
}
|
||||
|
||||
action.OpType = ActionDeleteBranch
|
||||
err = db.notifyWatchers(ctx, action)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "notify watchers")
|
||||
}
|
||||
|
||||
// Delete branch doesn't have anything to push or compare
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only update issues via commits when internal issue tracker is enabled
|
||||
if opts.Repo.EnableIssues && !opts.Repo.EnableExternalTracker {
|
||||
if err = updateCommitReferencesToIssues(pusher, opts.Repo, opts.Commits.Commits); err != nil {
|
||||
log.Error("update commit references to issues: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.UI.FeedMaxCommitNum > 0 && len(opts.Commits.Commits) > conf.UI.FeedMaxCommitNum {
|
||||
opts.Commits.Commits = opts.Commits.Commits[:conf.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
data, err := jsoniter.Marshal(opts.Commits)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshal JSON")
|
||||
}
|
||||
action.Content = string(data)
|
||||
|
||||
var compareURL string
|
||||
if isNewRef {
|
||||
err = PrepareWebhooks(
|
||||
opts.Repo,
|
||||
HOOK_EVENT_CREATE,
|
||||
&api.CreatePayload{
|
||||
Ref: refName,
|
||||
RefType: "branch",
|
||||
DefaultBranch: opts.Repo.DefaultBranch,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "prepare webhooks for new branch")
|
||||
}
|
||||
|
||||
action.OpType = ActionCreateBranch
|
||||
err = db.notifyWatchers(ctx, action)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "notify watchers")
|
||||
}
|
||||
} else {
|
||||
compareURL = conf.Server.ExternalURL + opts.Commits.CompareURL
|
||||
}
|
||||
|
||||
commits, err := opts.Commits.APIFormat(ctx,
|
||||
NewUsersStore(db.DB),
|
||||
repoutil.RepositoryPath(opts.Owner.Name, opts.Repo.Name),
|
||||
repoutil.HTMLURL(opts.Owner.Name, opts.Repo.Name),
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "convert commits to API format")
|
||||
}
|
||||
|
||||
err = PrepareWebhooks(
|
||||
opts.Repo,
|
||||
HOOK_EVENT_PUSH,
|
||||
&api.PushPayload{
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: compareURL,
|
||||
Commits: commits,
|
||||
Repo: apiRepo,
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "prepare webhooks for new commit")
|
||||
}
|
||||
|
||||
action.OpType = ActionCommitRepo
|
||||
err = db.notifyWatchers(ctx, action)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "notify watchers")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PushTagOptions struct {
|
||||
Owner *User
|
||||
Repo *Repository
|
||||
PusherName string
|
||||
RefFullName string
|
||||
NewCommitID string
|
||||
}
|
||||
|
||||
func (db *actions) PushTag(ctx context.Context, opts PushTagOptions) error {
|
||||
err := NewReposStore(db.DB).Touch(ctx, opts.Repo.ID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "touch repository")
|
||||
}
|
||||
|
||||
pusher, err := NewUsersStore(db.DB).GetByUsername(ctx, opts.PusherName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "get pusher [name: %s]", opts.PusherName)
|
||||
}
|
||||
|
||||
refName := git.RefShortName(opts.RefFullName)
|
||||
action := &Action{
|
||||
ActUserID: pusher.ID,
|
||||
ActUserName: pusher.Name,
|
||||
RepoID: opts.Repo.ID,
|
||||
RepoUserName: opts.Owner.Name,
|
||||
RepoName: opts.Repo.Name,
|
||||
RefName: refName,
|
||||
IsPrivate: opts.Repo.IsPrivate || opts.Repo.IsUnlisted,
|
||||
}
|
||||
|
||||
apiRepo := opts.Repo.APIFormat(opts.Owner)
|
||||
apiPusher := pusher.APIFormat()
|
||||
if opts.NewCommitID == git.EmptyID {
|
||||
err = PrepareWebhooks(
|
||||
opts.Repo,
|
||||
HOOK_EVENT_DELETE,
|
||||
&api.DeletePayload{
|
||||
Ref: refName,
|
||||
RefType: "tag",
|
||||
PusherType: api.PUSHER_TYPE_USER,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "prepare webhooks for delete tag")
|
||||
}
|
||||
|
||||
action.OpType = ActionDeleteTag
|
||||
err = db.notifyWatchers(ctx, action)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "notify watchers")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err = PrepareWebhooks(
|
||||
opts.Repo,
|
||||
HOOK_EVENT_CREATE,
|
||||
&api.CreatePayload{
|
||||
Ref: refName,
|
||||
RefType: "tag",
|
||||
Sha: opts.NewCommitID,
|
||||
DefaultBranch: opts.Repo.DefaultBranch,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "prepare webhooks for new tag")
|
||||
}
|
||||
|
||||
action.OpType = ActionPushTag
|
||||
err = db.notifyWatchers(ctx, action)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "notify watchers")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ActionType is the type of an action.
|
||||
type ActionType int
|
||||
|
||||
// ⚠️ WARNING: Only append to the end of list to maintain backward compatibility.
|
||||
const (
|
||||
ActionCreateRepo ActionType = iota + 1 // 1
|
||||
ActionRenameRepo // 2
|
||||
ActionStarRepo // 3
|
||||
ActionWatchRepo // 4
|
||||
ActionCommitRepo // 5
|
||||
ActionCreateIssue // 6
|
||||
ActionCreatePullRequest // 7
|
||||
ActionTransferRepo // 8
|
||||
ActionPushTag // 9
|
||||
ActionCommentIssue // 10
|
||||
ActionMergePullRequest // 11
|
||||
ActionCloseIssue // 12
|
||||
ActionReopenIssue // 13
|
||||
ActionClosePullRequest // 14
|
||||
ActionReopenPullRequest // 15
|
||||
ActionCreateBranch // 16
|
||||
ActionDeleteBranch // 17
|
||||
ActionDeleteTag // 18
|
||||
ActionForkRepo // 19
|
||||
ActionMirrorSyncPush // 20
|
||||
ActionMirrorSyncCreate // 21
|
||||
ActionMirrorSyncDelete // 22
|
||||
)
|
||||
|
||||
// Action is a user operation to a repository. It implements template.Actioner
|
||||
// interface to be able to use it in template rendering.
|
||||
type Action struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
UserID int64 `gorm:"index"` // Receiver user ID
|
||||
OpType ActionType
|
||||
ActUserID int64 // Doer user ID
|
||||
ActUserName string // Doer user name
|
||||
ActAvatar string `xorm:"-" gorm:"-" json:"-"`
|
||||
RepoID int64 `xorm:"INDEX" gorm:"index"`
|
||||
RepoUserName string
|
||||
RepoName string
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"`
|
||||
Content string `xorm:"TEXT"`
|
||||
|
||||
Created time.Time `xorm:"-" gorm:"-" json:"-"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
// BeforeCreate implements the GORM create hook.
|
||||
func (a *Action) BeforeCreate(tx *gorm.DB) error {
|
||||
if a.CreatedUnix <= 0 {
|
||||
a.CreatedUnix = tx.NowFunc().Unix()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AfterFind implements the GORM query hook.
|
||||
func (a *Action) AfterFind(_ *gorm.DB) error {
|
||||
a.Created = time.Unix(a.CreatedUnix, 0).Local()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Action) GetOpType() int {
|
||||
return int(a.OpType)
|
||||
}
|
||||
|
||||
func (a *Action) GetActUserName() string {
|
||||
return a.ActUserName
|
||||
}
|
||||
|
||||
func (a *Action) ShortActUserName() string {
|
||||
return strutil.Ellipsis(a.ActUserName, 20)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoUserName() string {
|
||||
return a.RepoUserName
|
||||
}
|
||||
|
||||
func (a *Action) ShortRepoUserName() string {
|
||||
return strutil.Ellipsis(a.RepoUserName, 20)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoName() string {
|
||||
return a.RepoName
|
||||
}
|
||||
|
||||
func (a *Action) ShortRepoName() string {
|
||||
return strutil.Ellipsis(a.RepoName, 33)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoPath() string {
|
||||
return path.Join(a.RepoUserName, a.RepoName)
|
||||
}
|
||||
|
||||
func (a *Action) ShortRepoPath() string {
|
||||
return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoLink() string {
|
||||
if conf.Server.Subpath != "" {
|
||||
return path.Join(conf.Server.Subpath, a.GetRepoPath())
|
||||
}
|
||||
return "/" + a.GetRepoPath()
|
||||
}
|
||||
|
||||
func (a *Action) GetBranch() string {
|
||||
return a.RefName
|
||||
}
|
||||
|
||||
func (a *Action) GetContent() string {
|
||||
return a.Content
|
||||
}
|
||||
|
||||
func (a *Action) GetCreate() time.Time {
|
||||
return a.Created
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
return strings.SplitN(a.Content, "|", 2)
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueTitle() string {
|
||||
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("Failed to get issue title [repo_id: %d, index: %d]: %v", a.RepoID, index, err)
|
||||
return "error getting issue"
|
||||
}
|
||||
return issue.Title
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueContent() string {
|
||||
index, _ := strconv.ParseInt(a.GetIssueInfos()[0], 10, 64)
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error("Failed to get issue content [repo_id: %d, index: %d]: %v", a.RepoID, index, err)
|
||||
return "error getting issue"
|
||||
}
|
||||
return issue.Content
|
||||
}
|
||||
|
||||
// PushCommit contains information of a pushed commit.
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
CommitterEmail string
|
||||
CommitterName string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
// PushCommits is a list of pushed commits.
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
CompareURL string
|
||||
|
||||
avatars map[string]string
|
||||
}
|
||||
|
||||
// NewPushCommits returns a new PushCommits.
|
||||
func NewPushCommits() *PushCommits {
|
||||
return &PushCommits{
|
||||
avatars: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (pcs *PushCommits) APIFormat(ctx context.Context, usersStore UsersStore, repoPath, repoURL string) ([]*api.PayloadCommit, error) {
|
||||
// NOTE: We cache query results in case there are many commits in a single push.
|
||||
usernameByEmail := make(map[string]string)
|
||||
getUsernameByEmail := func(email string) (string, error) {
|
||||
username, ok := usernameByEmail[email]
|
||||
if ok {
|
||||
return username, nil
|
||||
}
|
||||
|
||||
user, err := usersStore.GetByEmail(ctx, email)
|
||||
if err != nil {
|
||||
if IsErrUserNotExist(err) {
|
||||
usernameByEmail[email] = ""
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
usernameByEmail[email] = user.Name
|
||||
return user.Name, nil
|
||||
}
|
||||
|
||||
commits := make([]*api.PayloadCommit, len(pcs.Commits))
|
||||
for i, commit := range pcs.Commits {
|
||||
authorUsername, err := getUsernameByEmail(commit.AuthorEmail)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get author username")
|
||||
}
|
||||
|
||||
committerUsername, err := getUsernameByEmail(commit.CommitterEmail)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get committer username")
|
||||
}
|
||||
|
||||
nameStatus := &git.NameStatus{}
|
||||
if !testutil.InTest {
|
||||
nameStatus, err = git.ShowNameStatus(repoPath, commit.Sha1)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "show name status [commit_sha1: %s]", commit.Sha1)
|
||||
}
|
||||
}
|
||||
|
||||
commits[i] = &api.PayloadCommit{
|
||||
ID: commit.Sha1,
|
||||
Message: commit.Message,
|
||||
URL: fmt.Sprintf("%s/commit/%s", repoURL, commit.Sha1),
|
||||
Author: &api.PayloadUser{
|
||||
Name: commit.AuthorName,
|
||||
Email: commit.AuthorEmail,
|
||||
UserName: authorUsername,
|
||||
},
|
||||
Committer: &api.PayloadUser{
|
||||
Name: commit.CommitterName,
|
||||
Email: commit.CommitterEmail,
|
||||
UserName: committerUsername,
|
||||
},
|
||||
Added: nameStatus.Added,
|
||||
Removed: nameStatus.Removed,
|
||||
Modified: nameStatus.Modified,
|
||||
Timestamp: commit.Timestamp,
|
||||
}
|
||||
}
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
// AvatarLink tries to match user in database with email in order to show custom
|
||||
// avatars, and falls back to general avatar link.
|
||||
//
|
||||
// FIXME: This method does not belong to PushCommits, should be a pure template
|
||||
// function.
|
||||
func (pcs *PushCommits) AvatarLink(email string) string {
|
||||
_, ok := pcs.avatars[email]
|
||||
if !ok {
|
||||
u, err := Users.GetByEmail(context.Background(), email)
|
||||
if err != nil {
|
||||
pcs.avatars[email] = tool.AvatarLink(email)
|
||||
if !IsErrUserNotExist(err) {
|
||||
log.Error("Failed to get user [email: %s]: %v", email, err)
|
||||
}
|
||||
} else {
|
||||
pcs.avatars[email] = u.RelAvatarLink()
|
||||
}
|
||||
}
|
||||
|
||||
return pcs.avatars[email]
|
||||
}
|
||||
872
internal/db/actions_test.go
Normal file
872
internal/db/actions_test.go
Normal file
@@ -0,0 +1,872 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/dbtest"
|
||||
)
|
||||
|
||||
func TestIssueReferencePattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
message string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "no match",
|
||||
message: "Hello world!",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "contains issue numbers",
|
||||
message: "#123 is fixed, and #456 is WIP",
|
||||
want: []string{"#123", " #456"},
|
||||
},
|
||||
{
|
||||
name: "contains full issue references",
|
||||
message: "#123 is fixed, and user/repo#456 is WIP",
|
||||
want: []string{"#123", " user/repo#456"},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := issueReferencePattern.FindAllString(test.message, -1)
|
||||
assert.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAction_BeforeCreate(t *testing.T) {
|
||||
now := time.Now()
|
||||
db := &gorm.DB{
|
||||
Config: &gorm.Config{
|
||||
SkipDefaultTransaction: true,
|
||||
NowFunc: func() time.Time {
|
||||
return now
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("CreatedUnix has been set", func(t *testing.T) {
|
||||
action := &Action{CreatedUnix: 1}
|
||||
_ = action.BeforeCreate(db)
|
||||
assert.Equal(t, int64(1), action.CreatedUnix)
|
||||
})
|
||||
|
||||
t.Run("CreatedUnix has not been set", func(t *testing.T) {
|
||||
action := &Action{}
|
||||
_ = action.BeforeCreate(db)
|
||||
assert.Equal(t, db.NowFunc().Unix(), action.CreatedUnix)
|
||||
})
|
||||
}
|
||||
|
||||
func TestActions(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(Action), new(User), new(Repository), new(EmailAddress), new(Watch)}
|
||||
db := &actions{
|
||||
DB: dbtest.NewDB(t, "actions", tables...),
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
test func(*testing.T, *actions)
|
||||
}{
|
||||
{"CommitRepo", actionsCommitRepo},
|
||||
{"ListByOrganization", actionsListByOrganization},
|
||||
{"ListByUser", actionsListByUser},
|
||||
{"MergePullRequest", actionsMergePullRequest},
|
||||
{"MirrorSyncCreate", actionsMirrorSyncCreate},
|
||||
{"MirrorSyncDelete", actionsMirrorSyncDelete},
|
||||
{"MirrorSyncPush", actionsMirrorSyncPush},
|
||||
{"NewRepo", actionsNewRepo},
|
||||
{"PushTag", actionsPushTag},
|
||||
{"RenameRepo", actionsRenameRepo},
|
||||
{"TransferRepo", actionsTransferRepo},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := clearTables(t, db.DB, tables...)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
tc.test(t, db)
|
||||
})
|
||||
if t.Failed() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func actionsCommitRepo(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Unix(1588568886, 0).UTC()
|
||||
|
||||
t.Run("new commit", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).WithContext(ctx).Delete(new(Action)).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
err = db.CommitRepo(ctx,
|
||||
CommitRepoOptions{
|
||||
PusherName: alice.Name,
|
||||
Owner: alice,
|
||||
Repo: repo,
|
||||
RefFullName: "refs/heads/main",
|
||||
OldCommitID: "ca82a6dff817ec66f44342007202690a93763949",
|
||||
NewCommitID: "085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7",
|
||||
Commits: CommitsToPushCommits(
|
||||
[]*git.Commit{
|
||||
{
|
||||
ID: git.MustIDFromString("085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7"),
|
||||
Author: &git.Signature{
|
||||
Name: "alice",
|
||||
Email: "alice@example.com",
|
||||
When: now,
|
||||
},
|
||||
Committer: &git.Signature{
|
||||
Name: "alice",
|
||||
Email: "alice@example.com",
|
||||
When: now,
|
||||
},
|
||||
Message: "A random commit",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionCommitRepo,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
Content: `{"Len":1,"Commits":[{"Sha1":"085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7","Message":"A random commit","AuthorEmail":"alice@example.com","AuthorName":"alice","CommitterEmail":"alice@example.com","CommitterName":"alice","Timestamp":"2020-05-04T05:08:06Z"}],"CompareURL":"alice/example/compare/ca82a6dff817ec66f44342007202690a93763949...085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7"}`,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("new ref", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).WithContext(ctx).Delete(new(Action)).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
err = db.CommitRepo(ctx,
|
||||
CommitRepoOptions{
|
||||
PusherName: alice.Name,
|
||||
Owner: alice,
|
||||
Repo: repo,
|
||||
RefFullName: "refs/heads/main",
|
||||
OldCommitID: git.EmptyID,
|
||||
NewCommitID: "085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7",
|
||||
Commits: CommitsToPushCommits(
|
||||
[]*git.Commit{
|
||||
{
|
||||
ID: git.MustIDFromString("085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7"),
|
||||
Author: &git.Signature{
|
||||
Name: "alice",
|
||||
Email: "alice@example.com",
|
||||
When: now,
|
||||
},
|
||||
Committer: &git.Signature{
|
||||
Name: "alice",
|
||||
Email: "alice@example.com",
|
||||
When: now,
|
||||
},
|
||||
Message: "A random commit",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 2)
|
||||
got[0].ID = 0
|
||||
got[1].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionCommitRepo,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
Content: `{"Len":1,"Commits":[{"Sha1":"085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7","Message":"A random commit","AuthorEmail":"alice@example.com","AuthorName":"alice","CommitterEmail":"alice@example.com","CommitterName":"alice","Timestamp":"2020-05-04T05:08:06Z"}],"CompareURL":""}`,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionCreateBranch,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
Content: `{"Len":1,"Commits":[{"Sha1":"085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7","Message":"A random commit","AuthorEmail":"alice@example.com","AuthorName":"alice","CommitterEmail":"alice@example.com","CommitterName":"alice","Timestamp":"2020-05-04T05:08:06Z"}],"CompareURL":""}`,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
want[1].Created = time.Unix(want[1].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("delete ref", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).WithContext(ctx).Delete(new(Action)).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
err = db.CommitRepo(ctx,
|
||||
CommitRepoOptions{
|
||||
PusherName: alice.Name,
|
||||
Owner: alice,
|
||||
Repo: repo,
|
||||
RefFullName: "refs/heads/main",
|
||||
OldCommitID: "ca82a6dff817ec66f44342007202690a93763949",
|
||||
NewCommitID: git.EmptyID,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionDeleteBranch,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func actionsListByOrganization(t *testing.T, db *actions) {
|
||||
if os.Getenv("GOGS_DATABASE_TYPE") != "postgres" {
|
||||
t.Skip("Skipping testing with not using PostgreSQL")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
conf.SetMockUI(t,
|
||||
conf.UIOpts{
|
||||
User: conf.UIUserOpts{
|
||||
NewsFeedPagingNum: 20,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
orgID int64
|
||||
actorID int64
|
||||
afterID int64
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "no afterID",
|
||||
orgID: 1,
|
||||
actorID: 1,
|
||||
afterID: 0,
|
||||
want: `SELECT * FROM "action" WHERE user_id = 1 AND (true OR id < 0) AND repo_id IN (SELECT repository.id FROM "repository" JOIN team_repo ON repository.id = team_repo.repo_id WHERE team_repo.team_id IN (SELECT team_id FROM "team_user" WHERE team_user.org_id = 1 AND uid = 1) OR (repository.is_private = false AND repository.is_unlisted = false)) ORDER BY id DESC LIMIT 20`,
|
||||
},
|
||||
{
|
||||
name: "has afterID",
|
||||
orgID: 1,
|
||||
actorID: 1,
|
||||
afterID: 5,
|
||||
want: `SELECT * FROM "action" WHERE user_id = 1 AND (false OR id < 5) AND repo_id IN (SELECT repository.id FROM "repository" JOIN team_repo ON repository.id = team_repo.repo_id WHERE team_repo.team_id IN (SELECT team_id FROM "team_user" WHERE team_user.org_id = 1 AND uid = 1) OR (repository.is_private = false AND repository.is_unlisted = false)) ORDER BY id DESC LIMIT 20`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := db.DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||
return NewActionsStore(tx).(*actions).listByOrganization(ctx, test.orgID, test.actorID, test.afterID).Find(new(Action))
|
||||
})
|
||||
assert.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func actionsListByUser(t *testing.T, db *actions) {
|
||||
if os.Getenv("GOGS_DATABASE_TYPE") != "postgres" {
|
||||
t.Skip("Skipping testing with not using PostgreSQL")
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
conf.SetMockUI(t,
|
||||
conf.UIOpts{
|
||||
User: conf.UIUserOpts{
|
||||
NewsFeedPagingNum: 20,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
userID int64
|
||||
actorID int64
|
||||
afterID int64
|
||||
isProfile bool
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "same user no afterID not in profile",
|
||||
userID: 1,
|
||||
actorID: 1,
|
||||
afterID: 0,
|
||||
isProfile: false,
|
||||
want: `SELECT * FROM "action" WHERE user_id = 1 AND (true OR id < 0) AND (true OR (is_private = false AND act_user_id = 1)) ORDER BY id DESC LIMIT 20`,
|
||||
},
|
||||
{
|
||||
name: "same user no afterID in profile",
|
||||
userID: 1,
|
||||
actorID: 1,
|
||||
afterID: 0,
|
||||
isProfile: true,
|
||||
want: `SELECT * FROM "action" WHERE user_id = 1 AND (true OR id < 0) AND (true OR (is_private = false AND act_user_id = 1)) ORDER BY id DESC LIMIT 20`,
|
||||
},
|
||||
{
|
||||
name: "same user has afterID not in profile",
|
||||
userID: 1,
|
||||
actorID: 1,
|
||||
afterID: 5,
|
||||
isProfile: false,
|
||||
want: `SELECT * FROM "action" WHERE user_id = 1 AND (false OR id < 5) AND (true OR (is_private = false AND act_user_id = 1)) ORDER BY id DESC LIMIT 20`,
|
||||
},
|
||||
{
|
||||
name: "different user no afterID in profile",
|
||||
userID: 1,
|
||||
actorID: 2,
|
||||
afterID: 0,
|
||||
isProfile: true,
|
||||
want: `SELECT * FROM "action" WHERE user_id = 1 AND (true OR id < 0) AND (false OR (is_private = false AND act_user_id = 1)) ORDER BY id DESC LIMIT 20`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := db.DB.ToSQL(func(tx *gorm.DB) *gorm.DB {
|
||||
return NewActionsStore(tx).(*actions).listByUser(ctx, test.userID, test.actorID, test.afterID, test.isProfile).Find(new(Action))
|
||||
})
|
||||
assert.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func actionsMergePullRequest(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.MergePullRequest(ctx,
|
||||
alice,
|
||||
alice,
|
||||
repo,
|
||||
&Issue{
|
||||
Index: 1,
|
||||
Title: "Fix issue 1",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionMergePullRequest,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: false,
|
||||
Content: `1|Fix issue 1`,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func actionsMirrorSyncCreate(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.MirrorSyncCreate(ctx,
|
||||
alice,
|
||||
repo,
|
||||
"main",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionMirrorSyncCreate,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func actionsMirrorSyncDelete(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.MirrorSyncDelete(ctx,
|
||||
alice,
|
||||
repo,
|
||||
"main",
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionMirrorSyncDelete,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func actionsMirrorSyncPush(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
now := time.Unix(1588568886, 0).UTC()
|
||||
err = db.MirrorSyncPush(ctx,
|
||||
MirrorSyncPushOptions{
|
||||
Owner: alice,
|
||||
Repo: repo,
|
||||
RefName: "main",
|
||||
OldCommitID: "ca82a6dff817ec66f44342007202690a93763949",
|
||||
NewCommitID: "085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7",
|
||||
Commits: CommitsToPushCommits(
|
||||
[]*git.Commit{
|
||||
{
|
||||
ID: git.MustIDFromString("085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7"),
|
||||
Author: &git.Signature{
|
||||
Name: "alice",
|
||||
Email: "alice@example.com",
|
||||
When: now,
|
||||
},
|
||||
Committer: &git.Signature{
|
||||
Name: "alice",
|
||||
Email: "alice@example.com",
|
||||
When: now,
|
||||
},
|
||||
Message: "A random commit",
|
||||
},
|
||||
},
|
||||
),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionMirrorSyncPush,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
Content: `{"Len":1,"Commits":[{"Sha1":"085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7","Message":"A random commit","AuthorEmail":"alice@example.com","AuthorName":"alice","CommitterEmail":"alice@example.com","CommitterName":"alice","Timestamp":"2020-05-04T05:08:06Z"}],"CompareURL":"alice/example/compare/ca82a6dff817ec66f44342007202690a93763949...085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7"}`,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func actionsNewRepo(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("new repo", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).WithContext(ctx).Delete(new(Action)).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
err = db.NewRepo(ctx, alice, alice, repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionCreateRepo,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("fork repo", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).WithContext(ctx).Delete(new(Action)).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
repo.IsFork = true
|
||||
err = db.NewRepo(ctx, alice, alice, repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionForkRepo,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func actionsPushTag(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("new tag", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).WithContext(ctx).Delete(new(Action)).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
err = db.PushTag(ctx,
|
||||
PushTagOptions{
|
||||
Owner: alice,
|
||||
Repo: repo,
|
||||
PusherName: alice.Name,
|
||||
RefFullName: "refs/tags/v1.0.0",
|
||||
NewCommitID: "085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionPushTag,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "v1.0.0",
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("delete tag", func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).WithContext(ctx).Delete(new(Action)).Error
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
err = db.PushTag(ctx,
|
||||
PushTagOptions{
|
||||
Owner: alice,
|
||||
Repo: repo,
|
||||
PusherName: alice.Name,
|
||||
RefFullName: "refs/tags/v1.0.0",
|
||||
NewCommitID: git.EmptyID,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionDeleteTag,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: "v1.0.0",
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func actionsRenameRepo(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.RenameRepo(ctx, alice, alice, "oldExample", repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionRenameRepo,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: alice.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: false,
|
||||
Content: "oldExample",
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func actionsTransferRepo(t *testing.T, db *actions) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := NewUsersStore(db.DB).Create(ctx, "alice", "alice@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
bob, err := NewUsersStore(db.DB).Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
repo, err := NewReposStore(db.DB).Create(ctx,
|
||||
alice.ID,
|
||||
CreateRepoOptions{
|
||||
Name: "example",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.TransferRepo(ctx, alice, alice, bob, repo)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := db.ListByUser(ctx, alice.ID, alice.ID, 0, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, got, 1)
|
||||
got[0].ID = 0
|
||||
|
||||
want := []*Action{
|
||||
{
|
||||
UserID: alice.ID,
|
||||
OpType: ActionTransferRepo,
|
||||
ActUserID: alice.ID,
|
||||
ActUserName: alice.Name,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: bob.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: false,
|
||||
Content: "alice/example",
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
}
|
||||
want[0].Created = time.Unix(want[0].CreatedUnix, 0)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
@@ -221,7 +221,7 @@ func importTable(ctx context.Context, db *gorm.DB, table interface{}, r io.Reade
|
||||
// PostgreSQL needs manually reset table sequence for auto increment keys
|
||||
if conf.UsePostgreSQL && !skipResetIDSeq[rawTableName] {
|
||||
seqName := rawTableName + "_id_seq"
|
||||
if _, err = x.Context(ctx).Exec(fmt.Sprintf(`SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM "%s"), 1), false);`, seqName, rawTableName)); err != nil {
|
||||
if err = db.WithContext(ctx).Exec(fmt.Sprintf(`SELECT setval('%s', COALESCE((SELECT MAX(id)+1 FROM "%s"), 1), false)`, seqName, rawTableName)).Error; err != nil {
|
||||
return errors.Wrapf(err, "reset table %q.%q", rawTableName, seqName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,10 @@ func TestDumpAndImport(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
if len(Tables) != 4 {
|
||||
t.Fatalf("New table has added (want 4 got %d), please add new tests for the table and update this check", len(Tables))
|
||||
if len(Tables) != 5 {
|
||||
t.Fatalf("New table has added (want 5 got %d), please add new tests for the table and update this check", len(Tables))
|
||||
}
|
||||
|
||||
db := dbtest.NewDB(t, "dumpAndImport", Tables...)
|
||||
@@ -90,6 +89,48 @@ func setupDBToDump(t *testing.T, db *gorm.DB) {
|
||||
CreatedUnix: 1588568886,
|
||||
},
|
||||
|
||||
&Action{
|
||||
ID: 1,
|
||||
UserID: 1,
|
||||
OpType: ActionCreateBranch,
|
||||
ActUserID: 1,
|
||||
ActUserName: "alice",
|
||||
RepoID: 1,
|
||||
RepoUserName: "alice",
|
||||
RepoName: "example",
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
Content: `{"Len":1,"Commits":[],"CompareURL":""}`,
|
||||
CreatedUnix: 1588568886,
|
||||
},
|
||||
&Action{
|
||||
ID: 2,
|
||||
UserID: 1,
|
||||
OpType: ActionCommitRepo,
|
||||
ActUserID: 1,
|
||||
ActUserName: "alice",
|
||||
RepoID: 1,
|
||||
RepoUserName: "alice",
|
||||
RepoName: "example",
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
Content: `{"Len":1,"Commits":[],"CompareURL":""}`,
|
||||
CreatedUnix: 1588568886,
|
||||
},
|
||||
&Action{
|
||||
ID: 3,
|
||||
UserID: 1,
|
||||
OpType: ActionDeleteBranch,
|
||||
ActUserID: 1,
|
||||
ActUserName: "alice",
|
||||
RepoID: 1,
|
||||
RepoUserName: "alice",
|
||||
RepoName: "example",
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
CreatedUnix: 1588568886,
|
||||
},
|
||||
|
||||
&LFSObject{
|
||||
RepoID: 1,
|
||||
OID: "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
|
||||
|
||||
@@ -172,11 +172,11 @@ func (cmt *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue)
|
||||
}
|
||||
|
||||
switch opType {
|
||||
case ACTION_COMMENT_ISSUE:
|
||||
case ActionCommentIssue:
|
||||
issue.Content = cmt.Content
|
||||
case ACTION_CLOSE_ISSUE:
|
||||
case ActionCloseIssue:
|
||||
issue.Content = fmt.Sprintf("Closed #%d", issue.Index)
|
||||
case ACTION_REOPEN_ISSUE:
|
||||
case ActionReopenIssue:
|
||||
issue.Content = fmt.Sprintf("Reopened #%d", issue.Index)
|
||||
}
|
||||
if err = mailIssueCommentToParticipants(issue, cmt.Poster, mentions); err != nil {
|
||||
@@ -216,7 +216,7 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
|
||||
// Check comment type.
|
||||
switch opts.Type {
|
||||
case COMMENT_TYPE_COMMENT:
|
||||
act.OpType = ACTION_COMMENT_ISSUE
|
||||
act.OpType = ActionCommentIssue
|
||||
|
||||
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
|
||||
return nil, err
|
||||
@@ -245,9 +245,9 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
|
||||
}
|
||||
|
||||
case COMMENT_TYPE_REOPEN:
|
||||
act.OpType = ACTION_REOPEN_ISSUE
|
||||
act.OpType = ActionReopenIssue
|
||||
if opts.Issue.IsPull {
|
||||
act.OpType = ACTION_REOPEN_PULL_REQUEST
|
||||
act.OpType = ActionReopenPullRequest
|
||||
}
|
||||
|
||||
if opts.Issue.IsPull {
|
||||
@@ -260,9 +260,9 @@ func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err
|
||||
}
|
||||
|
||||
case COMMENT_TYPE_CLOSE:
|
||||
act.OpType = ACTION_CLOSE_ISSUE
|
||||
act.OpType = ActionCloseIssue
|
||||
if opts.Issue.IsPull {
|
||||
act.OpType = ACTION_CLOSE_PULL_REQUEST
|
||||
act.OpType = ActionClosePullRequest
|
||||
}
|
||||
|
||||
if opts.Issue.IsPull {
|
||||
@@ -353,7 +353,7 @@ func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content stri
|
||||
Action: api.HOOK_ISSUE_COMMENT_CREATED,
|
||||
Issue: issue.APIFormat(),
|
||||
Comment: comment.APIFormat(),
|
||||
Repository: repo.APIFormat(nil),
|
||||
Repository: repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
|
||||
@@ -494,7 +494,7 @@ func UpdateComment(doer *User, c *Comment, oldContent string) (err error) {
|
||||
From: oldContent,
|
||||
},
|
||||
},
|
||||
Repository: c.Issue.Repo.APIFormat(nil),
|
||||
Repository: c.Issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [comment_id: %d]: %v", c.ID, err)
|
||||
@@ -544,7 +544,7 @@ func DeleteCommentByID(doer *User, id int64) error {
|
||||
Action: api.HOOK_ISSUE_COMMENT_DELETED,
|
||||
Issue: comment.Issue.APIFormat(),
|
||||
Comment: comment.APIFormat(),
|
||||
Repository: comment.Issue.Repo.APIFormat(nil),
|
||||
Repository: comment.Issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [comment_id: %d]: %v", comment.ID, err)
|
||||
|
||||
@@ -41,7 +41,7 @@ func newLogWriter() (logger.Writer, error) {
|
||||
//
|
||||
// NOTE: Lines are sorted in alphabetical order, each letter in its own line.
|
||||
var Tables = []interface{}{
|
||||
new(Access), new(AccessToken),
|
||||
new(Access), new(AccessToken), new(Action),
|
||||
new(LFSObject), new(LoginSource),
|
||||
}
|
||||
|
||||
@@ -119,12 +119,14 @@ func Init(w logger.Writer) (*gorm.DB, error) {
|
||||
|
||||
// Initialize stores, sorted in alphabetical order.
|
||||
AccessTokens = &accessTokens{DB: db}
|
||||
Actions = NewActionsStore(db)
|
||||
LoginSources = &loginSources{DB: db, files: sourceFiles}
|
||||
LFS = &lfs{DB: db}
|
||||
Perms = &perms{DB: db}
|
||||
Repos = &repos{DB: db}
|
||||
Repos = NewReposStore(db)
|
||||
TwoFactors = &twoFactors{DB: db}
|
||||
Users = &users{DB: db}
|
||||
Users = NewUsersStore(db)
|
||||
Watches = NewWatchesStore(db)
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
|
||||
Action: api.HOOK_ISSUE_LABEL_UPDATED,
|
||||
Index: issue.Index,
|
||||
PullRequest: issue.PullRequest.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
} else {
|
||||
@@ -245,7 +245,7 @@ func (issue *Issue) sendLabelUpdatedWebhook(doer *User) {
|
||||
Action: api.HOOK_ISSUE_LABEL_UPDATED,
|
||||
Index: issue.Index,
|
||||
Issue: issue.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
}
|
||||
@@ -350,7 +350,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
|
||||
Action: api.HOOK_ISSUE_LABEL_CLEARED,
|
||||
Index: issue.Index,
|
||||
PullRequest: issue.PullRequest.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
} else {
|
||||
@@ -358,7 +358,7 @@ func (issue *Issue) ClearLabels(doer *User) (err error) {
|
||||
Action: api.HOOK_ISSUE_LABEL_CLEARED,
|
||||
Index: issue.Index,
|
||||
Issue: issue.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
}
|
||||
@@ -477,7 +477,7 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
|
||||
apiPullRequest := &api.PullRequestPayload{
|
||||
Index: issue.Index,
|
||||
PullRequest: issue.PullRequest.APIFormat(),
|
||||
Repository: repo.APIFormat(nil),
|
||||
Repository: repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}
|
||||
if isClosed {
|
||||
@@ -490,7 +490,7 @@ func (issue *Issue) ChangeStatus(doer *User, repo *Repository, isClosed bool) (e
|
||||
apiIssues := &api.IssuesPayload{
|
||||
Index: issue.Index,
|
||||
Issue: issue.APIFormat(),
|
||||
Repository: repo.APIFormat(nil),
|
||||
Repository: repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}
|
||||
if isClosed {
|
||||
@@ -525,7 +525,7 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
|
||||
From: oldTitle,
|
||||
},
|
||||
},
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
} else {
|
||||
@@ -538,7 +538,7 @@ func (issue *Issue) ChangeTitle(doer *User, title string) (err error) {
|
||||
From: oldTitle,
|
||||
},
|
||||
},
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
}
|
||||
@@ -567,7 +567,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
|
||||
From: oldContent,
|
||||
},
|
||||
},
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
} else {
|
||||
@@ -580,7 +580,7 @@ func (issue *Issue) ChangeContent(doer *User, content string) (err error) {
|
||||
From: oldContent,
|
||||
},
|
||||
},
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
}
|
||||
@@ -610,7 +610,7 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
|
||||
apiPullRequest := &api.PullRequestPayload{
|
||||
Index: issue.Index,
|
||||
PullRequest: issue.PullRequest.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}
|
||||
if isRemoveAssignee {
|
||||
@@ -623,7 +623,7 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
|
||||
apiIssues := &api.IssuesPayload{
|
||||
Index: issue.Index,
|
||||
Issue: issue.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}
|
||||
if isRemoveAssignee {
|
||||
@@ -763,7 +763,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
|
||||
if err = NotifyWatchers(&Action{
|
||||
ActUserID: issue.Poster.ID,
|
||||
ActUserName: issue.Poster.Name,
|
||||
OpType: ACTION_CREATE_ISSUE,
|
||||
OpType: ActionCreateIssue,
|
||||
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
@@ -780,7 +780,7 @@ func NewIssue(repo *Repository, issue *Issue, labelIDs []int64, uuids []string)
|
||||
Action: api.HOOK_ISSUE_OPENED,
|
||||
Index: issue.Index,
|
||||
Issue: issue.APIFormat(),
|
||||
Repository: repo.APIFormat(nil),
|
||||
Repository: repo.APIFormatLegacy(nil),
|
||||
Sender: issue.Poster.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
|
||||
@@ -33,8 +33,8 @@ var LFS LFSStore
|
||||
|
||||
// LFSObject is the relation between an LFS object and a repository.
|
||||
type LFSObject struct {
|
||||
RepoID int64 `gorm:"primary_key;auto_increment:false"`
|
||||
OID lfsutil.OID `gorm:"primary_key;column:oid"`
|
||||
RepoID int64 `gorm:"primaryKey;auto_increment:false"`
|
||||
OID lfsutil.OID `gorm:"primaryKey;column:oid"`
|
||||
Size int64 `gorm:"not null"`
|
||||
Storage lfsutil.Storage `gorm:"not null"`
|
||||
CreatedAt time.Time `gorm:"not null"`
|
||||
|
||||
@@ -21,7 +21,6 @@ func TestLFS(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(LFSObject)}
|
||||
|
||||
@@ -33,7 +33,7 @@ type loginSourceFilesStore interface {
|
||||
// Len returns number of login sources.
|
||||
Len() int
|
||||
// List returns a list of login sources filtered by options.
|
||||
List(opts ListLoginSourceOpts) []*LoginSource
|
||||
List(opts ListLoginSourceOptions) []*LoginSource
|
||||
// Update updates in-memory copy of the authentication source.
|
||||
Update(source *LoginSource)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func (s *loginSourceFiles) Len() int {
|
||||
return len(s.sources)
|
||||
}
|
||||
|
||||
func (s *loginSourceFiles) List(opts ListLoginSourceOpts) []*LoginSource {
|
||||
func (s *loginSourceFiles) List(opts ListLoginSourceOptions) []*LoginSource {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
|
||||
@@ -53,12 +53,12 @@ func TestLoginSourceFiles_List(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("list all sources", func(t *testing.T) {
|
||||
sources := store.List(ListLoginSourceOpts{})
|
||||
sources := store.List(ListLoginSourceOptions{})
|
||||
assert.Equal(t, 2, len(sources), "number of sources")
|
||||
})
|
||||
|
||||
t.Run("list only activated sources", func(t *testing.T) {
|
||||
sources := store.List(ListLoginSourceOpts{OnlyActivated: true})
|
||||
sources := store.List(ListLoginSourceOptions{OnlyActivated: true})
|
||||
assert.Equal(t, 1, len(sources), "number of sources")
|
||||
assert.Equal(t, int64(101), sources[0].ID)
|
||||
})
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
type LoginSourcesStore interface {
|
||||
// Create creates a new login source and persist to database. It returns
|
||||
// ErrLoginSourceAlreadyExist when a login source with same name already exists.
|
||||
Create(ctx context.Context, opts CreateLoginSourceOpts) (*LoginSource, error)
|
||||
Create(ctx context.Context, opts CreateLoginSourceOptions) (*LoginSource, error)
|
||||
// Count returns the total number of login sources.
|
||||
Count(ctx context.Context) int64
|
||||
// DeleteByID deletes a login source by given ID. It returns ErrLoginSourceInUse
|
||||
@@ -38,7 +38,7 @@ type LoginSourcesStore interface {
|
||||
// ErrLoginSourceNotExist when not found.
|
||||
GetByID(ctx context.Context, id int64) (*LoginSource, error)
|
||||
// List returns a list of login sources filtered by options.
|
||||
List(ctx context.Context, opts ListLoginSourceOpts) ([]*LoginSource, error)
|
||||
List(ctx context.Context, opts ListLoginSourceOptions) ([]*LoginSource, error)
|
||||
// ResetNonDefault clears default flag for all the other login sources.
|
||||
ResetNonDefault(ctx context.Context, source *LoginSource) error
|
||||
// Save persists all values of given login source to database or local file. The
|
||||
@@ -50,7 +50,7 @@ var LoginSources LoginSourcesStore
|
||||
|
||||
// LoginSource represents an external way for authorizing users.
|
||||
type LoginSource struct {
|
||||
ID int64
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
Type auth.Type
|
||||
Name string `xorm:"UNIQUE" gorm:"UNIQUE"`
|
||||
IsActived bool `xorm:"NOT NULL DEFAULT false" gorm:"NOT NULL"`
|
||||
@@ -189,7 +189,7 @@ type loginSources struct {
|
||||
files loginSourceFilesStore
|
||||
}
|
||||
|
||||
type CreateLoginSourceOpts struct {
|
||||
type CreateLoginSourceOptions struct {
|
||||
Type auth.Type
|
||||
Name string
|
||||
Activated bool
|
||||
@@ -210,7 +210,7 @@ func (err ErrLoginSourceAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("login source already exists: %v", err.args)
|
||||
}
|
||||
|
||||
func (db *loginSources) Create(ctx context.Context, opts CreateLoginSourceOpts) (*LoginSource, error) {
|
||||
func (db *loginSources) Create(ctx context.Context, opts CreateLoginSourceOptions) (*LoginSource, error) {
|
||||
err := db.WithContext(ctx).Where("name = ?", opts.Name).First(new(LoginSource)).Error
|
||||
if err == nil {
|
||||
return nil, ErrLoginSourceAlreadyExist{args: errutil.Args{"name": opts.Name}}
|
||||
@@ -274,12 +274,12 @@ func (db *loginSources) GetByID(ctx context.Context, id int64) (*LoginSource, er
|
||||
return source, nil
|
||||
}
|
||||
|
||||
type ListLoginSourceOpts struct {
|
||||
type ListLoginSourceOptions struct {
|
||||
// Whether to only include activated login sources.
|
||||
OnlyActivated bool
|
||||
}
|
||||
|
||||
func (db *loginSources) List(ctx context.Context, opts ListLoginSourceOpts) ([]*LoginSource, error) {
|
||||
func (db *loginSources) List(ctx context.Context, opts ListLoginSourceOptions) ([]*LoginSource, error) {
|
||||
var sources []*LoginSource
|
||||
query := db.WithContext(ctx).Order("id ASC")
|
||||
if opts.OnlyActivated {
|
||||
@@ -303,7 +303,7 @@ func (db *loginSources) ResetNonDefault(ctx context.Context, dflt *LoginSource)
|
||||
return err
|
||||
}
|
||||
|
||||
for _, source := range db.files.List(ListLoginSourceOpts{}) {
|
||||
for _, source := range db.files.List(ListLoginSourceOptions{}) {
|
||||
if source.File != nil && source.ID != dflt.ID {
|
||||
source.File.SetGeneral("is_default", "false")
|
||||
if err = source.File.Save(); err != nil {
|
||||
|
||||
@@ -81,7 +81,6 @@ func Test_loginSources(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(LoginSource), new(User)}
|
||||
@@ -119,7 +118,7 @@ func loginSourcesCreate(t *testing.T, db *loginSources) {
|
||||
|
||||
// Create first login source with name "GitHub"
|
||||
source, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub",
|
||||
Activated: true,
|
||||
@@ -138,7 +137,7 @@ func loginSourcesCreate(t *testing.T, db *loginSources) {
|
||||
assert.Equal(t, db.NowFunc().Format(time.RFC3339), source.Updated.UTC().Format(time.RFC3339))
|
||||
|
||||
// Try create second login source with same name should fail
|
||||
_, err = db.Create(ctx, CreateLoginSourceOpts{Name: source.Name})
|
||||
_, err = db.Create(ctx, CreateLoginSourceOptions{Name: source.Name})
|
||||
wantErr := ErrLoginSourceAlreadyExist{args: errutil.Args{"name": source.Name}}
|
||||
assert.Equal(t, wantErr, err)
|
||||
}
|
||||
@@ -148,7 +147,7 @@ func loginSourcesCount(t *testing.T, db *loginSources) {
|
||||
|
||||
// Create two login sources, one in database and one as source file.
|
||||
_, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub",
|
||||
Activated: true,
|
||||
@@ -172,7 +171,7 @@ func loginSourcesDeleteByID(t *testing.T, db *loginSources) {
|
||||
|
||||
t.Run("delete but in used", func(t *testing.T) {
|
||||
source, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub",
|
||||
Activated: true,
|
||||
@@ -186,7 +185,7 @@ func loginSourcesDeleteByID(t *testing.T, db *loginSources) {
|
||||
|
||||
// Create a user that uses this login source
|
||||
_, err = (&users{DB: db.DB}).Create(ctx, "alice", "",
|
||||
CreateUserOpts{
|
||||
CreateUserOptions{
|
||||
LoginSource: source.ID,
|
||||
},
|
||||
)
|
||||
@@ -206,7 +205,7 @@ func loginSourcesDeleteByID(t *testing.T, db *loginSources) {
|
||||
|
||||
// Create a login source with name "GitHub2"
|
||||
source, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub2",
|
||||
Activated: true,
|
||||
@@ -254,7 +253,7 @@ func loginSourcesGetByID(t *testing.T, db *loginSources) {
|
||||
|
||||
// Create a login source with name "GitHub"
|
||||
source, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub",
|
||||
Activated: true,
|
||||
@@ -278,7 +277,7 @@ func loginSourcesList(t *testing.T, db *loginSources) {
|
||||
ctx := context.Background()
|
||||
|
||||
mock := NewMockLoginSourceFilesStore()
|
||||
mock.ListFunc.SetDefaultHook(func(opts ListLoginSourceOpts) []*LoginSource {
|
||||
mock.ListFunc.SetDefaultHook(func(opts ListLoginSourceOptions) []*LoginSource {
|
||||
if opts.OnlyActivated {
|
||||
return []*LoginSource{
|
||||
{ID: 1},
|
||||
@@ -293,7 +292,7 @@ func loginSourcesList(t *testing.T, db *loginSources) {
|
||||
|
||||
// Create two login sources in database, one activated and the other one not
|
||||
_, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.PAM,
|
||||
Name: "PAM",
|
||||
Config: &pam.Config{
|
||||
@@ -303,7 +302,7 @@ func loginSourcesList(t *testing.T, db *loginSources) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
_, err = db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub",
|
||||
Activated: true,
|
||||
@@ -315,12 +314,12 @@ func loginSourcesList(t *testing.T, db *loginSources) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// List all login sources
|
||||
sources, err := db.List(ctx, ListLoginSourceOpts{})
|
||||
sources, err := db.List(ctx, ListLoginSourceOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 4, len(sources), "number of sources")
|
||||
|
||||
// Only list activated login sources
|
||||
sources, err = db.List(ctx, ListLoginSourceOpts{OnlyActivated: true})
|
||||
sources, err = db.List(ctx, ListLoginSourceOptions{OnlyActivated: true})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, len(sources), "number of sources")
|
||||
}
|
||||
@@ -329,7 +328,7 @@ func loginSourcesResetNonDefault(t *testing.T, db *loginSources) {
|
||||
ctx := context.Background()
|
||||
|
||||
mock := NewMockLoginSourceFilesStore()
|
||||
mock.ListFunc.SetDefaultHook(func(opts ListLoginSourceOpts) []*LoginSource {
|
||||
mock.ListFunc.SetDefaultHook(func(opts ListLoginSourceOptions) []*LoginSource {
|
||||
mockFile := NewMockLoginSourceFileStore()
|
||||
mockFile.SetGeneralFunc.SetDefaultHook(func(name, value string) {
|
||||
assert.Equal(t, "is_default", name)
|
||||
@@ -345,7 +344,7 @@ func loginSourcesResetNonDefault(t *testing.T, db *loginSources) {
|
||||
|
||||
// Create two login sources both have default on
|
||||
source1, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.PAM,
|
||||
Name: "PAM",
|
||||
Default: true,
|
||||
@@ -356,7 +355,7 @@ func loginSourcesResetNonDefault(t *testing.T, db *loginSources) {
|
||||
)
|
||||
require.NoError(t, err)
|
||||
source2, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub",
|
||||
Activated: true,
|
||||
@@ -388,7 +387,7 @@ func loginSourcesSave(t *testing.T, db *loginSources) {
|
||||
t.Run("save to database", func(t *testing.T) {
|
||||
// Create a login source with name "GitHub"
|
||||
source, err := db.Create(ctx,
|
||||
CreateLoginSourceOpts{
|
||||
CreateLoginSourceOptions{
|
||||
Type: auth.GitHub,
|
||||
Name: "GitHub",
|
||||
Activated: true,
|
||||
|
||||
@@ -54,6 +54,8 @@ var migrations = []Migration{
|
||||
|
||||
// v19 -> v20:v0.13.0
|
||||
NewMigration("migrate access tokens to store SHA56", migrateAccessTokenToSHA256),
|
||||
// v20 -> v21:v0.13.0
|
||||
NewMigration("add index to action.user_id", addIndexToActionUserID),
|
||||
}
|
||||
|
||||
// Migrate migrates the database schema and/or data to the current version.
|
||||
|
||||
19
internal/db/migrations/v21.go
Normal file
19
internal/db/migrations/v21.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func addIndexToActionUserID(db *gorm.DB) error {
|
||||
type action struct {
|
||||
UserID string `gorm:"index"`
|
||||
}
|
||||
if db.Migrator().HasIndex(&action{}, "UserID") {
|
||||
return nil
|
||||
}
|
||||
return db.Migrator().CreateIndex(&action{}, "UserID")
|
||||
}
|
||||
82
internal/db/migrations/v21_test.go
Normal file
82
internal/db/migrations/v21_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gogs.io/gogs/internal/dbtest"
|
||||
)
|
||||
|
||||
type actionPreV21 struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
UserID int64
|
||||
OpType int
|
||||
ActUserID int64
|
||||
ActUserName string
|
||||
RepoID int64 `gorm:"index"`
|
||||
RepoUserName string
|
||||
RepoName string
|
||||
RefName string
|
||||
IsPrivate bool `gorm:"not null;default:FALSE"`
|
||||
Content string
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (*actionPreV21) TableName() string {
|
||||
return "action"
|
||||
}
|
||||
|
||||
type actionV21 struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
UserID int64 `gorm:"index"`
|
||||
OpType int
|
||||
ActUserID int64
|
||||
ActUserName string
|
||||
RepoID int64 `gorm:"index"`
|
||||
RepoUserName string
|
||||
RepoName string
|
||||
RefName string
|
||||
IsPrivate bool `gorm:"not null;default:FALSE"`
|
||||
Content string
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (*actionV21) TableName() string {
|
||||
return "action"
|
||||
}
|
||||
|
||||
func TestAddIndexToActionUserID(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
db := dbtest.NewDB(t, "addIndexToActionUserID", new(actionPreV21))
|
||||
err := db.Create(
|
||||
&actionPreV21{
|
||||
ID: 1,
|
||||
UserID: 1,
|
||||
OpType: 1,
|
||||
ActUserID: 1,
|
||||
ActUserName: "alice",
|
||||
RepoID: 1,
|
||||
RepoUserName: "alice",
|
||||
RepoName: "example",
|
||||
RefName: "main",
|
||||
IsPrivate: false,
|
||||
CreatedUnix: db.NowFunc().Unix(),
|
||||
},
|
||||
).Error
|
||||
require.NoError(t, err)
|
||||
assert.False(t, db.Migrator().HasIndex(&actionV21{}, "UserID"))
|
||||
|
||||
err = addIndexToActionUserID(db)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, db.Migrator().HasIndex(&actionV21{}, "UserID"))
|
||||
}
|
||||
@@ -363,7 +363,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
|
||||
Action: hookAction,
|
||||
Index: issue.Index,
|
||||
PullRequest: issue.PullRequest.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
} else {
|
||||
@@ -371,7 +371,7 @@ func ChangeMilestoneAssign(doer *User, issue *Issue, oldMilestoneID int64) (err
|
||||
Action: hookAction,
|
||||
Index: issue.Index,
|
||||
Issue: issue.APIFormat(),
|
||||
Repository: issue.Repo.APIFormat(nil),
|
||||
Repository: issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -314,6 +315,8 @@ func MirrorUpdate() {
|
||||
// SyncMirrors checks and syncs mirrors.
|
||||
// TODO: sync more mirrors at same time.
|
||||
func SyncMirrors() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Start listening on new sync requests.
|
||||
for repoID := range MirrorQueue.Queue() {
|
||||
log.Trace("SyncMirrors [repo_id: %s]", repoID)
|
||||
@@ -358,8 +361,8 @@ func SyncMirrors() {
|
||||
|
||||
// Delete reference
|
||||
if result.newCommitID == gitShortEmptyID {
|
||||
if err = MirrorSyncDeleteAction(m.Repo, result.refName); err != nil {
|
||||
log.Error("MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err)
|
||||
if err = Actions.MirrorSyncDelete(ctx, m.Repo.MustOwner(), m.Repo, result.refName); err != nil {
|
||||
log.Error("Failed to create action for mirror sync delete [repo_id: %d]: %v", m.RepoID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -367,8 +370,8 @@ func SyncMirrors() {
|
||||
// New reference
|
||||
isNewRef := false
|
||||
if result.oldCommitID == gitShortEmptyID {
|
||||
if err = MirrorSyncCreateAction(m.Repo, result.refName); err != nil {
|
||||
log.Error("MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err)
|
||||
if err = Actions.MirrorSyncCreate(ctx, m.Repo.MustOwner(), m.Repo, result.refName); err != nil {
|
||||
log.Error("Failed to create action for mirror sync create [repo_id: %d]: %v", m.RepoID, err)
|
||||
continue
|
||||
}
|
||||
isNewRef = true
|
||||
@@ -416,13 +419,18 @@ func SyncMirrors() {
|
||||
newCommitID = refNewCommit.ID.String()
|
||||
}
|
||||
|
||||
if err = MirrorSyncPushAction(m.Repo, MirrorSyncPushActionOptions{
|
||||
RefName: result.refName,
|
||||
OldCommitID: oldCommitID,
|
||||
NewCommitID: newCommitID,
|
||||
Commits: CommitsToPushCommits(commits),
|
||||
}); err != nil {
|
||||
log.Error("MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err)
|
||||
err = Actions.MirrorSyncPush(ctx,
|
||||
MirrorSyncPushOptions{
|
||||
Owner: m.Repo.MustOwner(),
|
||||
Repo: m.Repo,
|
||||
RefName: result.refName,
|
||||
OldCommitID: oldCommitID,
|
||||
NewCommitID: newCommitID,
|
||||
Commits: CommitsToPushCommits(commits),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Error("Failed to create action for mirror sync push [repo_id: %d]: %v", m.RepoID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func NewMockLoginSourcesStore() *MockLoginSourcesStore {
|
||||
},
|
||||
},
|
||||
CreateFunc: &LoginSourcesStoreCreateFunc{
|
||||
defaultHook: func(context.Context, CreateLoginSourceOpts) (r0 *LoginSource, r1 error) {
|
||||
defaultHook: func(context.Context, CreateLoginSourceOptions) (r0 *LoginSource, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
@@ -66,7 +66,7 @@ func NewMockLoginSourcesStore() *MockLoginSourcesStore {
|
||||
},
|
||||
},
|
||||
ListFunc: &LoginSourcesStoreListFunc{
|
||||
defaultHook: func(context.Context, ListLoginSourceOpts) (r0 []*LoginSource, r1 error) {
|
||||
defaultHook: func(context.Context, ListLoginSourceOptions) (r0 []*LoginSource, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
@@ -94,7 +94,7 @@ func NewStrictMockLoginSourcesStore() *MockLoginSourcesStore {
|
||||
},
|
||||
},
|
||||
CreateFunc: &LoginSourcesStoreCreateFunc{
|
||||
defaultHook: func(context.Context, CreateLoginSourceOpts) (*LoginSource, error) {
|
||||
defaultHook: func(context.Context, CreateLoginSourceOptions) (*LoginSource, error) {
|
||||
panic("unexpected invocation of MockLoginSourcesStore.Create")
|
||||
},
|
||||
},
|
||||
@@ -109,7 +109,7 @@ func NewStrictMockLoginSourcesStore() *MockLoginSourcesStore {
|
||||
},
|
||||
},
|
||||
ListFunc: &LoginSourcesStoreListFunc{
|
||||
defaultHook: func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error) {
|
||||
defaultHook: func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error) {
|
||||
panic("unexpected invocation of MockLoginSourcesStore.List")
|
||||
},
|
||||
},
|
||||
@@ -260,15 +260,15 @@ func (c LoginSourcesStoreCountFuncCall) Results() []interface{} {
|
||||
// LoginSourcesStoreCreateFunc describes the behavior when the Create method
|
||||
// of the parent MockLoginSourcesStore instance is invoked.
|
||||
type LoginSourcesStoreCreateFunc struct {
|
||||
defaultHook func(context.Context, CreateLoginSourceOpts) (*LoginSource, error)
|
||||
hooks []func(context.Context, CreateLoginSourceOpts) (*LoginSource, error)
|
||||
defaultHook func(context.Context, CreateLoginSourceOptions) (*LoginSource, error)
|
||||
hooks []func(context.Context, CreateLoginSourceOptions) (*LoginSource, error)
|
||||
history []LoginSourcesStoreCreateFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Create delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockLoginSourcesStore) Create(v0 context.Context, v1 CreateLoginSourceOpts) (*LoginSource, error) {
|
||||
func (m *MockLoginSourcesStore) Create(v0 context.Context, v1 CreateLoginSourceOptions) (*LoginSource, error) {
|
||||
r0, r1 := m.CreateFunc.nextHook()(v0, v1)
|
||||
m.CreateFunc.appendCall(LoginSourcesStoreCreateFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
@@ -277,7 +277,7 @@ func (m *MockLoginSourcesStore) Create(v0 context.Context, v1 CreateLoginSourceO
|
||||
// SetDefaultHook sets function that is called when the Create method of the
|
||||
// parent MockLoginSourcesStore instance is invoked and the hook queue is
|
||||
// empty.
|
||||
func (f *LoginSourcesStoreCreateFunc) SetDefaultHook(hook func(context.Context, CreateLoginSourceOpts) (*LoginSource, error)) {
|
||||
func (f *LoginSourcesStoreCreateFunc) SetDefaultHook(hook func(context.Context, CreateLoginSourceOptions) (*LoginSource, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ func (f *LoginSourcesStoreCreateFunc) SetDefaultHook(hook func(context.Context,
|
||||
// Create method of the parent MockLoginSourcesStore 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 *LoginSourcesStoreCreateFunc) PushHook(hook func(context.Context, CreateLoginSourceOpts) (*LoginSource, error)) {
|
||||
func (f *LoginSourcesStoreCreateFunc) PushHook(hook func(context.Context, CreateLoginSourceOptions) (*LoginSource, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
@@ -294,19 +294,19 @@ func (f *LoginSourcesStoreCreateFunc) PushHook(hook func(context.Context, Create
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *LoginSourcesStoreCreateFunc) SetDefaultReturn(r0 *LoginSource, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, CreateLoginSourceOpts) (*LoginSource, error) {
|
||||
f.SetDefaultHook(func(context.Context, CreateLoginSourceOptions) (*LoginSource, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *LoginSourcesStoreCreateFunc) PushReturn(r0 *LoginSource, r1 error) {
|
||||
f.PushHook(func(context.Context, CreateLoginSourceOpts) (*LoginSource, error) {
|
||||
f.PushHook(func(context.Context, CreateLoginSourceOptions) (*LoginSource, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *LoginSourcesStoreCreateFunc) nextHook() func(context.Context, CreateLoginSourceOpts) (*LoginSource, error) {
|
||||
func (f *LoginSourcesStoreCreateFunc) nextHook() func(context.Context, CreateLoginSourceOptions) (*LoginSource, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
@@ -344,7 +344,7 @@ type LoginSourcesStoreCreateFuncCall struct {
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 CreateLoginSourceOpts
|
||||
Arg1 CreateLoginSourceOptions
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 *LoginSource
|
||||
@@ -582,15 +582,15 @@ func (c LoginSourcesStoreGetByIDFuncCall) Results() []interface{} {
|
||||
// LoginSourcesStoreListFunc describes the behavior when the List method of
|
||||
// the parent MockLoginSourcesStore instance is invoked.
|
||||
type LoginSourcesStoreListFunc struct {
|
||||
defaultHook func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error)
|
||||
hooks []func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error)
|
||||
defaultHook func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error)
|
||||
hooks []func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error)
|
||||
history []LoginSourcesStoreListFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// List delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockLoginSourcesStore) List(v0 context.Context, v1 ListLoginSourceOpts) ([]*LoginSource, error) {
|
||||
func (m *MockLoginSourcesStore) List(v0 context.Context, v1 ListLoginSourceOptions) ([]*LoginSource, error) {
|
||||
r0, r1 := m.ListFunc.nextHook()(v0, v1)
|
||||
m.ListFunc.appendCall(LoginSourcesStoreListFuncCall{v0, v1, r0, r1})
|
||||
return r0, r1
|
||||
@@ -599,7 +599,7 @@ func (m *MockLoginSourcesStore) List(v0 context.Context, v1 ListLoginSourceOpts)
|
||||
// SetDefaultHook sets function that is called when the List method of the
|
||||
// parent MockLoginSourcesStore instance is invoked and the hook queue is
|
||||
// empty.
|
||||
func (f *LoginSourcesStoreListFunc) SetDefaultHook(hook func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error)) {
|
||||
func (f *LoginSourcesStoreListFunc) SetDefaultHook(hook func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
@@ -607,7 +607,7 @@ func (f *LoginSourcesStoreListFunc) SetDefaultHook(hook func(context.Context, Li
|
||||
// List method of the parent MockLoginSourcesStore 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 *LoginSourcesStoreListFunc) PushHook(hook func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error)) {
|
||||
func (f *LoginSourcesStoreListFunc) PushHook(hook func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
@@ -616,19 +616,19 @@ func (f *LoginSourcesStoreListFunc) PushHook(hook func(context.Context, ListLogi
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *LoginSourcesStoreListFunc) SetDefaultReturn(r0 []*LoginSource, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error) {
|
||||
f.SetDefaultHook(func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *LoginSourcesStoreListFunc) PushReturn(r0 []*LoginSource, r1 error) {
|
||||
f.PushHook(func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error) {
|
||||
f.PushHook(func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *LoginSourcesStoreListFunc) nextHook() func(context.Context, ListLoginSourceOpts) ([]*LoginSource, error) {
|
||||
func (f *LoginSourcesStoreListFunc) nextHook() func(context.Context, ListLoginSourceOptions) ([]*LoginSource, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
@@ -666,7 +666,7 @@ type LoginSourcesStoreListFuncCall struct {
|
||||
Arg0 context.Context
|
||||
// Arg1 is the value of the 2nd argument passed to this method
|
||||
// invocation.
|
||||
Arg1 ListLoginSourceOpts
|
||||
Arg1 ListLoginSourceOptions
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 []*LoginSource
|
||||
@@ -1328,7 +1328,7 @@ func NewMockLoginSourceFilesStore() *MockLoginSourceFilesStore {
|
||||
},
|
||||
},
|
||||
ListFunc: &LoginSourceFilesStoreListFunc{
|
||||
defaultHook: func(ListLoginSourceOpts) (r0 []*LoginSource) {
|
||||
defaultHook: func(ListLoginSourceOptions) (r0 []*LoginSource) {
|
||||
return
|
||||
},
|
||||
},
|
||||
@@ -1356,7 +1356,7 @@ func NewStrictMockLoginSourceFilesStore() *MockLoginSourceFilesStore {
|
||||
},
|
||||
},
|
||||
ListFunc: &LoginSourceFilesStoreListFunc{
|
||||
defaultHook: func(ListLoginSourceOpts) []*LoginSource {
|
||||
defaultHook: func(ListLoginSourceOptions) []*LoginSource {
|
||||
panic("unexpected invocation of MockLoginSourceFilesStore.List")
|
||||
},
|
||||
},
|
||||
@@ -1374,7 +1374,7 @@ func NewStrictMockLoginSourceFilesStore() *MockLoginSourceFilesStore {
|
||||
type surrogateMockLoginSourceFilesStore interface {
|
||||
GetByID(int64) (*LoginSource, error)
|
||||
Len() int
|
||||
List(ListLoginSourceOpts) []*LoginSource
|
||||
List(ListLoginSourceOptions) []*LoginSource
|
||||
Update(*LoginSource)
|
||||
}
|
||||
|
||||
@@ -1605,15 +1605,15 @@ func (c LoginSourceFilesStoreLenFuncCall) Results() []interface{} {
|
||||
// LoginSourceFilesStoreListFunc describes the behavior when the List method
|
||||
// of the parent MockLoginSourceFilesStore instance is invoked.
|
||||
type LoginSourceFilesStoreListFunc struct {
|
||||
defaultHook func(ListLoginSourceOpts) []*LoginSource
|
||||
hooks []func(ListLoginSourceOpts) []*LoginSource
|
||||
defaultHook func(ListLoginSourceOptions) []*LoginSource
|
||||
hooks []func(ListLoginSourceOptions) []*LoginSource
|
||||
history []LoginSourceFilesStoreListFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// List delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockLoginSourceFilesStore) List(v0 ListLoginSourceOpts) []*LoginSource {
|
||||
func (m *MockLoginSourceFilesStore) List(v0 ListLoginSourceOptions) []*LoginSource {
|
||||
r0 := m.ListFunc.nextHook()(v0)
|
||||
m.ListFunc.appendCall(LoginSourceFilesStoreListFuncCall{v0, r0})
|
||||
return r0
|
||||
@@ -1622,7 +1622,7 @@ func (m *MockLoginSourceFilesStore) List(v0 ListLoginSourceOpts) []*LoginSource
|
||||
// SetDefaultHook sets function that is called when the List method of the
|
||||
// parent MockLoginSourceFilesStore instance is invoked and the hook queue
|
||||
// is empty.
|
||||
func (f *LoginSourceFilesStoreListFunc) SetDefaultHook(hook func(ListLoginSourceOpts) []*LoginSource) {
|
||||
func (f *LoginSourceFilesStoreListFunc) SetDefaultHook(hook func(ListLoginSourceOptions) []*LoginSource) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
@@ -1630,7 +1630,7 @@ func (f *LoginSourceFilesStoreListFunc) SetDefaultHook(hook func(ListLoginSource
|
||||
// List method of the parent MockLoginSourceFilesStore 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 *LoginSourceFilesStoreListFunc) PushHook(hook func(ListLoginSourceOpts) []*LoginSource) {
|
||||
func (f *LoginSourceFilesStoreListFunc) PushHook(hook func(ListLoginSourceOptions) []*LoginSource) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
@@ -1639,19 +1639,19 @@ func (f *LoginSourceFilesStoreListFunc) PushHook(hook func(ListLoginSourceOpts)
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *LoginSourceFilesStoreListFunc) SetDefaultReturn(r0 []*LoginSource) {
|
||||
f.SetDefaultHook(func(ListLoginSourceOpts) []*LoginSource {
|
||||
f.SetDefaultHook(func(ListLoginSourceOptions) []*LoginSource {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *LoginSourceFilesStoreListFunc) PushReturn(r0 []*LoginSource) {
|
||||
f.PushHook(func(ListLoginSourceOpts) []*LoginSource {
|
||||
f.PushHook(func(ListLoginSourceOptions) []*LoginSource {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
func (f *LoginSourceFilesStoreListFunc) nextHook() func(ListLoginSourceOpts) []*LoginSource {
|
||||
func (f *LoginSourceFilesStoreListFunc) nextHook() func(ListLoginSourceOptions) []*LoginSource {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
@@ -1686,7 +1686,7 @@ func (f *LoginSourceFilesStoreListFunc) History() []LoginSourceFilesStoreListFun
|
||||
type LoginSourceFilesStoreListFuncCall struct {
|
||||
// Arg0 is the value of the 1st argument passed to this method
|
||||
// invocation.
|
||||
Arg0 ListLoginSourceOpts
|
||||
Arg0 ListLoginSourceOptions
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 []*LoginSource
|
||||
|
||||
@@ -52,7 +52,7 @@ func init() {
|
||||
legacyTables = append(legacyTables,
|
||||
new(User), new(PublicKey), new(TwoFactor), new(TwoFactorRecoveryCode),
|
||||
new(Repository), new(DeployKey), new(Collaboration), new(Upload),
|
||||
new(Watch), new(Star), new(Follow), new(Action),
|
||||
new(Watch), new(Star), new(Follow),
|
||||
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
|
||||
new(Label), new(IssueLabel), new(Milestone),
|
||||
new(Mirror), new(Release), new(Webhook), new(HookTask),
|
||||
|
||||
@@ -32,7 +32,7 @@ var Perms PermsStore
|
||||
// In case of an organization repository, the members of the owners team are in
|
||||
// this table.
|
||||
type Access struct {
|
||||
ID int64
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
UserID int64 `xorm:"UNIQUE(s)" gorm:"uniqueIndex:access_user_repo_unique;not null"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)" gorm:"uniqueIndex:access_user_repo_unique;not null"`
|
||||
Mode AccessMode `gorm:"not null"`
|
||||
|
||||
@@ -18,7 +18,6 @@ func TestPerms(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(Access)}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -138,7 +139,7 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
|
||||
Name: "deleted",
|
||||
}
|
||||
} else {
|
||||
apiHeadRepo = pr.HeadRepo.APIFormat(nil)
|
||||
apiHeadRepo = pr.HeadRepo.APIFormatLegacy(nil)
|
||||
}
|
||||
|
||||
apiIssue := pr.Issue.APIFormat()
|
||||
@@ -156,7 +157,7 @@ func (pr *PullRequest) APIFormat() *api.PullRequest {
|
||||
HeadBranch: pr.HeadBranch,
|
||||
HeadRepo: apiHeadRepo,
|
||||
BaseBranch: pr.BaseBranch,
|
||||
BaseRepo: pr.BaseRepo.APIFormat(nil),
|
||||
BaseRepo: pr.BaseRepo.APIFormatLegacy(nil),
|
||||
HTMLURL: pr.Issue.HTMLURL(),
|
||||
HasMerged: pr.HasMerged,
|
||||
}
|
||||
@@ -195,6 +196,8 @@ const (
|
||||
// Merge merges pull request to base repository.
|
||||
// FIXME: add repoWorkingPull make sure two merges does not happen at same time.
|
||||
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle MergeStyle, commitDescription string) (err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
defer func() {
|
||||
go HookQueue.Add(pr.BaseRepo.ID)
|
||||
go AddTestPullRequestTask(doer, pr.BaseRepo.ID, pr.BaseBranch, false)
|
||||
@@ -334,8 +337,8 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
|
||||
return fmt.Errorf("Commit: %v", err)
|
||||
}
|
||||
|
||||
if err = MergePullRequestAction(doer, pr.Issue.Repo, pr.Issue); err != nil {
|
||||
log.Error("MergePullRequestAction [%d]: %v", pr.ID, err)
|
||||
if err = Actions.MergePullRequest(ctx, doer, pr.Issue.Repo.Owner, pr.Issue.Repo, pr.Issue); err != nil {
|
||||
log.Error("Failed to create action for merge pull request, pull_request_id: %d, error: %v", pr.ID, err)
|
||||
}
|
||||
|
||||
// Reload pull request information.
|
||||
@@ -347,7 +350,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
|
||||
Action: api.HOOK_ISSUE_CLOSED,
|
||||
Index: pr.Index,
|
||||
PullRequest: pr.APIFormat(),
|
||||
Repository: pr.Issue.Repo.APIFormat(nil),
|
||||
Repository: pr.Issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
@@ -372,7 +375,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
|
||||
commits = append([]*git.Commit{mergeCommit}, commits...)
|
||||
}
|
||||
|
||||
pcs, err := CommitsToPushCommits(commits).ToApiPayloadCommits(pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL())
|
||||
pcs, err := CommitsToPushCommits(commits).APIFormat(ctx, Users, pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL())
|
||||
if err != nil {
|
||||
log.Error("Failed to convert to API payload commits: %v", err)
|
||||
return nil
|
||||
@@ -384,7 +387,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
|
||||
After: mergeCommit.ID.String(),
|
||||
CompareURL: conf.Server.ExternalURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
|
||||
Commits: pcs,
|
||||
Repo: pr.BaseRepo.APIFormat(nil),
|
||||
Repo: pr.BaseRepo.APIFormatLegacy(nil),
|
||||
Pusher: pr.HeadRepo.MustOwner().APIFormat(),
|
||||
Sender: doer.APIFormat(),
|
||||
}
|
||||
@@ -487,7 +490,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
|
||||
if err = NotifyWatchers(&Action{
|
||||
ActUserID: pull.Poster.ID,
|
||||
ActUserName: pull.Poster.Name,
|
||||
OpType: ACTION_CREATE_PULL_REQUEST,
|
||||
OpType: ActionCreatePullRequest,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Title),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
@@ -506,7 +509,7 @@ func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []str
|
||||
Action: api.HOOK_ISSUE_OPENED,
|
||||
Index: pull.Index,
|
||||
PullRequest: pr.APIFormat(),
|
||||
Repository: repo.APIFormat(nil),
|
||||
Repository: repo.APIFormatLegacy(nil),
|
||||
Sender: pull.Poster.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
@@ -798,7 +801,7 @@ func AddTestPullRequestTask(doer *User, repoID int64, branch string, isSync bool
|
||||
Action: api.HOOK_ISSUE_SYNCHRONIZED,
|
||||
Index: pr.Issue.Index,
|
||||
PullRequest: pr.Issue.PullRequest.APIFormat(),
|
||||
Repository: pr.Issue.Repo.APIFormat(nil),
|
||||
Repository: pr.Issue.Repo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [pull_id: %v]: %v", pr.ID, err)
|
||||
|
||||
@@ -153,7 +153,7 @@ func (r *Release) preparePublishWebhooks() {
|
||||
if err := PrepareWebhooks(r.Repo, HOOK_EVENT_RELEASE, &api.ReleasePayload{
|
||||
Action: api.HOOK_RELEASE_PUBLISHED,
|
||||
Release: r.APIFormat(),
|
||||
Repository: r.Repo.APIFormat(nil),
|
||||
Repository: r.Repo.APIFormatLegacy(nil),
|
||||
Sender: r.Publisher.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks: %v", err)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/unknwon/cae/zip"
|
||||
"github.com/unknwon/com"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -33,11 +34,12 @@ import (
|
||||
embedConf "gogs.io/gogs/conf"
|
||||
"gogs.io/gogs/internal/avatar"
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/db/errors"
|
||||
dberrors "gogs.io/gogs/internal/db/errors"
|
||||
"gogs.io/gogs/internal/errutil"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
"gogs.io/gogs/internal/osutil"
|
||||
"gogs.io/gogs/internal/process"
|
||||
"gogs.io/gogs/internal/repoutil"
|
||||
"gogs.io/gogs/internal/semverutil"
|
||||
"gogs.io/gogs/internal/sync"
|
||||
)
|
||||
@@ -150,15 +152,15 @@ func NewRepoContext() {
|
||||
|
||||
// Repository contains information of a repository.
|
||||
type Repository struct {
|
||||
ID int64
|
||||
OwnerID int64 `xorm:"UNIQUE(s)" gorm:"UNIQUE_INDEX:s"`
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
OwnerID int64 `xorm:"UNIQUE(s)" gorm:"uniqueIndex:repo_owner_name_unique"`
|
||||
Owner *User `xorm:"-" gorm:"-" json:"-"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL" gorm:"UNIQUE_INDEX:s"`
|
||||
Name string `xorm:"INDEX NOT NULL" gorm:"NOT NULL"`
|
||||
Description string `xorm:"VARCHAR(512)" gorm:"TYPE:VARCHAR(512)"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL" gorm:"uniqueIndex:repo_owner_name_unique;index;not null"`
|
||||
Name string `xorm:"INDEX NOT NULL" gorm:"index;not null"`
|
||||
Description string `xorm:"VARCHAR(512)" gorm:"type:VARCHAR(512)"`
|
||||
Website string
|
||||
DefaultBranch string
|
||||
Size int64 `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"`
|
||||
Size int64 `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
|
||||
UseCustomAvatar bool
|
||||
|
||||
// Counters
|
||||
@@ -171,37 +173,37 @@ type Repository struct {
|
||||
NumPulls int
|
||||
NumClosedPulls int
|
||||
NumOpenPulls int `xorm:"-" gorm:"-" json:"-"`
|
||||
NumMilestones int `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"`
|
||||
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"`
|
||||
NumMilestones int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
|
||||
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
|
||||
NumOpenMilestones int `xorm:"-" gorm:"-" json:"-"`
|
||||
NumTags int `xorm:"-" gorm:"-" json:"-"`
|
||||
|
||||
IsPrivate bool
|
||||
// TODO: When migrate to GORM, make sure to do a loose migration with `HasColumn` and `AddColumn`,
|
||||
// see docs in https://gorm.io/docs/migration.html.
|
||||
IsUnlisted bool `xorm:"NOT NULL DEFAULT false"`
|
||||
IsUnlisted bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"`
|
||||
IsBare bool
|
||||
|
||||
IsMirror bool
|
||||
*Mirror `xorm:"-" gorm:"-" json:"-"`
|
||||
|
||||
// Advanced settings
|
||||
EnableWiki bool `xorm:"NOT NULL DEFAULT true" gorm:"NOT NULL;DEFAULT:TRUE"`
|
||||
EnableWiki bool `xorm:"NOT NULL DEFAULT true" gorm:"not null;default:TRUE"`
|
||||
AllowPublicWiki bool
|
||||
EnableExternalWiki bool
|
||||
ExternalWikiURL string
|
||||
EnableIssues bool `xorm:"NOT NULL DEFAULT true" gorm:"NOT NULL;DEFAULT:TRUE"`
|
||||
EnableIssues bool `xorm:"NOT NULL DEFAULT true" gorm:"not null;default:TRUE"`
|
||||
AllowPublicIssues bool
|
||||
EnableExternalTracker bool
|
||||
ExternalTrackerURL string
|
||||
ExternalTrackerFormat string
|
||||
ExternalTrackerStyle string
|
||||
ExternalMetas map[string]string `xorm:"-" gorm:"-" json:"-"`
|
||||
EnablePulls bool `xorm:"NOT NULL DEFAULT true" gorm:"NOT NULL;DEFAULT:TRUE"`
|
||||
PullsIgnoreWhitespace bool `xorm:"NOT NULL DEFAULT false" gorm:"NOT NULL;DEFAULT:FALSE"`
|
||||
PullsAllowRebase bool `xorm:"NOT NULL DEFAULT false" gorm:"NOT NULL;DEFAULT:FALSE"`
|
||||
EnablePulls bool `xorm:"NOT NULL DEFAULT true" gorm:"not null;default:TRUE"`
|
||||
PullsIgnoreWhitespace bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"`
|
||||
PullsAllowRebase bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"`
|
||||
|
||||
IsFork bool `xorm:"NOT NULL DEFAULT false" gorm:"NOT NULL;DEFAULT:FALSE"`
|
||||
IsFork bool `xorm:"NOT NULL DEFAULT false" gorm:"not null;default:FALSE"`
|
||||
ForkID int64
|
||||
BaseRepo *Repository `xorm:"-" gorm:"-" json:"-"`
|
||||
|
||||
@@ -290,6 +292,7 @@ func (repo *Repository) FullName() string {
|
||||
return repo.MustOwner().Name + "/" + repo.Name
|
||||
}
|
||||
|
||||
// Deprecated: Use repoutil.HTMLURL instead.
|
||||
func (repo *Repository) HTMLURL() string {
|
||||
return conf.Server.ExternalURL + repo.FullName()
|
||||
}
|
||||
@@ -356,7 +359,9 @@ func (repo *Repository) DeleteAvatar() error {
|
||||
// This method assumes following fields have been assigned with valid values:
|
||||
// Required - BaseRepo (if fork)
|
||||
// Arguments that are allowed to be nil: permission
|
||||
func (repo *Repository) APIFormat(permission *api.Permission, user ...*User) *api.Repository {
|
||||
//
|
||||
// Deprecated: Use APIFormat instead.
|
||||
func (repo *Repository) APIFormatLegacy(permission *api.Permission, user ...*User) *api.Repository {
|
||||
cloneLink := repo.CloneLink()
|
||||
apiRepo := &api.Repository{
|
||||
ID: repo.ID,
|
||||
@@ -390,7 +395,7 @@ func (repo *Repository) APIFormat(permission *api.Permission, user ...*User) *ap
|
||||
p.Admin = user[0].IsAdminOfRepo(repo)
|
||||
p.Push = user[0].IsWriterOfRepo(repo)
|
||||
}
|
||||
apiRepo.Parent = repo.BaseRepo.APIFormat(p)
|
||||
apiRepo.Parent = repo.BaseRepo.APIFormatLegacy(p)
|
||||
}
|
||||
return apiRepo
|
||||
}
|
||||
@@ -537,6 +542,7 @@ func (repo *Repository) repoPath(e Engine) string {
|
||||
return RepoPath(repo.mustOwner(e).Name, repo.Name)
|
||||
}
|
||||
|
||||
// Deprecated: Use repoutil.RepositoryPath instead.
|
||||
func (repo *Repository) RepoPath() string {
|
||||
return repo.repoPath(x)
|
||||
}
|
||||
@@ -553,6 +559,7 @@ func (repo *Repository) Link() string {
|
||||
return conf.Server.Subpath + "/" + repo.FullName()
|
||||
}
|
||||
|
||||
// Deprecated: Use repoutil.ComparePath instead.
|
||||
func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string {
|
||||
return fmt.Sprintf("%s/%s/compare/%s...%s", repo.MustOwner().Name, repo.Name, oldCommitID, newCommitID)
|
||||
}
|
||||
@@ -694,37 +701,28 @@ func IsRepositoryExist(u *User, repoName string) (bool, error) {
|
||||
return isRepositoryExist(x, u, repoName)
|
||||
}
|
||||
|
||||
// CloneLink represents different types of clone URLs of repository.
|
||||
type CloneLink struct {
|
||||
SSH string
|
||||
HTTPS string
|
||||
Git string
|
||||
}
|
||||
|
||||
// ComposeHTTPSCloneURL returns HTTPS clone URL based on given owner and repository name.
|
||||
func ComposeHTTPSCloneURL(owner, repo string) string {
|
||||
return fmt.Sprintf("%s%s/%s.git", conf.Server.ExternalURL, owner, repo)
|
||||
}
|
||||
|
||||
func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
|
||||
// Deprecated: Use repoutil.NewCloneLink instead.
|
||||
func (repo *Repository) cloneLink(isWiki bool) *repoutil.CloneLink {
|
||||
repoName := repo.Name
|
||||
if isWiki {
|
||||
repoName += ".wiki"
|
||||
}
|
||||
|
||||
repo.Owner = repo.MustOwner()
|
||||
cl := new(CloneLink)
|
||||
cl := new(repoutil.CloneLink)
|
||||
if conf.SSH.Port != 22 {
|
||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", conf.App.RunUser, conf.SSH.Domain, conf.SSH.Port, repo.Owner.Name, repoName)
|
||||
} else {
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", conf.App.RunUser, conf.SSH.Domain, repo.Owner.Name, repoName)
|
||||
}
|
||||
cl.HTTPS = ComposeHTTPSCloneURL(repo.Owner.Name, repoName)
|
||||
cl.HTTPS = repoutil.HTTPSCloneURL(repo.Owner.Name, repoName)
|
||||
return cl
|
||||
}
|
||||
|
||||
// CloneLink returns clone URLs of repository.
|
||||
func (repo *Repository) CloneLink() (cl *CloneLink) {
|
||||
//
|
||||
// Deprecated: Use repoutil.NewCloneLink instead.
|
||||
func (repo *Repository) CloneLink() (cl *repoutil.CloneLink) {
|
||||
return repo.cloneLink(false)
|
||||
}
|
||||
|
||||
@@ -758,7 +756,7 @@ func wikiRemoteURL(remote string) string {
|
||||
|
||||
// MigrateRepository migrates a existing repository from other project hosting.
|
||||
func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
repo, err := CreateRepository(doer, owner, CreateRepoOptions{
|
||||
repo, err := CreateRepository(doer, owner, CreateRepoOptionsLegacy{
|
||||
Name: opts.Name,
|
||||
Description: opts.Description,
|
||||
IsPrivate: opts.IsPrivate,
|
||||
@@ -930,7 +928,7 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
type CreateRepoOptions struct {
|
||||
type CreateRepoOptionsLegacy struct {
|
||||
Name string
|
||||
Description string
|
||||
Gitignores string
|
||||
@@ -953,7 +951,7 @@ func getRepoInitFile(tp, name string) ([]byte, error) {
|
||||
return embedConf.Files.ReadFile(relPath)
|
||||
}
|
||||
|
||||
func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRepoOptions) error {
|
||||
func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRepoOptionsLegacy) error {
|
||||
// Clone to temporary path and do the init commit.
|
||||
_, stderr, err := process.Exec(
|
||||
fmt.Sprintf("initRepository(git clone): %s", repoPath), "git", "clone", repoPath, tmpDir)
|
||||
@@ -1016,7 +1014,7 @@ func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRep
|
||||
}
|
||||
|
||||
// initRepository performs initial commit with chosen setup files on behave of doer.
|
||||
func initRepository(e Engine, repoPath string, doer *User, repo *Repository, opts CreateRepoOptions) (err error) {
|
||||
func initRepository(e Engine, repoPath string, doer *User, repo *Repository, opts CreateRepoOptionsLegacy) (err error) {
|
||||
// Somehow the directory could exist.
|
||||
if com.IsExist(repoPath) {
|
||||
return fmt.Errorf("initRepository: path already exists: %s", repoPath)
|
||||
@@ -1116,7 +1114,29 @@ func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err
|
||||
|
||||
if err = watchRepo(e, owner.ID, repo.ID, true); err != nil {
|
||||
return fmt.Errorf("watchRepo: %v", err)
|
||||
} else if err = newRepoAction(e, doer, owner, repo); err != nil {
|
||||
}
|
||||
|
||||
// FIXME: This is identical to Actions.NewRepo but we are not yet able to wrap
|
||||
// transaction with different ORM objects, should delete this once migrated to
|
||||
// GORM for this part of logic.
|
||||
newRepoAction := func(e Engine, doer *User, repo *Repository) (err error) {
|
||||
opType := ActionCreateRepo
|
||||
if repo.IsFork {
|
||||
opType = ActionForkRepo
|
||||
}
|
||||
|
||||
return notifyWatchers(e, &Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: opType,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
CreatedUnix: time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
if err = newRepoAction(e, doer, repo); err != nil {
|
||||
return fmt.Errorf("newRepoAction: %v", err)
|
||||
}
|
||||
|
||||
@@ -1137,7 +1157,7 @@ func (err ErrReachLimitOfRepo) Error() string {
|
||||
}
|
||||
|
||||
// CreateRepository creates a repository for given user or organization.
|
||||
func CreateRepository(doer, owner *User, opts CreateRepoOptions) (_ *Repository, err error) {
|
||||
func CreateRepository(doer, owner *User, opts CreateRepoOptionsLegacy) (_ *Repository, err error) {
|
||||
if !owner.CanCreateRepo() {
|
||||
return nil, ErrReachLimitOfRepo{Limit: owner.RepoCreationNum()}
|
||||
}
|
||||
@@ -1265,6 +1285,8 @@ func FilterRepositoryWithIssues(repoIDs []int64) ([]int64, error) {
|
||||
}
|
||||
|
||||
// RepoPath returns repository path by given user and repository name.
|
||||
//
|
||||
// Deprecated: Use repoutil.RepositoryPath instead.
|
||||
func RepoPath(userName, repoName string) string {
|
||||
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git")
|
||||
}
|
||||
@@ -1361,9 +1383,34 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
||||
return fmt.Errorf("decrease old owner repository count: %v", err)
|
||||
}
|
||||
|
||||
// Remove watch for organization.
|
||||
if owner.IsOrganization() {
|
||||
if err = watchRepo(sess, owner.ID, repo.ID, false); err != nil {
|
||||
return errors.Wrap(err, "unwatch repository for the organization owner")
|
||||
}
|
||||
}
|
||||
|
||||
if err = watchRepo(sess, newOwner.ID, repo.ID, true); err != nil {
|
||||
return fmt.Errorf("watchRepo: %v", err)
|
||||
} else if err = transferRepoAction(sess, doer, owner, repo); err != nil {
|
||||
}
|
||||
|
||||
// FIXME: This is identical to Actions.TransferRepo but we are not yet able to
|
||||
// wrap transaction with different ORM objects, should delete this once migrated
|
||||
// to GORM for this part of logic.
|
||||
transferRepoAction := func(e Engine, doer, oldOwner *User, repo *Repository) error {
|
||||
return notifyWatchers(e, &Action{
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ActionTransferRepo,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate || repo.IsUnlisted,
|
||||
Content: path.Join(oldOwner.Name, repo.Name),
|
||||
CreatedUnix: time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
if err = transferRepoAction(sess, doer, owner, repo); err != nil {
|
||||
return fmt.Errorf("transferRepoAction: %v", err)
|
||||
}
|
||||
|
||||
@@ -1651,7 +1698,7 @@ func DeleteRepository(ownerID, repoID int64) error {
|
||||
func GetRepositoryByRef(ref string) (*Repository, error) {
|
||||
n := strings.IndexByte(ref, byte('/'))
|
||||
if n < 2 {
|
||||
return nil, errors.InvalidRepoReference{Ref: ref}
|
||||
return nil, dberrors.InvalidRepoReference{Ref: ref}
|
||||
}
|
||||
|
||||
userName, repoName := ref[:n], ref[n+1:]
|
||||
@@ -2235,9 +2282,9 @@ func (repos MirrorRepositoryList) LoadAttributes() error {
|
||||
|
||||
// Watch is connection request for receiving repository notification.
|
||||
type Watch struct {
|
||||
ID int64
|
||||
UserID int64 `xorm:"UNIQUE(watch)"`
|
||||
RepoID int64 `xorm:"UNIQUE(watch)"`
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
UserID int64 `xorm:"UNIQUE(watch)" gorm:"uniqueIndex:watch_user_repo_unique;not null"`
|
||||
RepoID int64 `xorm:"UNIQUE(watch)" gorm:"uniqueIndex:watch_user_repo_unique;not null"`
|
||||
}
|
||||
|
||||
func isWatching(e Engine, userID, repoID int64) bool {
|
||||
@@ -2276,12 +2323,15 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {
|
||||
return watchRepo(x, userID, repoID, watch)
|
||||
}
|
||||
|
||||
// Deprecated: Use Repos.ListByRepo instead.
|
||||
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
|
||||
watches := make([]*Watch, 0, 10)
|
||||
return watches, e.Find(&watches, &Watch{RepoID: repoID})
|
||||
}
|
||||
|
||||
// GetWatchers returns all watchers of given repository.
|
||||
//
|
||||
// Deprecated: Use Repos.ListByRepo instead.
|
||||
func GetWatchers(repoID int64) ([]*Watch, error) {
|
||||
return getWatchers(x, repoID)
|
||||
}
|
||||
@@ -2298,7 +2348,12 @@ func (repo *Repository) GetWatchers(page int) ([]*User, error) {
|
||||
return users, sess.Find(&users)
|
||||
}
|
||||
|
||||
// Deprecated: Use Actions.notifyWatchers instead.
|
||||
func notifyWatchers(e Engine, act *Action) error {
|
||||
if act.CreatedUnix <= 0 {
|
||||
act.CreatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
// Add feeds for user self and all watchers.
|
||||
watchers, err := getWatchers(e, act.RepoID)
|
||||
if err != nil {
|
||||
@@ -2329,6 +2384,8 @@ func notifyWatchers(e Engine, act *Action) error {
|
||||
}
|
||||
|
||||
// NotifyWatchers creates batch of actions for every watcher.
|
||||
//
|
||||
// Deprecated: Use Actions.notifyWatchers instead.
|
||||
func NotifyWatchers(act *Action) error {
|
||||
return notifyWatchers(x, act)
|
||||
}
|
||||
@@ -2469,8 +2526,8 @@ func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string)
|
||||
log.Error("UpdateSize [repo_id: %d]: %v", repo.ID, err)
|
||||
}
|
||||
if err = PrepareWebhooks(baseRepo, HOOK_EVENT_FORK, &api.ForkPayload{
|
||||
Forkee: repo.APIFormat(nil),
|
||||
Repo: baseRepo.APIFormat(nil),
|
||||
Forkee: repo.APIFormatLegacy(nil),
|
||||
Repo: baseRepo.APIFormatLegacy(nil),
|
||||
Sender: doer.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [repo_id: %d]: %v", baseRepo.ID, err)
|
||||
|
||||
@@ -10,18 +10,28 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gogs.io/gogs/internal/errutil"
|
||||
"gogs.io/gogs/internal/repoutil"
|
||||
)
|
||||
|
||||
// ReposStore is the persistent interface for repositories.
|
||||
//
|
||||
// NOTE: All methods are sorted in alphabetical order.
|
||||
type ReposStore interface {
|
||||
// Create creates a new repository record in the database. It returns
|
||||
// ErrNameNotAllowed when the repository name is not allowed, or
|
||||
// ErrRepoAlreadyExist when a repository with same name already exists for the
|
||||
// owner.
|
||||
Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error)
|
||||
// GetByName returns the repository with given owner and name. It returns
|
||||
// ErrRepoNotExist when not found.
|
||||
GetByName(ctx context.Context, ownerID int64, name string) (*Repository, error)
|
||||
// Touch updates the updated time to the current time and removes the bare state
|
||||
// of the given repository.
|
||||
Touch(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
var Repos ReposStore
|
||||
@@ -47,12 +57,58 @@ func (r *Repository) AfterFind(_ *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type RepositoryAPIFormatOptions struct {
|
||||
Permission *api.Permission
|
||||
Parent *api.Repository
|
||||
}
|
||||
|
||||
// APIFormat returns the API format of a repository.
|
||||
func (r *Repository) APIFormat(owner *User, opts ...RepositoryAPIFormatOptions) *api.Repository {
|
||||
var opt RepositoryAPIFormatOptions
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
|
||||
cloneLink := repoutil.NewCloneLink(owner.Name, r.Name, false)
|
||||
return &api.Repository{
|
||||
ID: r.ID,
|
||||
Owner: owner.APIFormat(),
|
||||
Name: r.Name,
|
||||
FullName: owner.Name + "/" + r.Name,
|
||||
Description: r.Description,
|
||||
Private: r.IsPrivate,
|
||||
Fork: r.IsFork,
|
||||
Parent: opt.Parent,
|
||||
Empty: r.IsBare,
|
||||
Mirror: r.IsMirror,
|
||||
Size: r.Size,
|
||||
HTMLURL: repoutil.HTMLURL(owner.Name, r.Name),
|
||||
SSHURL: cloneLink.SSH,
|
||||
CloneURL: cloneLink.HTTPS,
|
||||
Website: r.Website,
|
||||
Stars: r.NumStars,
|
||||
Forks: r.NumForks,
|
||||
Watchers: r.NumWatches,
|
||||
OpenIssues: r.NumOpenIssues,
|
||||
DefaultBranch: r.DefaultBranch,
|
||||
Created: r.Created,
|
||||
Updated: r.Updated,
|
||||
Permissions: opt.Permission,
|
||||
}
|
||||
}
|
||||
|
||||
var _ ReposStore = (*repos)(nil)
|
||||
|
||||
type repos struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
// NewReposStore returns a persistent interface for repositories with given
|
||||
// database connection.
|
||||
func NewReposStore(db *gorm.DB) ReposStore {
|
||||
return &repos{DB: db}
|
||||
}
|
||||
|
||||
type ErrRepoAlreadyExist struct {
|
||||
args errutil.Args
|
||||
}
|
||||
@@ -66,7 +122,7 @@ func (err ErrRepoAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("repository already exists: %v", err.args)
|
||||
}
|
||||
|
||||
type createRepoOpts struct {
|
||||
type CreateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
DefaultBranch string
|
||||
@@ -79,10 +135,7 @@ type createRepoOpts struct {
|
||||
ForkID int64
|
||||
}
|
||||
|
||||
// create creates a new repository record in the database. Fields of "repo" will be updated
|
||||
// in place upon insertion. It returns ErrNameNotAllowed when the repository name is not allowed,
|
||||
// or ErrRepoAlreadyExist when a repository with same name already exists for the owner.
|
||||
func (db *repos) create(ctx context.Context, ownerID int64, opts createRepoOpts) (*Repository, error) {
|
||||
func (db *repos) Create(ctx context.Context, ownerID int64, opts CreateRepoOptions) (*Repository, error) {
|
||||
err := isRepoNameAllowed(opts.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -90,7 +143,12 @@ func (db *repos) create(ctx context.Context, ownerID int64, opts createRepoOpts)
|
||||
|
||||
_, err = db.GetByName(ctx, ownerID, opts.Name)
|
||||
if err == nil {
|
||||
return nil, ErrRepoAlreadyExist{args: errutil.Args{"ownerID": ownerID, "name": opts.Name}}
|
||||
return nil, ErrRepoAlreadyExist{
|
||||
args: errutil.Args{
|
||||
"ownerID": ownerID,
|
||||
"name": opts.Name,
|
||||
},
|
||||
}
|
||||
} else if !IsErrRepoNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,7 +173,7 @@ func (db *repos) create(ctx context.Context, ownerID int64, opts createRepoOpts)
|
||||
var _ errutil.NotFound = (*ErrRepoNotExist)(nil)
|
||||
|
||||
type ErrRepoNotExist struct {
|
||||
args map[string]interface{}
|
||||
args errutil.Args
|
||||
}
|
||||
|
||||
func IsErrRepoNotExist(err error) bool {
|
||||
@@ -139,9 +197,25 @@ func (db *repos) GetByName(ctx context.Context, ownerID int64, name string) (*Re
|
||||
Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, ErrRepoNotExist{args: map[string]interface{}{"ownerID": ownerID, "name": name}}
|
||||
return nil, ErrRepoNotExist{
|
||||
args: errutil.Args{
|
||||
"ownerID": ownerID,
|
||||
"name": name,
|
||||
},
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func (db *repos) Touch(ctx context.Context, id int64) error {
|
||||
return db.WithContext(ctx).
|
||||
Model(new(Repository)).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
"is_bare": false,
|
||||
"updated_unix": db.NowFunc().Unix(),
|
||||
}).
|
||||
Error
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ func TestRepos(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(Repository)}
|
||||
@@ -32,8 +31,9 @@ func TestRepos(t *testing.T) {
|
||||
name string
|
||||
test func(*testing.T, *repos)
|
||||
}{
|
||||
{"create", reposCreate},
|
||||
{"Create", reposCreate},
|
||||
{"GetByName", reposGetByName},
|
||||
{"Touch", reposTouch},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
@@ -52,9 +52,9 @@ func reposCreate(t *testing.T, db *repos) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("name not allowed", func(t *testing.T) {
|
||||
_, err := db.create(ctx,
|
||||
_, err := db.Create(ctx,
|
||||
1,
|
||||
createRepoOpts{
|
||||
CreateRepoOptions{
|
||||
Name: "my.git",
|
||||
},
|
||||
)
|
||||
@@ -63,15 +63,15 @@ func reposCreate(t *testing.T, db *repos) {
|
||||
})
|
||||
|
||||
t.Run("already exists", func(t *testing.T) {
|
||||
_, err := db.create(ctx, 2,
|
||||
createRepoOpts{
|
||||
_, err := db.Create(ctx, 2,
|
||||
CreateRepoOptions{
|
||||
Name: "repo1",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.create(ctx, 2,
|
||||
createRepoOpts{
|
||||
_, err = db.Create(ctx, 2,
|
||||
CreateRepoOptions{
|
||||
Name: "repo1",
|
||||
},
|
||||
)
|
||||
@@ -79,8 +79,8 @@ func reposCreate(t *testing.T, db *repos) {
|
||||
assert.Equal(t, wantErr, err)
|
||||
})
|
||||
|
||||
repo, err := db.create(ctx, 3,
|
||||
createRepoOpts{
|
||||
repo, err := db.Create(ctx, 3,
|
||||
CreateRepoOptions{
|
||||
Name: "repo2",
|
||||
},
|
||||
)
|
||||
@@ -94,8 +94,8 @@ func reposCreate(t *testing.T, db *repos) {
|
||||
func reposGetByName(t *testing.T, db *repos) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo, err := db.create(ctx, 1,
|
||||
createRepoOpts{
|
||||
repo, err := db.Create(ctx, 1,
|
||||
CreateRepoOptions{
|
||||
Name: "repo1",
|
||||
},
|
||||
)
|
||||
@@ -108,3 +108,31 @@ func reposGetByName(t *testing.T, db *repos) {
|
||||
wantErr := ErrRepoNotExist{args: errutil.Args{"ownerID": int64(1), "name": "bad_name"}}
|
||||
assert.Equal(t, wantErr, err)
|
||||
}
|
||||
|
||||
func reposTouch(t *testing.T, db *repos) {
|
||||
ctx := context.Background()
|
||||
|
||||
repo, err := db.Create(ctx, 1,
|
||||
CreateRepoOptions{
|
||||
Name: "repo1",
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.WithContext(ctx).Model(new(Repository)).Where("id = ?", repo.ID).Update("is_bare", true).Error
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make sure it is bare
|
||||
got, err := db.GetByName(ctx, repo.OwnerID, repo.Name)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, got.IsBare)
|
||||
|
||||
// Touch it
|
||||
err = db.Touch(ctx, repo.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// It should not be bare anymore
|
||||
got, err = db.GetByName(ctx, repo.OwnerID, repo.Name)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, got.IsBare)
|
||||
}
|
||||
|
||||
3
internal/db/testdata/backup/Action.golden.json
vendored
Normal file
3
internal/db/testdata/backup/Action.golden.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{"ID":1,"UserID":1,"OpType":16,"ActUserID":1,"ActUserName":"alice","RepoID":1,"RepoUserName":"alice","RepoName":"example","RefName":"main","IsPrivate":false,"Content":"{\"Len\":1,\"Commits\":[],\"CompareURL\":\"\"}","CreatedUnix":1588568886}
|
||||
{"ID":2,"UserID":1,"OpType":5,"ActUserID":1,"ActUserName":"alice","RepoID":1,"RepoUserName":"alice","RepoName":"example","RefName":"main","IsPrivate":false,"Content":"{\"Len\":1,\"Commits\":[],\"CompareURL\":\"\"}","CreatedUnix":1588568886}
|
||||
{"ID":3,"UserID":1,"OpType":17,"ActUserID":1,"ActUserName":"alice","RepoID":1,"RepoUserName":"alice","RepoName":"example","RefName":"main","IsPrivate":false,"Content":"","CreatedUnix":1588568886}
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
|
||||
// TwoFactor is a 2FA token of a user.
|
||||
type TwoFactor struct {
|
||||
ID int64
|
||||
UserID int64 `xorm:"UNIQUE" gorm:"UNIQUE"`
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
UserID int64 `xorm:"UNIQUE" gorm:"unique"`
|
||||
Secret string
|
||||
Created time.Time `xorm:"-" gorm:"-" json:"-"`
|
||||
CreatedUnix int64
|
||||
|
||||
@@ -11,16 +11,40 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"gogs.io/gogs/internal/dbtest"
|
||||
"gogs.io/gogs/internal/errutil"
|
||||
)
|
||||
|
||||
func TestTwoFactor_BeforeCreate(t *testing.T) {
|
||||
now := time.Now()
|
||||
db := &gorm.DB{
|
||||
Config: &gorm.Config{
|
||||
SkipDefaultTransaction: true,
|
||||
NowFunc: func() time.Time {
|
||||
return now
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("CreatedUnix has been set", func(t *testing.T) {
|
||||
tf := &TwoFactor{CreatedUnix: 1}
|
||||
_ = tf.BeforeCreate(db)
|
||||
assert.Equal(t, int64(1), tf.CreatedUnix)
|
||||
})
|
||||
|
||||
t.Run("CreatedUnix has not been set", func(t *testing.T) {
|
||||
tf := &TwoFactor{}
|
||||
_ = tf.BeforeCreate(db)
|
||||
assert.Equal(t, db.NowFunc().Unix(), tf.CreatedUnix)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTwoFactors(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(TwoFactor), new(TwoFactorRecoveryCode)}
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogs/git-module"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// CommitToPushCommit transforms a git.Commit to PushCommit type.
|
||||
@@ -50,6 +52,8 @@ type PushUpdateOptions struct {
|
||||
// PushUpdate must be called for any push actions in order to
|
||||
// generates necessary push action history feeds.
|
||||
func PushUpdate(opts PushUpdateOptions) (err error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
isNewRef := strings.HasPrefix(opts.OldCommitID, git.EmptyID)
|
||||
isDelRef := strings.HasPrefix(opts.NewCommitID, git.EmptyID)
|
||||
if isNewRef && isDelRef {
|
||||
@@ -85,16 +89,17 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
|
||||
|
||||
// Push tags
|
||||
if strings.HasPrefix(opts.FullRefspec, git.RefsTags) {
|
||||
if err := CommitRepoAction(CommitRepoActionOptions{
|
||||
PusherName: opts.PusherName,
|
||||
RepoOwnerID: owner.ID,
|
||||
RepoName: repo.Name,
|
||||
RefFullName: opts.FullRefspec,
|
||||
OldCommitID: opts.OldCommitID,
|
||||
NewCommitID: opts.NewCommitID,
|
||||
Commits: &PushCommits{},
|
||||
}); err != nil {
|
||||
return fmt.Errorf("CommitRepoAction.(tag): %v", err)
|
||||
err := Actions.PushTag(ctx,
|
||||
PushTagOptions{
|
||||
Owner: owner,
|
||||
Repo: repo,
|
||||
PusherName: opts.PusherName,
|
||||
RefFullName: opts.FullRefspec,
|
||||
NewCommitID: opts.NewCommitID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create action for push tag")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -122,16 +127,19 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := CommitRepoAction(CommitRepoActionOptions{
|
||||
PusherName: opts.PusherName,
|
||||
RepoOwnerID: owner.ID,
|
||||
RepoName: repo.Name,
|
||||
RefFullName: opts.FullRefspec,
|
||||
OldCommitID: opts.OldCommitID,
|
||||
NewCommitID: opts.NewCommitID,
|
||||
Commits: CommitsToPushCommits(commits),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("CommitRepoAction.(branch): %v", err)
|
||||
err = Actions.CommitRepo(ctx,
|
||||
CommitRepoOptions{
|
||||
Owner: owner,
|
||||
Repo: repo,
|
||||
PusherName: opts.PusherName,
|
||||
RefFullName: opts.FullRefspec,
|
||||
OldCommitID: opts.OldCommitID,
|
||||
NewCommitID: opts.NewCommitID,
|
||||
Commits: CommitsToPushCommits(commits),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "create action for commit push")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -49,14 +49,14 @@ const (
|
||||
|
||||
// User represents the object of individual and member of organization.
|
||||
type User struct {
|
||||
ID int64
|
||||
LowerName string `xorm:"UNIQUE NOT NULL" gorm:"UNIQUE"`
|
||||
Name string `xorm:"UNIQUE NOT NULL" gorm:"NOT NULL"`
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
LowerName string `xorm:"UNIQUE NOT NULL" gorm:"unique;not null"`
|
||||
Name string `xorm:"UNIQUE NOT NULL" gorm:"not null"`
|
||||
FullName string
|
||||
// Email is the primary email address (to be used for communication)
|
||||
Email string `xorm:"NOT NULL" gorm:"NOT NULL"`
|
||||
Passwd string `xorm:"NOT NULL" gorm:"NOT NULL"`
|
||||
LoginSource int64 `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"`
|
||||
Email string `xorm:"NOT NULL" gorm:"not null"`
|
||||
Passwd string `xorm:"NOT NULL" gorm:"not null"`
|
||||
LoginSource int64 `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
|
||||
LoginName string
|
||||
Type UserType
|
||||
OwnedOrgs []*User `xorm:"-" gorm:"-" json:"-"`
|
||||
@@ -64,8 +64,8 @@ type User struct {
|
||||
Repos []*Repository `xorm:"-" gorm:"-" json:"-"`
|
||||
Location string
|
||||
Website string
|
||||
Rands string `xorm:"VARCHAR(10)" gorm:"TYPE:VARCHAR(10)"`
|
||||
Salt string `xorm:"VARCHAR(10)" gorm:"TYPE:VARCHAR(10)"`
|
||||
Rands string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
|
||||
Salt string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
|
||||
|
||||
Created time.Time `xorm:"-" gorm:"-" json:"-"`
|
||||
CreatedUnix int64
|
||||
@@ -75,7 +75,7 @@ type User struct {
|
||||
// Remember visibility choice for convenience, true for private
|
||||
LastRepoVisibility bool
|
||||
// Maximum repository creation limit, -1 means use global default
|
||||
MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1" gorm:"NOT NULL;DEFAULT:-1"`
|
||||
MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1" gorm:"not null;default:-1"`
|
||||
|
||||
// Permissions
|
||||
IsActive bool // Activate primary email
|
||||
@@ -85,13 +85,13 @@ type User struct {
|
||||
ProhibitLogin bool
|
||||
|
||||
// Avatar
|
||||
Avatar string `xorm:"VARCHAR(2048) NOT NULL" gorm:"TYPE:VARCHAR(2048);NOT NULL"`
|
||||
AvatarEmail string `xorm:"NOT NULL" gorm:"NOT NULL"`
|
||||
Avatar string `xorm:"VARCHAR(2048) NOT NULL" gorm:"type:VARCHAR(2048);not null"`
|
||||
AvatarEmail string `xorm:"NOT NULL" gorm:"not null"`
|
||||
UseCustomAvatar bool
|
||||
|
||||
// Counters
|
||||
NumFollowers int
|
||||
NumFollowing int `xorm:"NOT NULL DEFAULT 0" gorm:"NOT NULL;DEFAULT:0"`
|
||||
NumFollowing int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
|
||||
NumStars int
|
||||
NumRepos int
|
||||
|
||||
@@ -466,7 +466,7 @@ func (u *User) DisplayName() string {
|
||||
}
|
||||
|
||||
func (u *User) ShortName(length int) string {
|
||||
return tool.EllipsisString(u.Name, length)
|
||||
return strutil.Ellipsis(u.Name, length)
|
||||
}
|
||||
|
||||
// IsMailable checks if a user is eligible
|
||||
@@ -908,6 +908,8 @@ func DeleteInactivateUsers() (err error) {
|
||||
}
|
||||
|
||||
// UserPath returns the path absolute path of user repositories.
|
||||
//
|
||||
// Deprecated: Use repoutil.UserPath instead.
|
||||
func UserPath(username string) string {
|
||||
return filepath.Join(conf.Repository.Root, strings.ToLower(username))
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
// primary email address, but is not obligatory.
|
||||
type EmailAddress struct {
|
||||
ID int64
|
||||
UID int64 `xorm:"INDEX NOT NULL" gorm:"INDEX"`
|
||||
Email string `xorm:"UNIQUE NOT NULL" gorm:"UNIQUE"`
|
||||
IsActivated bool `gorm:"NOT NULL;DEFAULT:FALSE"`
|
||||
UID int64 `xorm:"INDEX NOT NULL" gorm:"index;not null"`
|
||||
Email string `xorm:"UNIQUE NOT NULL" gorm:"unique;not null"`
|
||||
IsActivated bool `gorm:"not null;default:FALSE"`
|
||||
IsPrimary bool `xorm:"-" gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ type UsersStore interface {
|
||||
// Create creates a new user and persists to database. It returns
|
||||
// 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 CreateUserOpts) (*User, error)
|
||||
Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error)
|
||||
// GetByEmail returns the user (not organization) with given email. It ignores
|
||||
// records with unverified emails and returns ErrUserNotExist when not found.
|
||||
GetByEmail(ctx context.Context, email string) (*User, error)
|
||||
@@ -74,6 +74,12 @@ type users struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
// NewUsersStore returns a persistent interface for users with given database
|
||||
// connection.
|
||||
func NewUsersStore(db *gorm.DB) UsersStore {
|
||||
return &users{DB: db}
|
||||
}
|
||||
|
||||
type ErrLoginSourceMismatch struct {
|
||||
args errutil.Args
|
||||
}
|
||||
@@ -154,7 +160,7 @@ func (db *users) Authenticate(ctx context.Context, login, password string, login
|
||||
}
|
||||
|
||||
return db.Create(ctx, extAccount.Name, extAccount.Email,
|
||||
CreateUserOpts{
|
||||
CreateUserOptions{
|
||||
FullName: extAccount.FullName,
|
||||
LoginSource: authSourceID,
|
||||
LoginName: extAccount.Login,
|
||||
@@ -166,7 +172,7 @@ func (db *users) Authenticate(ctx context.Context, login, password string, login
|
||||
)
|
||||
}
|
||||
|
||||
type CreateUserOpts struct {
|
||||
type CreateUserOptions struct {
|
||||
FullName string
|
||||
Password string
|
||||
LoginSource int64
|
||||
@@ -211,7 +217,7 @@ func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("email has been used: %v", err.args)
|
||||
}
|
||||
|
||||
func (db *users) Create(ctx context.Context, username, email string, opts CreateUserOpts) (*User, error) {
|
||||
func (db *users) Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error) {
|
||||
err := isUsernameAllowed(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -22,7 +22,6 @@ func TestUsers(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(User), new(EmailAddress)}
|
||||
@@ -58,7 +57,7 @@ func usersAuthenticate(t *testing.T, db *users) {
|
||||
|
||||
password := "pa$$word"
|
||||
alice, err := db.Create(ctx, "alice", "alice@example.com",
|
||||
CreateUserOpts{
|
||||
CreateUserOptions{
|
||||
Password: password,
|
||||
},
|
||||
)
|
||||
@@ -109,7 +108,7 @@ func usersAuthenticate(t *testing.T, db *users) {
|
||||
setMockLoginSourcesStore(t, mockLoginSources)
|
||||
|
||||
bob, err := db.Create(ctx, "bob", "bob@example.com",
|
||||
CreateUserOpts{
|
||||
CreateUserOptions{
|
||||
Password: password,
|
||||
LoginSource: 1,
|
||||
},
|
||||
@@ -154,26 +153,26 @@ func usersCreate(t *testing.T, db *users) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := db.Create(ctx, "alice", "alice@example.com",
|
||||
CreateUserOpts{
|
||||
CreateUserOptions{
|
||||
Activated: true,
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("name not allowed", func(t *testing.T) {
|
||||
_, err := db.Create(ctx, "-", "", CreateUserOpts{})
|
||||
_, err := db.Create(ctx, "-", "", CreateUserOptions{})
|
||||
wantErr := ErrNameNotAllowed{args: errutil.Args{"reason": "reserved", "name": "-"}}
|
||||
assert.Equal(t, wantErr, err)
|
||||
})
|
||||
|
||||
t.Run("name already exists", func(t *testing.T) {
|
||||
_, err := db.Create(ctx, alice.Name, "", CreateUserOpts{})
|
||||
_, err := db.Create(ctx, alice.Name, "", CreateUserOptions{})
|
||||
wantErr := ErrUserAlreadyExist{args: errutil.Args{"name": alice.Name}}
|
||||
assert.Equal(t, wantErr, err)
|
||||
})
|
||||
|
||||
t.Run("email already exists", func(t *testing.T) {
|
||||
_, err := db.Create(ctx, "bob", alice.Email, CreateUserOpts{})
|
||||
_, err := db.Create(ctx, "bob", alice.Email, CreateUserOptions{})
|
||||
wantErr := ErrEmailAlreadyUsed{args: errutil.Args{"email": alice.Email}}
|
||||
assert.Equal(t, wantErr, err)
|
||||
})
|
||||
@@ -195,7 +194,7 @@ func usersGetByEmail(t *testing.T, db *users) {
|
||||
|
||||
t.Run("ignore organization", func(t *testing.T) {
|
||||
// TODO: Use Orgs.Create to replace SQL hack when the method is available.
|
||||
org, err := db.Create(ctx, "gogs", "gogs@exmaple.com", CreateUserOpts{})
|
||||
org, err := db.Create(ctx, "gogs", "gogs@exmaple.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = db.Model(&User{}).Where("id", org.ID).UpdateColumn("type", UserOrganization).Error
|
||||
@@ -207,7 +206,7 @@ func usersGetByEmail(t *testing.T, db *users) {
|
||||
})
|
||||
|
||||
t.Run("by primary email", func(t *testing.T) {
|
||||
alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOpts{})
|
||||
alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.GetByEmail(ctx, alice.Email)
|
||||
@@ -225,7 +224,7 @@ func usersGetByEmail(t *testing.T, db *users) {
|
||||
})
|
||||
|
||||
t.Run("by secondary email", func(t *testing.T) {
|
||||
bob, err := db.Create(ctx, "bob", "bob@example.com", CreateUserOpts{})
|
||||
bob, err := db.Create(ctx, "bob", "bob@example.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: Use UserEmails.Create to replace SQL hack when the method is available.
|
||||
@@ -250,7 +249,7 @@ func usersGetByEmail(t *testing.T, db *users) {
|
||||
func usersGetByID(t *testing.T, db *users) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOpts{})
|
||||
alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := db.GetByID(ctx, alice.ID)
|
||||
@@ -265,7 +264,7 @@ func usersGetByID(t *testing.T, db *users) {
|
||||
func usersGetByUsername(t *testing.T, db *users) {
|
||||
ctx := context.Background()
|
||||
|
||||
alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOpts{})
|
||||
alice, err := db.Create(ctx, "alice", "alice@exmaple.com", CreateUserOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := db.GetByUsername(ctx, alice.Name)
|
||||
|
||||
38
internal/db/watches.go
Normal file
38
internal/db/watches.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2020 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// WatchesStore is the persistent interface for watches.
|
||||
//
|
||||
// NOTE: All methods are sorted in alphabetical order.
|
||||
type WatchesStore interface {
|
||||
// ListByRepo returns all watches of the given repository.
|
||||
ListByRepo(ctx context.Context, repoID int64) ([]*Watch, error)
|
||||
}
|
||||
|
||||
var Watches WatchesStore
|
||||
|
||||
var _ WatchesStore = (*watches)(nil)
|
||||
|
||||
type watches struct {
|
||||
*gorm.DB
|
||||
}
|
||||
|
||||
// NewWatchesStore returns a persistent interface for watches with given
|
||||
// database connection.
|
||||
func NewWatchesStore(db *gorm.DB) WatchesStore {
|
||||
return &watches{DB: db}
|
||||
}
|
||||
|
||||
func (db *watches) ListByRepo(ctx context.Context, repoID int64) ([]*Watch, error) {
|
||||
var watches []*Watch
|
||||
return watches, db.WithContext(ctx).Where("repo_id = ?", repoID).Find(&watches).Error
|
||||
}
|
||||
47
internal/db/watches_test.go
Normal file
47
internal/db/watches_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gogs.io/gogs/internal/dbtest"
|
||||
)
|
||||
|
||||
func TestWatches(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
t.Parallel()
|
||||
|
||||
tables := []interface{}{new(Watch)}
|
||||
db := &watches{
|
||||
DB: dbtest.NewDB(t, "watches", tables...),
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
test func(*testing.T, *watches)
|
||||
}{
|
||||
{"ListByRepo", watchesListByRepo},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Cleanup(func() {
|
||||
err := clearTables(t, db.DB, tables...)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
tc.test(t, db)
|
||||
})
|
||||
if t.Failed() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func watchesListByRepo(_ *testing.T, _ *watches) {
|
||||
// TODO: Add tests once WatchRepo is migrated to GORM.
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"gogs.io/gogs/internal/httplib"
|
||||
"gogs.io/gogs/internal/netutil"
|
||||
"gogs.io/gogs/internal/sync"
|
||||
"gogs.io/gogs/internal/testutil"
|
||||
)
|
||||
|
||||
var HookQueue = sync.NewUniqueQueue(1000)
|
||||
@@ -676,6 +677,11 @@ func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payl
|
||||
|
||||
// PrepareWebhooks adds all active webhooks to task queue.
|
||||
func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
|
||||
// NOTE: To prevent too many cascading changes in a single refactoring PR, we
|
||||
// choose to ignore this function in tests.
|
||||
if x == nil && testutil.InTest {
|
||||
return nil
|
||||
}
|
||||
return prepareWebhooks(x, repo, event, p)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/gogs/git-module"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/repoutil"
|
||||
"gogs.io/gogs/internal/sync"
|
||||
)
|
||||
|
||||
@@ -37,7 +38,9 @@ func ToWikiPageName(urlString string) string {
|
||||
}
|
||||
|
||||
// WikiCloneLink returns clone URLs of repository wiki.
|
||||
func (repo *Repository) WikiCloneLink() (cl *CloneLink) {
|
||||
//
|
||||
// Deprecated: Use repoutil.NewCloneLink instead.
|
||||
func (repo *Repository) WikiCloneLink() (cl *repoutil.CloneLink) {
|
||||
return repo.cloneLink(true)
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ func NewDB(t *testing.T, suite string, tables ...interface{}) *gorm.DB {
|
||||
|
||||
dbOpts.Name = dbName
|
||||
|
||||
cleanup = func(db *gorm.DB) {
|
||||
db.Exec(fmt.Sprintf("DROP DATABASE `%s`", dbName))
|
||||
cleanup = func(_ *gorm.DB) {
|
||||
_, _ = sqlDB.Exec(fmt.Sprintf("DROP DATABASE `%s`", dbName))
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
case "postgres":
|
||||
@@ -86,8 +86,8 @@ func NewDB(t *testing.T, suite string, tables ...interface{}) *gorm.DB {
|
||||
|
||||
dbOpts.Name = dbName
|
||||
|
||||
cleanup = func(db *gorm.DB) {
|
||||
db.Exec(fmt.Sprintf(`DROP DATABASE %q`, dbName))
|
||||
cleanup = func(_ *gorm.DB) {
|
||||
_, _ = sqlDB.Exec(fmt.Sprintf(`DROP DATABASE %q`, dbName))
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
case "sqlite":
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
package lazyregexp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gogs.io/gogs/internal/testutil"
|
||||
)
|
||||
|
||||
// Regexp is a wrapper around regexp.Regexp, where the underlying regexp will be
|
||||
@@ -99,14 +99,12 @@ func (r *Regexp) ReplaceAll(src, repl []byte) []byte {
|
||||
return r.Regexp().ReplaceAll(src, repl)
|
||||
}
|
||||
|
||||
var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
|
||||
|
||||
// New creates a new lazy regexp, delaying the compiling work until it is first
|
||||
// needed. If the code is being run as part of tests, the regexp compiling will
|
||||
// happen immediately.
|
||||
func New(str string) *Regexp {
|
||||
lr := &Regexp{str: str}
|
||||
if inTest {
|
||||
if testutil.InTest {
|
||||
// In tests, always compile the regexps early.
|
||||
lr.Regexp()
|
||||
}
|
||||
|
||||
62
internal/repoutil/repoutil.go
Normal file
62
internal/repoutil/repoutil.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repoutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
)
|
||||
|
||||
// CloneLink represents different types of clone URLs of repository.
|
||||
type CloneLink struct {
|
||||
SSH string
|
||||
HTTPS string
|
||||
}
|
||||
|
||||
// NewCloneLink returns clone URLs using given owner and repository name.
|
||||
func NewCloneLink(owner, repo string, isWiki bool) *CloneLink {
|
||||
if isWiki {
|
||||
repo += ".wiki"
|
||||
}
|
||||
|
||||
cl := new(CloneLink)
|
||||
if conf.SSH.Port != 22 {
|
||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", conf.App.RunUser, conf.SSH.Domain, conf.SSH.Port, owner, repo)
|
||||
} else {
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", conf.App.RunUser, conf.SSH.Domain, owner, repo)
|
||||
}
|
||||
cl.HTTPS = HTTPSCloneURL(owner, repo)
|
||||
return cl
|
||||
}
|
||||
|
||||
// HTTPSCloneURL returns HTTPS clone URL using given owner and repository name.
|
||||
func HTTPSCloneURL(owner, repo string) string {
|
||||
return fmt.Sprintf("%s%s/%s.git", conf.Server.ExternalURL, owner, repo)
|
||||
}
|
||||
|
||||
// HTMLURL returns HTML URL using given owner and repository name.
|
||||
func HTMLURL(owner, repo string) string {
|
||||
return conf.Server.ExternalURL + owner + "/" + repo
|
||||
}
|
||||
|
||||
// CompareCommitsPath returns the comparison path using given owner, repository,
|
||||
// and commit IDs.
|
||||
func CompareCommitsPath(owner, repo, oldCommitID, newCommitID string) string {
|
||||
return fmt.Sprintf("%s/%s/compare/%s...%s", owner, repo, oldCommitID, newCommitID)
|
||||
}
|
||||
|
||||
// UserPath returns the absolute path for storing user repositories.
|
||||
func UserPath(user string) string {
|
||||
return filepath.Join(conf.Repository.Root, strings.ToLower(user))
|
||||
}
|
||||
|
||||
// RepositoryPath returns the absolute path using given user and repository
|
||||
// name.
|
||||
func RepositoryPath(owner, repo string) string {
|
||||
return filepath.Join(UserPath(owner), strings.ToLower(repo)+".git")
|
||||
}
|
||||
127
internal/repoutil/repoutil_test.go
Normal file
127
internal/repoutil/repoutil_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package repoutil
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"gogs.io/gogs/internal/conf"
|
||||
)
|
||||
|
||||
func TestNewCloneLink(t *testing.T) {
|
||||
conf.SetMockApp(t,
|
||||
conf.AppOpts{
|
||||
RunUser: "git",
|
||||
},
|
||||
)
|
||||
conf.SetMockServer(t,
|
||||
conf.ServerOpts{
|
||||
ExternalURL: "https://example.com/",
|
||||
},
|
||||
)
|
||||
|
||||
t.Run("regular SSH port", func(t *testing.T) {
|
||||
conf.SetMockSSH(t,
|
||||
conf.SSHOpts{
|
||||
Domain: "example.com",
|
||||
Port: 22,
|
||||
},
|
||||
)
|
||||
|
||||
got := NewCloneLink("alice", "example", false)
|
||||
want := &CloneLink{
|
||||
SSH: "git@example.com:alice/example.git",
|
||||
HTTPS: "https://example.com/alice/example.git",
|
||||
}
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("irregular SSH port", func(t *testing.T) {
|
||||
conf.SetMockSSH(t,
|
||||
conf.SSHOpts{
|
||||
Domain: "example.com",
|
||||
Port: 2222,
|
||||
},
|
||||
)
|
||||
|
||||
got := NewCloneLink("alice", "example", false)
|
||||
want := &CloneLink{
|
||||
SSH: "ssh://git@example.com:2222/alice/example.git",
|
||||
HTTPS: "https://example.com/alice/example.git",
|
||||
}
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
|
||||
t.Run("wiki", func(t *testing.T) {
|
||||
conf.SetMockSSH(t,
|
||||
conf.SSHOpts{
|
||||
Domain: "example.com",
|
||||
Port: 22,
|
||||
},
|
||||
)
|
||||
|
||||
got := NewCloneLink("alice", "example", true)
|
||||
want := &CloneLink{
|
||||
SSH: "git@example.com:alice/example.wiki.git",
|
||||
HTTPS: "https://example.com/alice/example.wiki.git",
|
||||
}
|
||||
assert.Equal(t, want, got)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHTMLURL(t *testing.T) {
|
||||
conf.SetMockServer(t,
|
||||
conf.ServerOpts{
|
||||
ExternalURL: "https://example.com/",
|
||||
},
|
||||
)
|
||||
|
||||
got := HTMLURL("alice", "example")
|
||||
want := "https://example.com/alice/example"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestCompareCommitsPath(t *testing.T) {
|
||||
got := CompareCommitsPath("alice", "example", "old", "new")
|
||||
want := "alice/example/compare/old...new"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestUserPath(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Skipping testing on Windows")
|
||||
return
|
||||
}
|
||||
|
||||
conf.SetMockRepository(t,
|
||||
conf.RepositoryOpts{
|
||||
Root: "/home/git/gogs-repositories",
|
||||
},
|
||||
)
|
||||
|
||||
got := UserPath("alice")
|
||||
want := "/home/git/gogs-repositories/alice"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func TestRepositoryPath(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Skipping testing on Windows")
|
||||
return
|
||||
}
|
||||
|
||||
conf.SetMockRepository(t,
|
||||
conf.RepositoryOpts{
|
||||
Root: "/home/git/gogs-repositories",
|
||||
},
|
||||
)
|
||||
|
||||
got := RepositoryPath("alice", "example")
|
||||
want := "/home/git/gogs-repositories/alice/example.git"
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func Authentications(c *context.Context) {
|
||||
c.PageIs("AdminAuthentications")
|
||||
|
||||
var err error
|
||||
c.Data["Sources"], err = db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOpts{})
|
||||
c.Data["Sources"], err = db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOptions{})
|
||||
if err != nil {
|
||||
c.Error(err, "list login sources")
|
||||
return
|
||||
@@ -160,7 +160,7 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) {
|
||||
}
|
||||
|
||||
source, err := db.LoginSources.Create(c.Req.Context(),
|
||||
db.CreateLoginSourceOpts{
|
||||
db.CreateLoginSourceOptions{
|
||||
Type: auth.Type(f.Type),
|
||||
Name: f.Name,
|
||||
Activated: f.IsActive,
|
||||
|
||||
@@ -46,7 +46,7 @@ func NewUser(c *context.Context) {
|
||||
|
||||
c.Data["login_type"] = "0-0"
|
||||
|
||||
sources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOpts{})
|
||||
sources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOptions{})
|
||||
if err != nil {
|
||||
c.Error(err, "list login sources")
|
||||
return
|
||||
@@ -62,7 +62,7 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
|
||||
c.Data["PageIsAdmin"] = true
|
||||
c.Data["PageIsAdminUsers"] = true
|
||||
|
||||
sources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOpts{})
|
||||
sources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOptions{})
|
||||
if err != nil {
|
||||
c.Error(err, "list login sources")
|
||||
return
|
||||
@@ -136,7 +136,7 @@ func prepareUserInfo(c *context.Context) *db.User {
|
||||
c.Data["LoginSource"] = &db.LoginSource{}
|
||||
}
|
||||
|
||||
sources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOpts{})
|
||||
sources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOptions{})
|
||||
if err != nil {
|
||||
c.Error(err, "list login sources")
|
||||
return nil
|
||||
|
||||
@@ -66,7 +66,7 @@ func Search(c *context.APIContext) {
|
||||
|
||||
results := make([]*api.Repository, len(repos))
|
||||
for i := range repos {
|
||||
results[i] = repos[i].APIFormat(nil)
|
||||
results[i] = repos[i].APIFormatLegacy(nil)
|
||||
}
|
||||
|
||||
c.SetLinkHeader(int(count), opts.PageSize)
|
||||
@@ -110,7 +110,7 @@ func listUserRepositories(c *context.APIContext, username string) {
|
||||
if c.User.ID != user.ID {
|
||||
repos := make([]*api.Repository, len(ownRepos))
|
||||
for i := range ownRepos {
|
||||
repos[i] = ownRepos[i].APIFormat(&api.Permission{Admin: true, Push: true, Pull: true})
|
||||
repos[i] = ownRepos[i].APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true})
|
||||
}
|
||||
c.JSONSuccess(&repos)
|
||||
return
|
||||
@@ -125,12 +125,12 @@ func listUserRepositories(c *context.APIContext, username string) {
|
||||
numOwnRepos := len(ownRepos)
|
||||
repos := make([]*api.Repository, numOwnRepos+len(accessibleRepos))
|
||||
for i := range ownRepos {
|
||||
repos[i] = ownRepos[i].APIFormat(&api.Permission{Admin: true, Push: true, Pull: true})
|
||||
repos[i] = ownRepos[i].APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true})
|
||||
}
|
||||
|
||||
i := numOwnRepos
|
||||
for repo, access := range accessibleRepos {
|
||||
repos[i] = repo.APIFormat(&api.Permission{
|
||||
repos[i] = repo.APIFormatLegacy(&api.Permission{
|
||||
Admin: access >= db.AccessModeAdmin,
|
||||
Push: access >= db.AccessModeWrite,
|
||||
Pull: true,
|
||||
@@ -154,7 +154,7 @@ func ListOrgRepositories(c *context.APIContext) {
|
||||
}
|
||||
|
||||
func CreateUserRepo(c *context.APIContext, owner *db.User, opt api.CreateRepoOption) {
|
||||
repo, err := db.CreateRepository(c.User, owner, db.CreateRepoOptions{
|
||||
repo, err := db.CreateRepository(c.User, owner, db.CreateRepoOptionsLegacy{
|
||||
Name: opt.Name,
|
||||
Description: opt.Description,
|
||||
Gitignores: opt.Gitignores,
|
||||
@@ -178,7 +178,7 @@ func CreateUserRepo(c *context.APIContext, owner *db.User, opt api.CreateRepoOpt
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(201, repo.APIFormat(&api.Permission{Admin: true, Push: true, Pull: true}))
|
||||
c.JSON(201, repo.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
|
||||
}
|
||||
|
||||
func Create(c *context.APIContext, opt api.CreateRepoOption) {
|
||||
@@ -282,7 +282,7 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
|
||||
}
|
||||
|
||||
log.Trace("Repository migrated: %s/%s", ctxUser.Name, f.RepoName)
|
||||
c.JSON(201, repo.APIFormat(&api.Permission{Admin: true, Push: true, Pull: true}))
|
||||
c.JSON(201, repo.APIFormatLegacy(&api.Permission{Admin: true, Push: true, Pull: true}))
|
||||
}
|
||||
|
||||
// FIXME: inject in the handler chain
|
||||
@@ -312,7 +312,7 @@ func Get(c *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSONSuccess(repo.APIFormat(&api.Permission{
|
||||
c.JSONSuccess(repo.APIFormatLegacy(&api.Permission{
|
||||
Admin: c.Repo.IsAdmin(),
|
||||
Push: c.Repo.IsWriter(),
|
||||
Pull: true,
|
||||
@@ -352,7 +352,7 @@ func ListForks(c *context.APIContext) {
|
||||
c.Error(err, "get owner")
|
||||
return
|
||||
}
|
||||
apiForks[i] = forks[i].APIFormat(&api.Permission{
|
||||
apiForks[i] = forks[i].APIFormatLegacy(&api.Permission{
|
||||
Admin: c.User.IsAdminOfRepo(forks[i]),
|
||||
Push: c.User.IsWriterOfRepo(forks[i]),
|
||||
Pull: true,
|
||||
|
||||
@@ -1492,20 +1492,36 @@ func (c PermsStoreSetRepoPermsFuncCall) Results() []interface{} {
|
||||
// MockReposStore is a mock implementation of the ReposStore interface (from
|
||||
// the package gogs.io/gogs/internal/db) used for unit testing.
|
||||
type MockReposStore struct {
|
||||
// CreateFunc is an instance of a mock function object controlling the
|
||||
// behavior of the method Create.
|
||||
CreateFunc *ReposStoreCreateFunc
|
||||
// GetByNameFunc is an instance of a mock function object controlling
|
||||
// the behavior of the method GetByName.
|
||||
GetByNameFunc *ReposStoreGetByNameFunc
|
||||
// TouchFunc is an instance of a mock function object controlling the
|
||||
// behavior of the method Touch.
|
||||
TouchFunc *ReposStoreTouchFunc
|
||||
}
|
||||
|
||||
// NewMockReposStore creates a new mock of the ReposStore interface. All
|
||||
// methods return zero values for all results, unless overwritten.
|
||||
func NewMockReposStore() *MockReposStore {
|
||||
return &MockReposStore{
|
||||
CreateFunc: &ReposStoreCreateFunc{
|
||||
defaultHook: func(context.Context, int64, db.CreateRepoOptions) (r0 *db.Repository, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
GetByNameFunc: &ReposStoreGetByNameFunc{
|
||||
defaultHook: func(context.Context, int64, string) (r0 *db.Repository, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
TouchFunc: &ReposStoreTouchFunc{
|
||||
defaultHook: func(context.Context, int64) (r0 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1513,11 +1529,21 @@ func NewMockReposStore() *MockReposStore {
|
||||
// All methods panic on invocation, unless overwritten.
|
||||
func NewStrictMockReposStore() *MockReposStore {
|
||||
return &MockReposStore{
|
||||
CreateFunc: &ReposStoreCreateFunc{
|
||||
defaultHook: func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) {
|
||||
panic("unexpected invocation of MockReposStore.Create")
|
||||
},
|
||||
},
|
||||
GetByNameFunc: &ReposStoreGetByNameFunc{
|
||||
defaultHook: func(context.Context, int64, string) (*db.Repository, error) {
|
||||
panic("unexpected invocation of MockReposStore.GetByName")
|
||||
},
|
||||
},
|
||||
TouchFunc: &ReposStoreTouchFunc{
|
||||
defaultHook: func(context.Context, int64) error {
|
||||
panic("unexpected invocation of MockReposStore.Touch")
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1525,12 +1551,128 @@ func NewStrictMockReposStore() *MockReposStore {
|
||||
// All methods delegate to the given implementation, unless overwritten.
|
||||
func NewMockReposStoreFrom(i db.ReposStore) *MockReposStore {
|
||||
return &MockReposStore{
|
||||
CreateFunc: &ReposStoreCreateFunc{
|
||||
defaultHook: i.Create,
|
||||
},
|
||||
GetByNameFunc: &ReposStoreGetByNameFunc{
|
||||
defaultHook: i.GetByName,
|
||||
},
|
||||
TouchFunc: &ReposStoreTouchFunc{
|
||||
defaultHook: i.Touch,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ReposStoreCreateFunc describes the behavior when the Create method of the
|
||||
// parent MockReposStore instance is invoked.
|
||||
type ReposStoreCreateFunc struct {
|
||||
defaultHook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error)
|
||||
hooks []func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error)
|
||||
history []ReposStoreCreateFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Create delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockReposStore) Create(v0 context.Context, v1 int64, v2 db.CreateRepoOptions) (*db.Repository, error) {
|
||||
r0, r1 := m.CreateFunc.nextHook()(v0, v1, v2)
|
||||
m.CreateFunc.appendCall(ReposStoreCreateFuncCall{v0, v1, v2, r0, r1})
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the Create method of the
|
||||
// parent MockReposStore instance is invoked and the hook queue is empty.
|
||||
func (f *ReposStoreCreateFunc) SetDefaultHook(hook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// Create method of the parent MockReposStore 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 *ReposStoreCreateFunc) PushHook(hook func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, 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 *ReposStoreCreateFunc) SetDefaultReturn(r0 *db.Repository, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *ReposStoreCreateFunc) PushReturn(r0 *db.Repository, r1 error) {
|
||||
f.PushHook(func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *ReposStoreCreateFunc) nextHook() func(context.Context, int64, db.CreateRepoOptions) (*db.Repository, 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 *ReposStoreCreateFunc) appendCall(r0 ReposStoreCreateFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of ReposStoreCreateFuncCall objects describing
|
||||
// the invocations of this function.
|
||||
func (f *ReposStoreCreateFunc) History() []ReposStoreCreateFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]ReposStoreCreateFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// ReposStoreCreateFuncCall is an object that describes an invocation of
|
||||
// method Create on an instance of MockReposStore.
|
||||
type ReposStoreCreateFuncCall 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 db.CreateRepoOptions
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 *db.Repository
|
||||
// Result1 is the value of the 2nd result returned from this method
|
||||
// invocation.
|
||||
Result1 error
|
||||
}
|
||||
|
||||
// Args returns an interface slice containing the arguments of this
|
||||
// invocation.
|
||||
func (c ReposStoreCreateFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1, c.Arg2}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c ReposStoreCreateFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// ReposStoreGetByNameFunc describes the behavior when the GetByName method
|
||||
// of the parent MockReposStore instance is invoked.
|
||||
type ReposStoreGetByNameFunc struct {
|
||||
@@ -1642,6 +1784,110 @@ func (c ReposStoreGetByNameFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0, c.Result1}
|
||||
}
|
||||
|
||||
// ReposStoreTouchFunc describes the behavior when the Touch method of the
|
||||
// parent MockReposStore instance is invoked.
|
||||
type ReposStoreTouchFunc struct {
|
||||
defaultHook func(context.Context, int64) error
|
||||
hooks []func(context.Context, int64) error
|
||||
history []ReposStoreTouchFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Touch delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockReposStore) Touch(v0 context.Context, v1 int64) error {
|
||||
r0 := m.TouchFunc.nextHook()(v0, v1)
|
||||
m.TouchFunc.appendCall(ReposStoreTouchFuncCall{v0, v1, r0})
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetDefaultHook sets function that is called when the Touch method of the
|
||||
// parent MockReposStore instance is invoked and the hook queue is empty.
|
||||
func (f *ReposStoreTouchFunc) SetDefaultHook(hook func(context.Context, int64) error) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
// PushHook adds a function to the end of hook queue. Each invocation of the
|
||||
// Touch method of the parent MockReposStore 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 *ReposStoreTouchFunc) PushHook(hook func(context.Context, int64) 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 *ReposStoreTouchFunc) SetDefaultReturn(r0 error) {
|
||||
f.SetDefaultHook(func(context.Context, int64) error {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *ReposStoreTouchFunc) PushReturn(r0 error) {
|
||||
f.PushHook(func(context.Context, int64) error {
|
||||
return r0
|
||||
})
|
||||
}
|
||||
|
||||
func (f *ReposStoreTouchFunc) nextHook() func(context.Context, int64) 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 *ReposStoreTouchFunc) appendCall(r0 ReposStoreTouchFuncCall) {
|
||||
f.mutex.Lock()
|
||||
f.history = append(f.history, r0)
|
||||
f.mutex.Unlock()
|
||||
}
|
||||
|
||||
// History returns a sequence of ReposStoreTouchFuncCall objects describing
|
||||
// the invocations of this function.
|
||||
func (f *ReposStoreTouchFunc) History() []ReposStoreTouchFuncCall {
|
||||
f.mutex.Lock()
|
||||
history := make([]ReposStoreTouchFuncCall, len(f.history))
|
||||
copy(history, f.history)
|
||||
f.mutex.Unlock()
|
||||
|
||||
return history
|
||||
}
|
||||
|
||||
// ReposStoreTouchFuncCall is an object that describes an invocation of
|
||||
// method Touch on an instance of MockReposStore.
|
||||
type ReposStoreTouchFuncCall 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
|
||||
// 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 ReposStoreTouchFuncCall) Args() []interface{} {
|
||||
return []interface{}{c.Arg0, c.Arg1}
|
||||
}
|
||||
|
||||
// Results returns an interface slice containing the results of this
|
||||
// invocation.
|
||||
func (c ReposStoreTouchFuncCall) Results() []interface{} {
|
||||
return []interface{}{c.Result0}
|
||||
}
|
||||
|
||||
// MockTwoFactorsStore is a mock implementation of the TwoFactorsStore
|
||||
// interface (from the package gogs.io/gogs/internal/db) used for unit
|
||||
// testing.
|
||||
@@ -2074,7 +2320,7 @@ func NewMockUsersStore() *MockUsersStore {
|
||||
},
|
||||
},
|
||||
CreateFunc: &UsersStoreCreateFunc{
|
||||
defaultHook: func(context.Context, string, string, db.CreateUserOpts) (r0 *db.User, r1 error) {
|
||||
defaultHook: func(context.Context, string, string, db.CreateUserOptions) (r0 *db.User, r1 error) {
|
||||
return
|
||||
},
|
||||
},
|
||||
@@ -2106,7 +2352,7 @@ func NewStrictMockUsersStore() *MockUsersStore {
|
||||
},
|
||||
},
|
||||
CreateFunc: &UsersStoreCreateFunc{
|
||||
defaultHook: func(context.Context, string, string, db.CreateUserOpts) (*db.User, error) {
|
||||
defaultHook: func(context.Context, string, string, db.CreateUserOptions) (*db.User, error) {
|
||||
panic("unexpected invocation of MockUsersStore.Create")
|
||||
},
|
||||
},
|
||||
@@ -2267,15 +2513,15 @@ func (c UsersStoreAuthenticateFuncCall) Results() []interface{} {
|
||||
// UsersStoreCreateFunc describes the behavior when the Create method of the
|
||||
// parent MockUsersStore instance is invoked.
|
||||
type UsersStoreCreateFunc struct {
|
||||
defaultHook func(context.Context, string, string, db.CreateUserOpts) (*db.User, error)
|
||||
hooks []func(context.Context, string, string, db.CreateUserOpts) (*db.User, error)
|
||||
defaultHook func(context.Context, string, string, db.CreateUserOptions) (*db.User, error)
|
||||
hooks []func(context.Context, string, string, db.CreateUserOptions) (*db.User, error)
|
||||
history []UsersStoreCreateFuncCall
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Create delegates to the next hook function in the queue and stores the
|
||||
// parameter and result values of this invocation.
|
||||
func (m *MockUsersStore) Create(v0 context.Context, v1 string, v2 string, v3 db.CreateUserOpts) (*db.User, error) {
|
||||
func (m *MockUsersStore) Create(v0 context.Context, v1 string, v2 string, v3 db.CreateUserOptions) (*db.User, error) {
|
||||
r0, r1 := m.CreateFunc.nextHook()(v0, v1, v2, v3)
|
||||
m.CreateFunc.appendCall(UsersStoreCreateFuncCall{v0, v1, v2, v3, r0, r1})
|
||||
return r0, r1
|
||||
@@ -2283,7 +2529,7 @@ func (m *MockUsersStore) Create(v0 context.Context, v1 string, v2 string, v3 db.
|
||||
|
||||
// SetDefaultHook sets function that is called when the Create method of the
|
||||
// parent MockUsersStore instance is invoked and the hook queue is empty.
|
||||
func (f *UsersStoreCreateFunc) SetDefaultHook(hook func(context.Context, string, string, db.CreateUserOpts) (*db.User, error)) {
|
||||
func (f *UsersStoreCreateFunc) SetDefaultHook(hook func(context.Context, string, string, db.CreateUserOptions) (*db.User, error)) {
|
||||
f.defaultHook = hook
|
||||
}
|
||||
|
||||
@@ -2291,7 +2537,7 @@ func (f *UsersStoreCreateFunc) SetDefaultHook(hook func(context.Context, string,
|
||||
// Create 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 *UsersStoreCreateFunc) PushHook(hook func(context.Context, string, string, db.CreateUserOpts) (*db.User, error)) {
|
||||
func (f *UsersStoreCreateFunc) PushHook(hook func(context.Context, string, string, db.CreateUserOptions) (*db.User, error)) {
|
||||
f.mutex.Lock()
|
||||
f.hooks = append(f.hooks, hook)
|
||||
f.mutex.Unlock()
|
||||
@@ -2300,19 +2546,19 @@ func (f *UsersStoreCreateFunc) PushHook(hook func(context.Context, string, strin
|
||||
// SetDefaultReturn calls SetDefaultHook with a function that returns the
|
||||
// given values.
|
||||
func (f *UsersStoreCreateFunc) SetDefaultReturn(r0 *db.User, r1 error) {
|
||||
f.SetDefaultHook(func(context.Context, string, string, db.CreateUserOpts) (*db.User, error) {
|
||||
f.SetDefaultHook(func(context.Context, string, string, db.CreateUserOptions) (*db.User, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
// PushReturn calls PushHook with a function that returns the given values.
|
||||
func (f *UsersStoreCreateFunc) PushReturn(r0 *db.User, r1 error) {
|
||||
f.PushHook(func(context.Context, string, string, db.CreateUserOpts) (*db.User, error) {
|
||||
f.PushHook(func(context.Context, string, string, db.CreateUserOptions) (*db.User, error) {
|
||||
return r0, r1
|
||||
})
|
||||
}
|
||||
|
||||
func (f *UsersStoreCreateFunc) nextHook() func(context.Context, string, string, db.CreateUserOpts) (*db.User, error) {
|
||||
func (f *UsersStoreCreateFunc) nextHook() func(context.Context, string, string, db.CreateUserOptions) (*db.User, error) {
|
||||
f.mutex.Lock()
|
||||
defer f.mutex.Unlock()
|
||||
|
||||
@@ -2356,7 +2602,7 @@ type UsersStoreCreateFuncCall struct {
|
||||
Arg2 string
|
||||
// Arg3 is the value of the 4th argument passed to this method
|
||||
// invocation.
|
||||
Arg3 db.CreateUserOpts
|
||||
Arg3 db.CreateUserOptions
|
||||
// Result0 is the value of the 1st result returned from this method
|
||||
// invocation.
|
||||
Result0 *db.User
|
||||
|
||||
@@ -146,7 +146,7 @@ func DeleteBranchPost(c *context.Context) {
|
||||
Ref: branchName,
|
||||
RefType: "branch",
|
||||
PusherType: api.PUSHER_TYPE_USER,
|
||||
Repo: c.Repo.Repository.APIFormat(nil),
|
||||
Repo: c.Repo.Repository.APIFormatLegacy(nil),
|
||||
Sender: c.User.APIFormat(),
|
||||
}); err != nil {
|
||||
log.Error("Failed to prepare webhooks for %q: %v", db.HOOK_EVENT_DELETE, err)
|
||||
|
||||
@@ -119,7 +119,7 @@ func CreatePost(c *context.Context, f form.CreateRepo) {
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := db.CreateRepository(c.User, ctxUser, db.CreateRepoOptions{
|
||||
repo, err := db.CreateRepository(c.User, ctxUser, db.CreateRepoOptionsLegacy{
|
||||
Name: f.RepoName,
|
||||
Description: f.Description,
|
||||
Gitignores: f.Gitignores,
|
||||
|
||||
@@ -100,8 +100,8 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
|
||||
log.Trace("Repository basic settings updated: %s/%s", c.Repo.Owner.Name, repo.Name)
|
||||
|
||||
if isNameChanged {
|
||||
if err := db.RenameRepoAction(c.User, oldRepoName, repo); err != nil {
|
||||
log.Error("RenameRepoAction: %v", err)
|
||||
if err := db.Actions.RenameRepo(c.Req.Context(), c.User, repo.MustOwner(), oldRepoName, repo); err != nil {
|
||||
log.Error("create rename repository action: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -536,7 +536,7 @@ func TestWebhook(c *context.Context) {
|
||||
Modified: nameStatus.Modified,
|
||||
},
|
||||
},
|
||||
Repo: c.Repo.Repository.APIFormat(nil),
|
||||
Repo: c.Repo.Repository.APIFormatLegacy(nil),
|
||||
Pusher: apiUser,
|
||||
Sender: apiUser,
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ func Login(c *context.Context) {
|
||||
}
|
||||
|
||||
// Display normal login page
|
||||
loginSources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOpts{OnlyActivated: true})
|
||||
loginSources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOptions{OnlyActivated: true})
|
||||
if err != nil {
|
||||
c.Error(err, "list activated login sources")
|
||||
return
|
||||
@@ -149,7 +149,7 @@ func afterLogin(c *context.Context, u *db.User, remember bool) {
|
||||
func LoginPost(c *context.Context, f form.SignIn) {
|
||||
c.Title("sign_in")
|
||||
|
||||
loginSources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOpts{OnlyActivated: true})
|
||||
loginSources, err := db.LoginSources.List(c.Req.Context(), db.ListLoginSourceOptions{OnlyActivated: true})
|
||||
if err != nil {
|
||||
c.Error(err, "list activated login sources")
|
||||
return
|
||||
|
||||
@@ -53,9 +53,17 @@ func getDashboardContextUser(c *context.Context) *db.User {
|
||||
// The user could be organization so it is not always the logged in user,
|
||||
// which is why we have to explicitly pass the context user ID.
|
||||
func retrieveFeeds(c *context.Context, ctxUser *db.User, userID int64, isProfile bool) {
|
||||
actions, err := db.GetFeeds(ctxUser, userID, c.QueryInt64("after_id"), isProfile)
|
||||
afterID := c.QueryInt64("after_id")
|
||||
|
||||
var err error
|
||||
var actions []*db.Action
|
||||
if ctxUser.IsOrganization() {
|
||||
actions, err = db.Actions.ListByOrganization(c.Req.Context(), ctxUser.ID, userID, afterID)
|
||||
} else {
|
||||
actions, err = db.Actions.ListByUser(c.Req.Context(), ctxUser.ID, userID, afterID, isProfile)
|
||||
}
|
||||
if err != nil {
|
||||
c.Error(err, "get feeds")
|
||||
c.Error(err, "list actions")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ func Profile(c *context.Context, puser *context.ParamsUser) {
|
||||
c.Data["TabName"] = tab
|
||||
switch tab {
|
||||
case "activity":
|
||||
retrieveFeeds(c, puser.User, -1, true)
|
||||
retrieveFeeds(c, puser.User, c.UserID(), true)
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -44,3 +44,13 @@ func RandomChars(n int) (string, error) {
|
||||
|
||||
return string(buffer), nil
|
||||
}
|
||||
|
||||
// Ellipsis returns a truncated string and appends "..." to the end of the
|
||||
// string if the string length is larger than the threshold. Otherwise, the
|
||||
// original string is returned.
|
||||
func Ellipsis(str string, threshold int) string {
|
||||
if len(str) <= threshold || threshold < 0 {
|
||||
return str
|
||||
}
|
||||
return str[:threshold] + "..."
|
||||
}
|
||||
|
||||
@@ -55,3 +55,43 @@ func TestRandomChars(t *testing.T) {
|
||||
cache[chars] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestEllipsis(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
str string
|
||||
threshold int
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty string and zero threshold",
|
||||
str: "",
|
||||
threshold: 0,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "smaller length than threshold",
|
||||
str: "ab",
|
||||
threshold: 3,
|
||||
want: "ab",
|
||||
},
|
||||
{
|
||||
name: "same length as threshold",
|
||||
str: "abc",
|
||||
threshold: 3,
|
||||
want: "abc",
|
||||
},
|
||||
{
|
||||
name: "greater length than threshold",
|
||||
str: "ab",
|
||||
threshold: 1,
|
||||
want: "a...",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := Ellipsis(test.str, test.threshold)
|
||||
assert.Equal(t, test.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/gitutil"
|
||||
"gogs.io/gogs/internal/markup"
|
||||
"gogs.io/gogs/internal/strutil"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
@@ -106,7 +107,7 @@ func FuncMap() []template.FuncMap {
|
||||
return str[start:end]
|
||||
},
|
||||
"Join": strings.Join,
|
||||
"EllipsisString": tool.EllipsisString,
|
||||
"EllipsisString": strutil.Ellipsis,
|
||||
"DiffFileTypeToStr": DiffFileTypeToStr,
|
||||
"DiffLineTypeToStr": DiffLineTypeToStr,
|
||||
"Sha1": Sha1,
|
||||
|
||||
13
internal/testutil/testutil.go
Normal file
13
internal/testutil/testutil.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// InTest is ture if the current binary looks like a test artifact.
|
||||
var InTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
|
||||
15
internal/testutil/testutil_test.go
Normal file
15
internal/testutil/testutil_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// Copyright 2022 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInTest(t *testing.T) {
|
||||
assert.True(t, InTest)
|
||||
}
|
||||
@@ -358,15 +358,6 @@ func Subtract(left, right interface{}) interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// EllipsisString returns a truncated short string,
|
||||
// it appends '...' in the end of the length of string is too large.
|
||||
func EllipsisString(str string, length int) string {
|
||||
if len(str) < length {
|
||||
return str
|
||||
}
|
||||
return str[:length-3] + "..."
|
||||
}
|
||||
|
||||
// TruncateString returns a truncated string with given limit,
|
||||
// it returns input string if length is not reached limit.
|
||||
func TruncateString(str string, limit int) string {
|
||||
|
||||
Reference in New Issue
Block a user