aboutsummaryrefslogtreecommitdiff
path: root/models
diff options
context:
space:
mode:
Diffstat (limited to 'models')
-rw-r--r--models/action.go141
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/models.go91
-rw-r--r--models/org.go8
-rw-r--r--models/repo.go109
-rw-r--r--models/update.go45
-rw-r--r--models/webhook.go88
-rw-r--r--models/webhook_discord.go70
-rw-r--r--models/webhook_slack.go57
9 files changed, 429 insertions, 182 deletions
diff --git a/models/action.go b/models/action.go
index 0c6637a0..b68e9a47 100644
--- a/models/action.go
+++ b/models/action.go
@@ -26,6 +26,7 @@ import (
type ActionType int
+// To maintain backward compatibility only append to the end of list
const (
ACTION_CREATE_REPO ActionType = iota + 1 // 1
ACTION_RENAME_REPO // 2
@@ -42,6 +43,10 @@ const (
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
)
var (
@@ -66,7 +71,7 @@ func init() {
// 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 `xorm:"pk autoincr"`
+ ID int64
UserID int64 // Receiver user id.
OpType ActionType
ActUserID int64 // Action user id.
@@ -172,26 +177,26 @@ func (a *Action) GetIssueContent() string {
return issue.Content
}
-func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
- if err = notifyWatchers(e, &Action{
- ActUserID: u.ID,
- ActUserName: u.Name,
- OpType: ACTION_CREATE_REPO,
+func newRepoAction(e Engine, doer, owner *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,
- }); err != nil {
- return fmt.Errorf("notify watchers '%d/%d': %v", u.ID, repo.ID, err)
- }
-
- log.Trace("action.newRepoAction: %s/%s", u.Name, repo.Name)
- return err
+ })
}
// NewRepoAction adds new action for creating repository.
-func NewRepoAction(u *User, repo *Repository) (err error) {
- return newRepoAction(x, u, repo)
+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) {
@@ -458,18 +463,16 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
return fmt.Errorf("UpdateRepository: %v", err)
}
- isNewBranch := false
+ isNewRef := opts.OldCommitID == git.EMPTY_SHA
+ isDelRef := opts.NewCommitID == git.EMPTY_SHA
+
opType := ACTION_COMMIT_REPO
- // Check it's tag push or branch.
+ // Check if it's tag push or branch.
if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {
opType = ACTION_PUSH_TAG
- opts.Commits = &PushCommits{}
} else {
- // TODO: detect branch deletion
// if not the first commit, set the compare URL.
- if opts.OldCommitID == git.EMPTY_SHA {
- isNewBranch = true
- } else {
+ if !isNewRef && !isDelRef {
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
}
@@ -488,38 +491,57 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
}
refName := git.RefEndName(opts.RefFullName)
- if err = NotifyWatchers(&Action{
+ action := &Action{
ActUserID: pusher.ID,
ActUserName: pusher.Name,
- OpType: opType,
Content: string(data),
RepoID: repo.ID,
RepoUserName: repo.MustOwner().Name,
RepoName: repo.Name,
RefName: refName,
IsPrivate: repo.IsPrivate,
- }); err != nil {
- return fmt.Errorf("NotifyWatchers: %v", err)
}
- defer func() {
- go HookQueue.Add(repo.ID)
- }()
-
- apiPusher := pusher.APIFormat()
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 := setting.AppUrl + opts.Commits.CompareURL
- if isNewBranch {
+ if isNewRef {
compareURL = ""
if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
- Ref: refName,
- RefType: "branch",
- Repo: apiRepo,
- Sender: apiPusher,
+ Ref: refName,
+ RefType: "branch",
+ DefaultBranch: repo.DefaultBranch,
+ Repo: apiRepo,
+ Sender: apiPusher,
}); err != nil {
- return fmt.Errorf("PrepareWebhooks (new branch): %v", err)
+ 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)
}
}
@@ -533,16 +555,47 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
Pusher: apiPusher,
Sender: apiPusher,
}); err != nil {
- return fmt.Errorf("PrepareWebhooks (new commit): %v", err)
+ return fmt.Errorf("PrepareWebhooks.(new commit): %v", err)
}
- case ACTION_PUSH_TAG: // Create
- return PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
- Ref: refName,
- RefType: "tag",
- Repo: apiRepo,
- Sender: apiPusher,
- })
+ 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",
+ 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
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 765ad93c..e543ae62 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -42,7 +42,7 @@ func (m *migration) Migrate(x *xorm.Engine) error {
// The version table. Should have only one row with id==1
type Version struct {
- ID int64 `xorm:"pk autoincr"`
+ ID int64
Version int64
}
diff --git a/models/models.go b/models/models.go
index bf071292..c79a071b 100644
--- a/models/models.go
+++ b/models/models.go
@@ -5,7 +5,9 @@
package models
import (
+ "bufio"
"database/sql"
+ "encoding/json"
"errors"
"fmt"
"net/url"
@@ -13,6 +15,7 @@ import (
"path"
"strings"
+ "github.com/Unknwon/com"
_ "github.com/denisenkom/go-mssqldb"
_ "github.com/go-sql-driver/mysql"
"github.com/go-xorm/core"
@@ -262,7 +265,89 @@ func Ping() error {
return x.Ping()
}
-// DumpDatabase dumps all data from database to file system.
-func DumpDatabase(filePath string) error {
- return x.DumpAllToFile(filePath)
+// The version table. Should have only one row with id==1
+type Version struct {
+ ID int64
+ Version int64
+}
+
+// DumpDatabase dumps all data from database to file system in JSON format.
+func DumpDatabase(dirPath string) (err error) {
+ os.MkdirAll(dirPath, os.ModePerm)
+ // Purposely create a local variable to not modify global variable
+ tables := append(tables, new(Version))
+ for _, table := range tables {
+ tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*models.")
+ tableFile := path.Join(dirPath, tableName+".json")
+ f, err := os.Create(tableFile)
+ if err != nil {
+ return fmt.Errorf("fail to create JSON file: %v", err)
+ }
+
+ if err = x.Asc("id").Iterate(table, func(idx int, bean interface{}) (err error) {
+ enc := json.NewEncoder(f)
+ return enc.Encode(bean)
+ }); err != nil {
+ f.Close()
+ return fmt.Errorf("fail to dump table '%s': %v", tableName, err)
+ }
+ f.Close()
+ }
+ return nil
+}
+
+// ImportDatabase imports data from backup archive.
+func ImportDatabase(dirPath string) (err error) {
+ // Purposely create a local variable to not modify global variable
+ tables := append(tables, new(Version))
+ for _, table := range tables {
+ tableName := strings.TrimPrefix(fmt.Sprintf("%T", table), "*models.")
+ tableFile := path.Join(dirPath, tableName+".json")
+ if !com.IsExist(tableFile) {
+ continue
+ }
+
+ if err = x.DropTables(table); err != nil {
+ return fmt.Errorf("fail to drop table '%s': %v", tableName, err)
+ } else if err = x.Sync2(table); err != nil {
+ return fmt.Errorf("fail to sync table '%s': %v", tableName, err)
+ }
+
+ f, err := os.Open(tableFile)
+ if err != nil {
+ return fmt.Errorf("fail to open JSON file: %v", err)
+ }
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ switch bean := table.(type) {
+ case *LoginSource:
+ meta := make(map[string]interface{})
+ if err = json.Unmarshal(scanner.Bytes(), &meta); err != nil {
+ return fmt.Errorf("fail to unmarshal to map: %v", err)
+ }
+
+ tp := LoginType(com.StrTo(com.ToStr(meta["Type"])).MustInt64())
+ switch tp {
+ case LOGIN_LDAP, LOGIN_DLDAP:
+ bean.Cfg = new(LDAPConfig)
+ case LOGIN_SMTP:
+ bean.Cfg = new(SMTPConfig)
+ case LOGIN_PAM:
+ bean.Cfg = new(PAMConfig)
+ default:
+ return fmt.Errorf("unrecognized login source type:: %v", tp)
+ }
+ table = bean
+ }
+
+ if err = json.Unmarshal(scanner.Bytes(), table); err != nil {
+ return fmt.Errorf("fail to unmarshal to struct: %v", err)
+ }
+
+ if _, err = x.Insert(table); err != nil {
+ return fmt.Errorf("fail to insert strcut: %v", err)
+ }
+ }
+ }
+ return nil
}
diff --git a/models/org.go b/models/org.go
index 0046aaa4..1deba7f4 100644
--- a/models/org.go
+++ b/models/org.go
@@ -20,8 +20,8 @@ var (
)
// IsOwnedBy returns true if given user is in the owner team.
-func (org *User) IsOwnedBy(uid int64) bool {
- return IsOrganizationOwner(org.ID, uid)
+func (org *User) IsOwnedBy(userID int64) bool {
+ return IsOrganizationOwner(org.ID, userID)
}
// IsOrgMember returns true if given user is member of organization.
@@ -246,8 +246,8 @@ type OrgUser struct {
}
// IsOrganizationOwner returns true if given user is in the owner team.
-func IsOrganizationOwner(orgId, uid int64) bool {
- has, _ := x.Where("is_owner=?", true).And("uid=?", uid).And("org_id=?", orgId).Get(new(OrgUser))
+func IsOrganizationOwner(orgID, userID int64) bool {
+ has, _ := x.Where("is_owner = ?", true).And("uid = ?", userID).And("org_id = ?", orgID).Get(new(OrgUser))
return has
}
diff --git a/models/repo.go b/models/repo.go
index 76cd8233..7fee5154 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -628,8 +628,8 @@ func wikiRemoteURL(remote string) string {
}
// MigrateRepository migrates a existing repository from other project hosting.
-func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
- repo, err := CreateRepository(u, CreateRepoOptions{
+func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository, error) {
+ repo, err := CreateRepository(doer, owner, CreateRepoOptions{
Name: opts.Name,
Description: opts.Description,
IsPrivate: opts.IsPrivate,
@@ -639,11 +639,11 @@ func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
return nil, err
}
- repoPath := RepoPath(u.Name, opts.Name)
- wikiPath := WikiPath(u.Name, opts.Name)
+ repoPath := RepoPath(owner.Name, opts.Name)
+ wikiPath := WikiPath(owner.Name, opts.Name)
- if u.IsOrganization() {
- t, err := u.GetOwnerTeam()
+ if owner.IsOrganization() {
+ t, err := owner.GetOwnerTeam()
if err != nil {
return nil, err
}
@@ -882,8 +882,8 @@ func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRep
return nil
}
-// InitRepository initializes README and .gitignore if needed.
-func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) {
+// 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) {
// Somehow the directory could exist.
if com.IsExist(repoPath) {
return fmt.Errorf("initRepository: path already exists: %s", repoPath)
@@ -908,7 +908,7 @@ func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts C
}
// Apply changes and commit.
- if err = initRepoCommit(tmpDir, u.NewGitSig()); err != nil {
+ if err = initRepoCommit(tmpDir, doer.NewGitSig()); err != nil {
return fmt.Errorf("initRepoCommit: %v", err)
}
}
@@ -941,32 +941,32 @@ func IsUsableRepoName(name string) error {
return isUsableName(reservedRepoNames, reservedRepoPatterns, name)
}
-func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
+func createRepository(e *xorm.Session, doer, owner *User, repo *Repository) (err error) {
if err = IsUsableRepoName(repo.Name); err != nil {
return err
}
- has, err := isRepositoryExist(e, u, repo.Name)
+ has, err := isRepositoryExist(e, owner, repo.Name)
if err != nil {
return fmt.Errorf("IsRepositoryExist: %v", err)
} else if has {
- return ErrRepoAlreadyExist{u.Name, repo.Name}
+ return ErrRepoAlreadyExist{owner.Name, repo.Name}
}
if _, err = e.Insert(repo); err != nil {
return err
}
- u.NumRepos++
+ owner.NumRepos++
// Remember visibility preference.
- u.LastRepoVisibility = repo.IsPrivate
- if err = updateUser(e, u); err != nil {
+ owner.LastRepoVisibility = repo.IsPrivate
+ if err = updateUser(e, owner); err != nil {
return fmt.Errorf("updateUser: %v", err)
}
// Give access to all members in owner team.
- if u.IsOrganization() {
- t, err := u.getOwnerTeam(e)
+ if owner.IsOrganization() {
+ t, err := owner.getOwnerTeam(e)
if err != nil {
return fmt.Errorf("getOwnerTeam: %v", err)
} else if err = t.addRepository(e, repo); err != nil {
@@ -979,9 +979,9 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
}
}
- if err = watchRepo(e, u.ID, repo.ID, true); err != nil {
+ if err = watchRepo(e, owner.ID, repo.ID, true); err != nil {
return fmt.Errorf("watchRepo: %v", err)
- } else if err = newRepoAction(e, u, repo); err != nil {
+ } else if err = newRepoAction(e, doer, owner, repo); err != nil {
return fmt.Errorf("newRepoAction: %v", err)
}
@@ -989,14 +989,14 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
}
// CreateRepository creates a repository for given user or organization.
-func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error) {
- if !u.CanCreateRepo() {
- return nil, ErrReachLimitOfRepo{u.MaxRepoCreation}
+func CreateRepository(doer, owner *User, opts CreateRepoOptions) (_ *Repository, err error) {
+ if !owner.CanCreateRepo() {
+ return nil, ErrReachLimitOfRepo{owner.MaxRepoCreation}
}
repo := &Repository{
- OwnerID: u.ID,
- Owner: u,
+ OwnerID: owner.ID,
+ Owner: owner,
Name: opts.Name,
LowerName: strings.ToLower(opts.Name),
Description: opts.Description,
@@ -1012,14 +1012,14 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error
return nil, err
}
- if err = createRepository(sess, u, repo); err != nil {
+ if err = createRepository(sess, doer, owner, repo); err != nil {
return nil, err
}
// No need for init mirror.
if !opts.IsMirror {
- repoPath := RepoPath(u.Name, repo.Name)
- if err = initRepository(sess, repoPath, u, repo, opts); err != nil {
+ repoPath := RepoPath(owner.Name, repo.Name)
+ if err = initRepository(sess, repoPath, doer, repo, opts); err != nil {
RemoveAllWithNotice("Delete repository for initialization failure", repoPath)
return nil, fmt.Errorf("initRepository: %v", err)
}
@@ -2068,25 +2068,28 @@ func (repo *Repository) GetWatchers(page int) ([]*User, error) {
func notifyWatchers(e Engine, act *Action) error {
// Add feeds for user self and all watchers.
- watches, err := getWatchers(e, act.RepoID)
+ watchers, err := getWatchers(e, act.RepoID)
if err != nil {
- return fmt.Errorf("get watchers: %v", err)
+ return fmt.Errorf("getWatchers: %v", err)
}
+ // Reset ID to reuse Action object
+ act.ID = 0
+
// Add feed for actioner.
act.UserID = act.ActUserID
- if _, err = e.InsertOne(act); err != nil {
- return fmt.Errorf("insert new actioner: %v", err)
+ if _, err = e.Insert(act); err != nil {
+ return fmt.Errorf("insert new action: %v", err)
}
- for i := range watches {
- if act.ActUserID == watches[i].UserID {
+ for i := range watchers {
+ if act.ActUserID == watchers[i].UserID {
continue
}
act.ID = 0
- act.UserID = watches[i].UserID
- if _, err = e.InsertOne(act); err != nil {
+ act.UserID = watchers[i].UserID
+ if _, err = e.Insert(act); err != nil {
return fmt.Errorf("insert new action: %v", err)
}
}
@@ -2161,24 +2164,26 @@ func (repo *Repository) GetStargazers(page int) ([]*User, error) {
// \___ / \____/|__| |__|_ \
// \/ \/
-// HasForkedRepo checks if given user has already forked a repository with given ID.
+// HasForkedRepo checks if given user has already forked a repository.
+// When user has already forked, it returns true along with the repository.
func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
repo := new(Repository)
- has, _ := x.Where("owner_id=? AND fork_id=?", ownerID, repoID).Get(repo)
+ has, _ := x.Where("owner_id = ? AND fork_id = ?", ownerID, repoID).Get(repo)
return repo, has
}
-func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
+// ForkRepository creates a fork of target repository under another user domain.
+func ForkRepository(doer, owner *User, baseRepo *Repository, name, desc string) (_ *Repository, err error) {
repo := &Repository{
- OwnerID: u.ID,
- Owner: u,
+ OwnerID: owner.ID,
+ Owner: owner,
Name: name,
LowerName: strings.ToLower(name),
Description: desc,
- DefaultBranch: oldRepo.DefaultBranch,
- IsPrivate: oldRepo.IsPrivate,
+ DefaultBranch: baseRepo.DefaultBranch,
+ IsPrivate: baseRepo.IsPrivate,
IsFork: true,
- ForkID: oldRepo.ID,
+ ForkID: baseRepo.ID,
}
sess := x.NewSession()
@@ -2187,18 +2192,16 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
return nil, err
}
- if err = createRepository(sess, u, repo); err != nil {
+ if err = createRepository(sess, doer, owner, repo); err != nil {
return nil, err
- }
-
- if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", oldRepo.ID); err != nil {
+ } else if _, err = sess.Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", baseRepo.ID); err != nil {
return nil, err
}
- repoPath := RepoPath(u.Name, repo.Name)
+ repoPath := repo.repoPath(sess)
_, stderr, err := process.ExecTimeout(10*time.Minute,
- fmt.Sprintf("ForkRepository 'git clone': %s/%s", u.Name, repo.Name),
- "git", "clone", "--bare", oldRepo.RepoPath(), repoPath)
+ fmt.Sprintf("ForkRepository 'git clone': %s/%s", owner.Name, repo.Name),
+ "git", "clone", "--bare", baseRepo.RepoPath(), repoPath)
if err != nil {
return nil, fmt.Errorf("git clone: %v", stderr)
}
@@ -2212,6 +2215,12 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
if err = createDelegateHooks(repoPath); err != nil {
return nil, fmt.Errorf("createDelegateHooks: %v", err)
+ } else if err = prepareWebhooks(sess, baseRepo, HOOK_EVENT_FORK, &api.ForkPayload{
+ Forkee: repo.APIFormat(nil),
+ Repo: baseRepo.APIFormat(nil),
+ Sender: doer.APIFormat(),
+ }); err != nil {
+ return nil, fmt.Errorf("prepareWebhooks: %v", err)
}
return repo, sess.Commit()
diff --git a/models/update.go b/models/update.go
index 507150be..77336ac9 100644
--- a/models/update.go
+++ b/models/update.go
@@ -10,8 +10,6 @@ import (
"os/exec"
"strings"
- log "gopkg.in/clog.v1"
-
git "github.com/gogits/git-module"
)
@@ -29,6 +27,10 @@ func CommitToPushCommit(commit *git.Commit) *PushCommit {
}
func ListToPushCommits(l *list.List) *PushCommits {
+ if l == nil {
+ return &PushCommits{}
+ }
+
commits := make([]*PushCommit, 0)
var actEmail string
for e := l.Front(); e != nil; e = e.Next() {
@@ -68,12 +70,6 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
return fmt.Errorf("Fail to call 'git update-server-info': %v", err)
}
- if isDelRef {
- log.Trace("Reference '%s' has been deleted from '%s/%s' by %s",
- opts.RefFullName, opts.RepoUserName, opts.RepoName, opts.PusherName)
- return nil
- }
-
gitRepo, err := git.OpenRepository(repoPath)
if err != nil {
return fmt.Errorf("OpenRepository: %v", err)
@@ -100,27 +96,30 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
NewCommitID: opts.NewCommitID,
Commits: &PushCommits{},
}); err != nil {
- return fmt.Errorf("CommitRepoAction (tag): %v", err)
+ return fmt.Errorf("CommitRepoAction.(tag): %v", err)
}
return nil
}
- newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
- if err != nil {
- return fmt.Errorf("gitRepo.GetCommit: %v", err)
- }
-
- // Push new branch.
var l *list.List
- if isNewRef {
- l, err = newCommit.CommitsBeforeLimit(10)
+ // Skip read parent commits when delete branch
+ if !isDelRef {
+ // Push new branch.
+ newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
if err != nil {
- return fmt.Errorf("newCommit.CommitsBeforeLimit: %v", err)
+ return fmt.Errorf("GetCommit [commit_id: %s]: %v", opts.NewCommitID, err)
}
- } else {
- l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
- if err != nil {
- return fmt.Errorf("newCommit.CommitsBeforeUntil: %v", err)
+
+ if isNewRef {
+ l, err = newCommit.CommitsBeforeLimit(10)
+ if err != nil {
+ return fmt.Errorf("CommitsBeforeLimit [commit_id: %s]: %v", newCommit.ID, err)
+ }
+ } else {
+ l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
+ if err != nil {
+ return fmt.Errorf("CommitsBeforeUntil [commit_id: %s]: %v", opts.OldCommitID, err)
+ }
}
}
@@ -133,7 +132,7 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
NewCommitID: opts.NewCommitID,
Commits: ListToPushCommits(l),
}); err != nil {
- return fmt.Errorf("CommitRepoAction (branch): %v", err)
+ return fmt.Errorf("CommitRepoAction.(branch): %v", err)
}
return nil
}
diff --git a/models/webhook.go b/models/webhook.go
index feabf03b..0f475df5 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -63,6 +63,8 @@ func IsValidHookContentType(name string) bool {
type HookEvents struct {
Create bool `json:"create"`
+ Delete bool `json:"delete"`
+ Fork bool `json:"fork"`
Push bool `json:"push"`
PullRequest bool `json:"pull_request"`
}
@@ -156,6 +158,18 @@ func (w *Webhook) HasCreateEvent() bool {
(w.ChooseEvents && w.HookEvents.Create)
}
+// HasDeleteEvent returns true if hook enabled delete event.
+func (w *Webhook) HasDeleteEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Delete)
+}
+
+// HasForkEvent returns true if hook enabled fork event.
+func (w *Webhook) HasForkEvent() bool {
+ return w.SendEverything ||
+ (w.ChooseEvents && w.HookEvents.Fork)
+}
+
// HasPushEvent returns true if hook enabled push event.
func (w *Webhook) HasPushEvent() bool {
return w.PushOnly || w.SendEverything ||
@@ -169,15 +183,21 @@ func (w *Webhook) HasPullRequestEvent() bool {
}
func (w *Webhook) EventsArray() []string {
- events := make([]string, 0, 3)
+ events := make([]string, 0, 5)
if w.HasCreateEvent() {
- events = append(events, "create")
+ events = append(events, string(HOOK_EVENT_CREATE))
+ }
+ if w.HasDeleteEvent() {
+ events = append(events, string(HOOK_EVENT_DELETE))
+ }
+ if w.HasForkEvent() {
+ events = append(events, string(HOOK_EVENT_FORK))
}
if w.HasPushEvent() {
- events = append(events, "push")
+ events = append(events, string(HOOK_EVENT_PUSH))
}
if w.HasPullRequestEvent() {
- events = append(events, "pull_request")
+ events = append(events, string(HOOK_EVENT_PULL_REQUEST))
}
return events
}
@@ -225,10 +245,10 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {
})
}
-// GetActiveWebhooksByRepoID returns all active webhooks of repository.
-func GetActiveWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
+// getActiveWebhooksByRepoID returns all active webhooks of repository.
+func getActiveWebhooksByRepoID(e Engine, repoID int64) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)
- return webhooks, x.Where("repo_id = ?", repoID).And("is_active = ?", true).Find(&webhooks)
+ return webhooks, e.Where("repo_id = ?", repoID).And("is_active = ?", true).Find(&webhooks)
}
// GetWebhooksByRepoID returns all webhooks of a repository.
@@ -283,10 +303,10 @@ func GetWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
return ws, err
}
-// GetActiveWebhooksByOrgID returns all active webhooks for an organization.
-func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
- err = x.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws)
- return ws, err
+// getActiveWebhooksByOrgID returns all active webhooks for an organization.
+func getActiveWebhooksByOrgID(e Engine, orgID int64) ([]*Webhook, error) {
+ ws := make([]*Webhook, 3)
+ return ws, e.Where("org_id=?", orgID).And("is_active=?", true).Find(&ws)
}
// ___ ___ __ ___________ __
@@ -337,6 +357,8 @@ type HookEventType string
const (
HOOK_EVENT_CREATE HookEventType = "create"
+ HOOK_EVENT_DELETE HookEventType = "delete"
+ HOOK_EVENT_FORK HookEventType = "fork"
HOOK_EVENT_PUSH HookEventType = "push"
HOOK_EVENT_PULL_REQUEST HookEventType = "pull_request"
)
@@ -430,16 +452,16 @@ func HookTasks(hookID int64, page int) ([]*HookTask, error) {
return tasks, x.Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).Where("hook_id=?", hookID).Desc("id").Find(&tasks)
}
-// CreateHookTask creates a new hook task,
+// createHookTask creates a new hook task,
// it handles conversion from Payload to PayloadContent.
-func CreateHookTask(t *HookTask) error {
+func createHookTask(e Engine, t *HookTask) error {
data, err := t.Payloader.JSONPayload()
if err != nil {
return err
}
t.UUID = gouuid.NewV4().String()
t.PayloadContent = string(data)
- _, err = x.Insert(t)
+ _, err = e.Insert(t)
return err
}
@@ -449,8 +471,8 @@ func UpdateHookTask(t *HookTask) error {
return err
}
-// prepareWebhooks adds list of webhooks to task queue.
-func prepareWebhooks(repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) {
+// prepareHookTasks adds list of webhooks to task queue.
+func prepareHookTasks(e Engine, repo *Repository, event HookEventType, p api.Payloader, webhooks []*Webhook) (err error) {
if len(webhooks) == 0 {
return nil
}
@@ -462,6 +484,10 @@ func prepareWebhooks(repo *Repository, event HookEventType, p api.Payloader, web
if !w.HasCreateEvent() {
continue
}
+ case HOOK_EVENT_DELETE:
+ if !w.HasDeleteEvent() {
+ continue
+ }
case HOOK_EVENT_PUSH:
if !w.HasPushEvent() {
continue
@@ -499,7 +525,7 @@ func prepareWebhooks(repo *Repository, event HookEventType, p api.Payloader, web
signature = hex.EncodeToString(sig.Sum(nil))
}
- if err = CreateHookTask(&HookTask{
+ if err = createHookTask(e, &HookTask{
RepoID: repo.ID,
HookID: w.ID,
Type: w.HookTaskType,
@@ -510,29 +536,37 @@ func prepareWebhooks(repo *Repository, event HookEventType, p api.Payloader, web
EventType: event,
IsSSL: w.IsSSL,
}); err != nil {
- return fmt.Errorf("CreateHookTask: %v", err)
+ return fmt.Errorf("createHookTask: %v", err)
}
}
+
+ // It's safe to fail when the whole function is called during hook execution
+ // because resource released after exit.
+ go HookQueue.Add(repo.ID)
return nil
}
-// PrepareWebhooks adds all active webhooks to task queue.
-func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
- webhooks, err := GetActiveWebhooksByRepoID(repo.ID)
+func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payloader) error {
+ webhooks, err := getActiveWebhooksByRepoID(e, repo.ID)
if err != nil {
- return fmt.Errorf("GetActiveWebhooksByRepoID [%d]: %v", repo.ID, err)
+ return fmt.Errorf("getActiveWebhooksByRepoID [%d]: %v", repo.ID, err)
}
// check if repo belongs to org and append additional webhooks
- if repo.MustOwner().IsOrganization() {
+ if repo.mustOwner(e).IsOrganization() {
// get hooks for org
- orgws, err := GetActiveWebhooksByOrgID(repo.OwnerID)
+ orgws, err := getActiveWebhooksByOrgID(e, repo.OwnerID)
if err != nil {
- return fmt.Errorf("GetActiveWebhooksByOrgID [%d]: %v", repo.OwnerID, err)
+ return fmt.Errorf("getActiveWebhooksByOrgID [%d]: %v", repo.OwnerID, err)
}
webhooks = append(webhooks, orgws...)
}
- return prepareWebhooks(repo, event, p, webhooks)
+ return prepareHookTasks(e, repo, event, p, webhooks)
+}
+
+// PrepareWebhooks adds all active webhooks to task queue.
+func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
+ return prepareWebhooks(x, repo, event, p)
}
// TestWebhook adds the test webhook matches the ID to task queue.
@@ -541,7 +575,7 @@ func TestWebhook(repo *Repository, event HookEventType, p api.Payloader, webhook
if err != nil {
return fmt.Errorf("GetWebhookOfRepoByID [repo_id: %d, id: %d]: %v", repo.ID, webhookID, err)
}
- return prepareWebhooks(repo, event, p, []*Webhook{webhook})
+ return prepareHookTasks(x, repo, event, p, []*Webhook{webhook})
}
func (t *HookTask) deliver() {
diff --git a/models/webhook_discord.go b/models/webhook_discord.go
index 27b01bc3..d26beaaf 100644
--- a/models/webhook_discord.go
+++ b/models/webhook_discord.go
@@ -68,22 +68,50 @@ func DiscordSHALinkFormatter(url string, text string) string {
return fmt.Sprintf("[`%s`](%s)", text, url)
}
-func getDiscordCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*DiscordPayload, error) {
- // Created tag/branch
+// getDiscordCreatePayload composes Discord payload for create new branch or tag.
+func getDiscordCreatePayload(p *api.CreatePayload) (*DiscordPayload, error) {
refName := git.RefEndName(p.Ref)
-
repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
refLink := DiscordLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
content := fmt.Sprintf("Created new %s: %s/%s", p.RefType, repoLink, refLink)
+ return &DiscordPayload{
+ Embeds: []*DiscordEmbedObject{{
+ Description: content,
+ URL: setting.AppUrl + p.Sender.UserName,
+ Author: &DiscordEmbedAuthorObject{
+ Name: p.Sender.UserName,
+ IconURL: p.Sender.AvatarUrl,
+ },
+ }},
+ }, nil
+}
- color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32)
+// getDiscordDeletePayload composes Discord payload for delete a branch or tag.
+func getDiscordDeletePayload(p *api.DeletePayload) (*DiscordPayload, error) {
+ refName := git.RefEndName(p.Ref)
+ repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
+ content := fmt.Sprintf("Deleted %s: %s/%s", p.RefType, repoLink, refName)
+ return &DiscordPayload{
+ Embeds: []*DiscordEmbedObject{{
+ Description: content,
+ URL: setting.AppUrl + p.Sender.UserName,
+ Author: &DiscordEmbedAuthorObject{
+ Name: p.Sender.UserName,
+ IconURL: p.Sender.AvatarUrl,
+ },
+ }},
+ }, nil
+}
+
+// getDiscordForkPayload composes Discord payload for forked by a repository.
+func getDiscordForkPayload(p *api.ForkPayload) (*DiscordPayload, error) {
+ baseLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
+ forkLink := DiscordLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
+ content := fmt.Sprintf("%s is forked to %s", baseLink, forkLink)
return &DiscordPayload{
- Username: slack.Username,
- AvatarURL: slack.IconURL,
Embeds: []*DiscordEmbedObject{{
Description: content,
URL: setting.AppUrl + p.Sender.UserName,
- Color: int(color),
Author: &DiscordEmbedAuthorObject{
Name: p.Sender.UserName,
IconURL: p.Sender.AvatarUrl,
@@ -206,22 +234,34 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (
}, nil
}
-func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) {
- d := new(DiscordPayload)
-
+func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (payload *DiscordPayload, err error) {
slack := &SlackMeta{}
if err := json.Unmarshal([]byte(meta), &slack); err != nil {
- return d, fmt.Errorf("GetDiscordPayload meta json: %v", err)
+ return nil, fmt.Errorf("json.Unmarshal: %v", err)
}
switch event {
case HOOK_EVENT_CREATE:
- return getDiscordCreatePayload(p.(*api.CreatePayload), slack)
+ payload, err = getDiscordCreatePayload(p.(*api.CreatePayload))
+ case HOOK_EVENT_DELETE:
+ payload, err = getDiscordDeletePayload(p.(*api.DeletePayload))
+ case HOOK_EVENT_FORK:
+ payload, err = getDiscordForkPayload(p.(*api.ForkPayload))
case HOOK_EVENT_PUSH:
- return getDiscordPushPayload(p.(*api.PushPayload), slack)
+ payload, err = getDiscordPushPayload(p.(*api.PushPayload), slack)
case HOOK_EVENT_PULL_REQUEST:
- return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), slack)
+ payload, err = getDiscordPullRequestPayload(p.(*api.PullRequestPayload), slack)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("event '%s': %v", event, err)
+ }
+
+ payload.Username = slack.Username
+ payload.AvatarURL = slack.IconURL
+ if len(payload.Embeds) > 0 {
+ color, _ := strconv.ParseInt(strings.TrimLeft(slack.Color, "#"), 16, 32)
+ payload.Embeds[0].Color = int(color)
}
- return d, nil
+ return payload, nil
}
diff --git a/models/webhook_slack.go b/models/webhook_slack.go
index 5943a9e5..6f81c1c0 100644
--- a/models/webhook_slack.go
+++ b/models/webhook_slack.go
@@ -69,19 +69,34 @@ func SlackLinkFormatter(url string, text string) string {
return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text))
}
-func getSlackCreatePayload(p *api.CreatePayload, slack *SlackMeta) (*SlackPayload, error) {
- // Created tag/branch
+// getSlackCreatePayload composes Slack payload for create new branch or tag.
+func getSlackCreatePayload(p *api.CreatePayload) (*SlackPayload, error) {
refName := git.RefEndName(p.Ref)
-
repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
+ return &SlackPayload{
+ Text: text,
+ }, nil
+}
+// getSlackDeletePayload composes Slack payload for delete a branch or tag.
+func getSlackDeletePayload(p *api.DeletePayload) (*SlackPayload, error) {
+ refName := git.RefEndName(p.Ref)
+ repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
+ text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName)
return &SlackPayload{
- Channel: slack.Channel,
- Text: text,
- Username: slack.Username,
- IconURL: slack.IconURL,
+ Text: text,
+ }, nil
+}
+
+// getSlackForkPayload composes Slack payload for forked by a repository.
+func getSlackForkPayload(p *api.ForkPayload) (*SlackPayload, error) {
+ baseLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
+ forkLink := SlackLinkFormatter(p.Forkee.HTMLURL, p.Forkee.FullName)
+ text := fmt.Sprintf("%s is forked to %s", baseLink, forkLink)
+ return &SlackPayload{
+ Text: text,
}, nil
}
@@ -178,22 +193,34 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
}, nil
}
-func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackPayload, error) {
- s := new(SlackPayload)
-
+func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (payload *SlackPayload, err error) {
slack := &SlackMeta{}
if err := json.Unmarshal([]byte(meta), &slack); err != nil {
- return s, fmt.Errorf("GetSlackPayload meta json: %v", err)
+ return nil, fmt.Errorf("json.Unmarshal: %v", err)
}
switch event {
case HOOK_EVENT_CREATE:
- return getSlackCreatePayload(p.(*api.CreatePayload), slack)
+ payload, err = getSlackCreatePayload(p.(*api.CreatePayload))
+ case HOOK_EVENT_DELETE:
+ payload, err = getSlackDeletePayload(p.(*api.DeletePayload))
+ case HOOK_EVENT_FORK:
+ payload, err = getSlackForkPayload(p.(*api.ForkPayload))
case HOOK_EVENT_PUSH:
- return getSlackPushPayload(p.(*api.PushPayload), slack)
+ payload, err = getSlackPushPayload(p.(*api.PushPayload), slack)
case HOOK_EVENT_PULL_REQUEST:
- return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
+ payload, err = getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("event '%s': %v", event, err)
+ }
+
+ payload.Channel = slack.Channel
+ payload.Username = slack.Username
+ payload.IconURL = slack.IconURL
+ if len(payload.Attachments) > 0 {
+ payload.Attachments[0].Color = slack.Color
}
- return s, nil
+ return payload, nil
}