diff options
Diffstat (limited to 'models')
-rw-r--r-- | models/action.go | 141 | ||||
-rw-r--r-- | models/migrations/migrations.go | 2 | ||||
-rw-r--r-- | models/models.go | 91 | ||||
-rw-r--r-- | models/org.go | 8 | ||||
-rw-r--r-- | models/repo.go | 109 | ||||
-rw-r--r-- | models/update.go | 45 | ||||
-rw-r--r-- | models/webhook.go | 88 | ||||
-rw-r--r-- | models/webhook_discord.go | 70 | ||||
-rw-r--r-- | models/webhook_slack.go | 57 |
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 } |