diff options
author | Michael Boke <michael@mbict.nl> | 2014-10-03 22:51:07 +0200 |
---|---|---|
committer | Michael Boke <michael@mbict.nl> | 2014-10-03 22:51:07 +0200 |
commit | ba1270df2d3d835b397317f133963e7b517242f1 (patch) | |
tree | 1265a142a1fd9951d30ae11648e7fbfb5806e594 /models | |
parent | ba0feadc34400cb91ff23f66096884d862651cdd (diff) | |
parent | 405ee14711ab946bd709ec28a526890c40cbc03b (diff) |
Merge remote-tracking branch 'upstream/master'
Conflicts:
conf/app.ini
Diffstat (limited to 'models')
-rw-r--r-- | models/action.go | 117 | ||||
-rw-r--r-- | models/git_diff.go | 56 | ||||
-rw-r--r-- | models/issue.go | 2 | ||||
-rw-r--r-- | models/login.go | 8 | ||||
-rw-r--r-- | models/models.go | 49 | ||||
-rw-r--r-- | models/org.go | 32 | ||||
-rw-r--r-- | models/publickey.go | 25 | ||||
-rw-r--r-- | models/repo.go | 169 | ||||
-rw-r--r-- | models/slack.go | 125 | ||||
-rw-r--r-- | models/update.go | 11 | ||||
-rw-r--r-- | models/user.go | 82 | ||||
-rw-r--r-- | models/webhook.go | 140 |
12 files changed, 590 insertions, 226 deletions
diff --git a/models/action.go b/models/action.go index b5f692c4..4203ead3 100644 --- a/models/action.go +++ b/models/action.go @@ -137,7 +137,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com return err } - url := fmt.Sprintf("/%s/%s/commit/%s", repoUserName, repoName, c.Sha1) + url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1) message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message) if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMIT, message, nil); err != nil { @@ -172,7 +172,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com // CommitRepoAction adds new action for committing repository. func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, - repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits) error { + repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits, oldCommitId string, newCommitId string) error { opType := COMMIT_REPO // Check it's tag push or branch. @@ -220,21 +220,52 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, ws, err := GetActiveWebhooksByRepoId(repoId) if err != nil { - return errors.New("action.CommitRepoAction(GetWebhooksByRepoId): " + err.Error()) - } else if len(ws) == 0 { + return errors.New("action.CommitRepoAction(GetActiveWebhooksByRepoId): " + err.Error()) + } + + // check if repo belongs to org and append additional webhooks + if repo.Owner.IsOrganization() { + // get hooks for org + orgws, err := GetActiveWebhooksByOrgId(repo.OwnerId) + if err != nil { + return errors.New("action.CommitRepoAction(GetActiveWebhooksByOrgId): " + err.Error()) + } + ws = append(ws, orgws...) + } + + if len(ws) == 0 { return nil } repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName) + compareUrl := "" + // if not the first commit, set the compareUrl + if !strings.HasPrefix(oldCommitId, "0000000") { + compareUrl = fmt.Sprintf("%s/compare/%s...%s", repoLink, oldCommitId, newCommitId) + } + + pusher_email, pusher_name := "", "" + pusher, err := GetUserByName(userName) + if err == nil { + pusher_email = pusher.Email + pusher_name = pusher.GetFullNameFallback() + } + commits := make([]*PayloadCommit, len(commit.Commits)) for i, cmt := range commit.Commits { + author_username := "" + author, err := GetUserByEmail(cmt.AuthorEmail) + if err == nil { + author_username = author.Name + } commits[i] = &PayloadCommit{ Id: cmt.Sha1, Message: cmt.Message, Url: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1), Author: &PayloadAuthor{ - Name: cmt.AuthorName, - Email: cmt.AuthorEmail, + Name: cmt.AuthorName, + Email: cmt.AuthorEmail, + UserName: author_username, }, } } @@ -249,15 +280,20 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, Website: repo.Website, Watchers: repo.NumWatches, Owner: &PayloadAuthor{ - Name: repoUserName, - Email: actEmail, + Name: repo.Owner.GetFullNameFallback(), + Email: repo.Owner.Email, + UserName: repo.Owner.Name, }, Private: repo.IsPrivate, }, Pusher: &PayloadAuthor{ - Name: repo.Owner.LowerName, - Email: repo.Owner.Email, + Name: pusher_name, + Email: pusher_email, + UserName: userName, }, + Before: oldCommitId, + After: newCommitId, + CompareUrl: compareUrl, } for _, w := range ws { @@ -266,15 +302,36 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string, continue } - p.Secret = w.Secret - CreateHookTask(&HookTask{ - Type: WEBHOOK, - Url: w.Url, - Payload: p, - ContentType: w.ContentType, - IsSsl: w.IsSsl, - }) + switch w.HookTaskType { + case SLACK: + { + s, err := GetSlackPayload(p, w.Meta) + if err != nil { + return errors.New("action.GetSlackPayload: " + err.Error()) + } + CreateHookTask(&HookTask{ + Type: w.HookTaskType, + Url: w.Url, + BasePayload: s, + ContentType: w.ContentType, + IsSsl: w.IsSsl, + }) + } + default: + { + p.Secret = w.Secret + CreateHookTask(&HookTask{ + Type: w.HookTaskType, + Url: w.Url, + BasePayload: p, + ContentType: w.ContentType, + IsSsl: w.IsSsl, + }) + } + } } + + go DeliverHooks() return nil } @@ -293,13 +350,29 @@ func NewRepoAction(u *User, repo *Repository) (err error) { // TransferRepoAction adds new action for transfering repository. func TransferRepoAction(u, newUser *User, repo *Repository) (err error) { - if err = NotifyWatchers(&Action{ActUserId: u.Id, ActUserName: u.Name, ActEmail: u.Email, - OpType: TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name, - IsPrivate: repo.IsPrivate}); err != nil { + action := &Action{ + ActUserId: u.Id, + ActUserName: u.Name, + ActEmail: u.Email, + OpType: TRANSFER_REPO, + RepoId: repo.Id, + RepoUserName: newUser.Name, + RepoName: repo.Name, + IsPrivate: repo.IsPrivate, + Content: path.Join(repo.Owner.LowerName, repo.LowerName), + } + if err = NotifyWatchers(action); err != nil { log.Error(4, "NotifyWatchers: %d/%s", u.Id, repo.Name) return err } + // Remove watch for organization. + if repo.Owner.IsOrganization() { + if err = WatchRepo(repo.Owner.Id, repo.Id, false); err != nil { + log.Error(4, "WatchRepo", err) + } + } + log.Trace("action.TransferRepoAction: %s/%s", u.Name, repo.Name) return err } @@ -309,7 +382,7 @@ func GetFeeds(uid, offset int64, isProfile bool) ([]*Action, error) { actions := make([]*Action, 0, 20) sess := x.Limit(20, int(offset)).Desc("id").Where("user_id=?", uid) if isProfile { - sess.Where("is_private=?", false).And("act_user_id=?", uid) + sess.And("is_private=?", false).And("act_user_id=?", uid) } err := sess.Find(&actions) return actions, err diff --git a/models/git_diff.go b/models/git_diff.go index 4b4d1234..e093e7ab 100644 --- a/models/git_diff.go +++ b/models/git_diff.go @@ -15,8 +15,7 @@ import ( "github.com/Unknwon/com" - "github.com/gogits/git" - + "github.com/gogits/gogs/modules/git" "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/process" ) @@ -71,7 +70,7 @@ func (diff *Diff) NumFiles() int { const DIFF_HEAD = "diff --git " -func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) { +func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff, error) { scanner := bufio.NewScanner(reader) var ( curFile *DiffFile @@ -80,6 +79,7 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) { } leftLine, rightLine int + isTooLong bool ) diff := &Diff{Files: make([]*DiffFile, 0)} @@ -91,16 +91,17 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) { continue } + if line == "" { + continue + } + i = i + 1 - // Diff data too large. - if i == 5000 { + // Diff data too large, we only show the first about maxlines lines + if i == maxlines { + isTooLong = true log.Warn("Diff data too large") - return &Diff{}, nil - } - - if line == "" { - continue + //return &Diff{}, nil } switch { @@ -111,6 +112,10 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) { curSection.Lines = append(curSection.Lines, diffLine) continue case line[0] == '@': + if isTooLong { + return diff, nil + } + curSection = &DiffSection{} curFile.Sections = append(curFile.Sections, curSection) ss := strings.Split(line, "@@") @@ -144,6 +149,10 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) { // Get new file. if strings.HasPrefix(line, DIFF_HEAD) { + if isTooLong { + return diff, nil + } + fs := strings.Split(line[len(DIFF_HEAD):], " ") a := fs[0] @@ -175,25 +184,30 @@ func ParsePatch(pid int64, cmd *exec.Cmd, reader io.Reader) (*Diff, error) { return diff, nil } -func GetDiff(repoPath, commitid string) (*Diff, error) { +func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxlines int) (*Diff, error) { repo, err := git.OpenRepository(repoPath) if err != nil { return nil, err } - commit, err := repo.GetCommit(commitid) + commit, err := repo.GetCommit(afterCommitId) if err != nil { return nil, err } rd, wr := io.Pipe() var cmd *exec.Cmd - // First commit of repository. - if commit.ParentCount() == 0 { - cmd = exec.Command("git", "show", commitid) + // if "after" commit given + if beforeCommitId == "" { + // First commit of repository. + if commit.ParentCount() == 0 { + cmd = exec.Command("git", "show", afterCommitId) + } else { + c, _ := commit.Parent(0) + cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId) + } } else { - c, _ := commit.Parent(0) - cmd = exec.Command("git", "diff", c.Id.String(), commitid) + cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId) } cmd.Dir = repoPath cmd.Stdout = wr @@ -208,7 +222,7 @@ func GetDiff(repoPath, commitid string) (*Diff, error) { }() defer rd.Close() - desc := fmt.Sprintf("GetDiff(%s)", repoPath) + desc := fmt.Sprintf("GetDiffRange(%s)", repoPath) pid := process.Add(desc, cmd) go func() { // In case process became zombie. @@ -224,5 +238,9 @@ func GetDiff(repoPath, commitid string) (*Diff, error) { } }() - return ParsePatch(pid, cmd, rd) + return ParsePatch(pid, maxlines, cmd, rd) +} + +func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) { + return GetDiffRange(repoPath, "", commitId, maxlines) } diff --git a/models/issue.go b/models/issue.go index 307ace81..f16c2e25 100644 --- a/models/issue.go +++ b/models/issue.go @@ -612,7 +612,7 @@ type Milestone struct { RepoId int64 `xorm:"INDEX"` Index int64 Name string - Content string + Content string `xorm:"TEXT"` RenderedContent string `xorm:"-"` IsClosed bool NumIssues int diff --git a/models/login.go b/models/login.go index da7722f2..2c5fc68e 100644 --- a/models/login.go +++ b/models/login.go @@ -161,12 +161,8 @@ func UserSignIn(uname, passwd string) (*User, error) { return nil, err } - if u.LoginType == NOTYPE { - if has { - u.LoginType = PLAIN - } else { - return nil, ErrUserNotExist - } + if u.LoginType == NOTYPE && has { + u.LoginType = PLAIN } // For plain login, user must exist to reach this line. diff --git a/models/models.go b/models/models.go index 4e2e08cf..570df0c1 100644 --- a/models/models.go +++ b/models/models.go @@ -55,11 +55,12 @@ func LoadModelsConfig() { DbCfg.Path = setting.Cfg.MustValue("database", "PATH", "data/gogs.db") } -func NewTestEngine(x *xorm.Engine) (err error) { +func getEngine() (*xorm.Engine, error) { + cnnstr := "" switch DbCfg.Type { case "mysql": - x, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", - DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name)) + cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", + DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name) case "postgres": var host, port = "127.0.0.1", "5432" fields := strings.Split(DbCfg.Host, ":") @@ -69,48 +70,33 @@ func NewTestEngine(x *xorm.Engine) (err error) { if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 { port = fields[1] } - cnnstr := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s", + cnnstr = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s", DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode) - x, err = xorm.NewEngine("postgres", cnnstr) case "sqlite3": if !EnableSQLite3 { - return fmt.Errorf("Unknown database type: %s", DbCfg.Type) + return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type) } os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm) - x, err = xorm.NewEngine("sqlite3", DbCfg.Path) + cnnstr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc" default: - return fmt.Errorf("Unknown database type: %s", DbCfg.Type) + return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type) } + return xorm.NewEngine(DbCfg.Type, cnnstr) +} + +func NewTestEngine(x *xorm.Engine) (err error) { + x, err = getEngine() if err != nil { - return fmt.Errorf("models.init(fail to conntect database): %v", err) + return fmt.Errorf("models.init(fail to connect to database): %v", err) } + return x.Sync(tables...) } func SetEngine() (err error) { - switch DbCfg.Type { - case "mysql": - x, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", - DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name)) - case "postgres": - var host, port = "127.0.0.1", "5432" - fields := strings.Split(DbCfg.Host, ":") - if len(fields) > 0 && len(strings.TrimSpace(fields[0])) > 0 { - host = fields[0] - } - if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 { - port = fields[1] - } - x, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s", - DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode)) - case "sqlite3": - os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm) - x, err = xorm.NewEngine("sqlite3", DbCfg.Path) - default: - return fmt.Errorf("Unknown database type: %s", DbCfg.Type) - } + x, err = getEngine() if err != nil { - return fmt.Errorf("models.init(fail to conntect database): %v", err) + return fmt.Errorf("models.init(fail to connect to database): %v", err) } // WARNNING: for serv command, MUST remove the output to os.stdout, @@ -125,6 +111,7 @@ func SetEngine() (err error) { x.Logger = xorm.NewSimpleLogger(f) x.ShowSQL = true + x.ShowInfo = true x.ShowDebug = true x.ShowErr = true x.ShowWarn = true diff --git a/models/org.go b/models/org.go index ce506705..31db8e36 100644 --- a/models/org.go +++ b/models/org.go @@ -507,7 +507,7 @@ func (t *Team) AddRepository(repo *Repository) (err error) { mode := AuthorizeToAccessType(t.Authorize) for _, u := range t.Members { - auth, err := GetHighestAuthorize(t.OrgId, u.Id, t.Id, repo.Id) + auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id) if err != nil { sess.Rollback() return err @@ -517,13 +517,7 @@ func (t *Team) AddRepository(repo *Repository) (err error) { UserName: u.LowerName, RepoName: path.Join(repo.Owner.LowerName, repo.LowerName), } - if auth == 0 { - access.Mode = mode - if _, err = sess.Insert(access); err != nil { - sess.Rollback() - return fmt.Errorf("fail to insert access: %v", err) - } - } else if auth < t.Authorize { + if auth < t.Authorize { if err = addAccessWithAuthorize(sess, access, mode); err != nil { sess.Rollback() return err @@ -570,7 +564,7 @@ func (t *Team) RemoveRepository(repoId int64) error { // Remove access to team members. for _, u := range t.Members { - auth, err := GetHighestAuthorize(t.OrgId, u.Id, t.Id, repo.Id) + auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id) if err != nil { sess.Rollback() return err @@ -668,7 +662,7 @@ func GetTeamById(teamId int64) (*Team, error) { } // GetHighestAuthorize returns highest repository authorize level for given user and team. -func GetHighestAuthorize(orgId, uid, teamId, repoId int64) (AuthorizeType, error) { +func GetHighestAuthorize(orgId, uid, repoId, teamId int64) (AuthorizeType, error) { ts, err := GetUserTeams(orgId, uid) if err != nil { return 0, err @@ -687,6 +681,7 @@ func GetHighestAuthorize(orgId, uid, teamId, repoId int64) (AuthorizeType, error } } } + return auth, nil } @@ -728,7 +723,7 @@ func UpdateTeam(t *Team, authChanged bool) (err error) { // ORG_WRITABLE is the highest authorize level for now. // Skip checking others if current team has this level. if t.Authorize < ORG_WRITABLE { - auth, err := GetHighestAuthorize(org.Id, u.Id, t.Id, repo.Id) + auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id) if err != nil { sess.Rollback() return err @@ -782,7 +777,7 @@ func DeleteTeam(t *Team) error { // Delete all accesses. for _, repo := range t.Repos { for _, u := range t.Members { - auth, err := GetHighestAuthorize(org.Id, u.Id, t.Id, repo.Id) + auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, t.Id) if err != nil { sess.Rollback() return err @@ -943,7 +938,7 @@ func AddTeamMember(orgId, teamId, uid int64) error { // Give access to team repositories. mode := AuthorizeToAccessType(t.Authorize) for _, repo := range t.Repos { - auth, err := GetHighestAuthorize(orgId, uid, teamId, repo.Id) + auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, teamId) if err != nil { sess.Rollback() return err @@ -953,14 +948,7 @@ func AddTeamMember(orgId, teamId, uid int64) error { UserName: u.LowerName, RepoName: path.Join(org.LowerName, repo.LowerName), } - // Equal 0 means given access doesn't exist. - if auth == 0 { - access.Mode = mode - if _, err = sess.Insert(access); err != nil { - sess.Rollback() - return fmt.Errorf("fail to insert access: %v", err) - } - } else if auth < t.Authorize { + if auth < t.Authorize { if err = addAccessWithAuthorize(sess, access, mode); err != nil { sess.Rollback() return err @@ -1037,7 +1025,7 @@ func removeTeamMemberWithSess(orgId, teamId, uid int64, sess *xorm.Session) erro // Delete access to team repositories. for _, repo := range t.Repos { - auth, err := GetHighestAuthorize(orgId, uid, teamId, repo.Id) + auth, err := GetHighestAuthorize(t.OrgId, u.Id, repo.Id, teamId) if err != nil { sess.Rollback() return err diff --git a/models/publickey.go b/models/publickey.go index 1246cffc..8bb924e8 100644 --- a/models/publickey.go +++ b/models/publickey.go @@ -22,6 +22,7 @@ import ( "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/process" + "github.com/gogits/gogs/modules/setting" ) const ( @@ -100,6 +101,7 @@ var ( "(MCE)": 1702, "(McE)": 1702, "(RSA)": 2048, + "(DSA)": 1024, } ) @@ -119,23 +121,30 @@ func CheckPublicKeyString(content string) (bool, error) { tmpFile.WriteString(content) tmpFile.Close() - // … see if ssh-keygen recognizes its contents + // Check if ssh-keygen recognizes its contents. stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath) if err != nil { return false, errors.New("ssh-keygen -l -f: " + stderr) } else if len(stdout) < 2 { return false, errors.New("ssh-keygen returned not enough output to evaluate the key") } + + // The ssh-keygen in Windows does not print key type, so no need go further. + if setting.IsWindows { + return true, nil + } + sshKeygenOutput := strings.Split(stdout, " ") if len(sshKeygenOutput) < 4 { return false, errors.New("Not enough fields returned by ssh-keygen -l -f") } + + // Check if key type and key size match. keySize, err := com.StrTo(sshKeygenOutput[0]).Int() if err != nil { return false, errors.New("Cannot get key size of the given key") } keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1]) - if minimumKeySize := MinimumKeySize[keyType]; minimumKeySize == 0 { return false, errors.New("Sorry, unrecognized public key type") } else if keySize < minimumKeySize { @@ -160,10 +169,14 @@ func saveAuthorizedKeyFile(key *PublicKey) error { if err != nil { return err } - if finfo.Mode().Perm() > 0600 { - log.Error(4, "authorized_keys file has unusual permission flags: %s - setting to -rw-------", finfo.Mode().Perm().String()) - if err = f.Chmod(0600); err != nil { - return err + + // FIXME: following command does not support in Windows. + if !setting.IsWindows { + if finfo.Mode().Perm() > 0600 { + log.Error(4, "authorized_keys file has unusual permission flags: %s - setting to -rw-------", finfo.Mode().Perm().String()) + if err = f.Chmod(0600); err != nil { + return err + } } } diff --git a/models/repo.go b/models/repo.go index 47036966..a79c2491 100644 --- a/models/repo.go +++ b/models/repo.go @@ -95,24 +95,35 @@ func NewRepoContext() { if err != nil { log.Fatal(4, "Fail to get Git version: %v", err) } - if ver.Major < 2 && ver.Minor < 8 { - log.Fatal(4, "Gogs requires Git version greater or equal to 1.8.0") - } - // Check if server has basic git setting. - stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name") + reqVer, err := git.ParseVersion("1.7.1") if err != nil { - log.Fatal(4, "Fail to get git user.name: %s", stderr) - } else if err != nil || len(strings.TrimSpace(stdout)) == 0 { - if _, stderr, err = process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil { - log.Fatal(4, "Fail to set git user.email: %s", stderr) - } else if _, stderr, err = process.Exec("NewRepoContext(set name)", "git", "config", "--global", "user.name", "Gogs"); err != nil { - log.Fatal(4, "Fail to set git user.name: %s", stderr) + log.Fatal(4, "Fail to parse required Git version: %v", err) + } + if ver.LessThan(reqVer) { + log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1") + } + + // Check if server has basic git setting and set if not. + if stdout, stderr, err := process.Exec("NewRepoContext(get setting)", "git", "config", "--get", "user.name"); err != nil || strings.TrimSpace(stdout) == "" { + // ExitError indicates user.name is not set + if _, ok := err.(*exec.ExitError); ok || strings.TrimSpace(stdout) == "" { + stndrdUserName := "Gogs" + stndrdUserEmail := "gogitservice@gmail.com" + if _, stderr, gerr := process.Exec("NewRepoContext(set name)", "git", "config", "--global", "user.name", stndrdUserName); gerr != nil { + log.Fatal(4, "Fail to set git user.name(%s): %s", gerr, stderr) + } + if _, stderr, gerr := process.Exec("NewRepoContext(set email)", "git", "config", "--global", "user.email", stndrdUserEmail); gerr != nil { + log.Fatal(4, "Fail to set git user.email(%s): %s", gerr, stderr) + } + log.Info("Git user.name and user.email set to %s <%s>", stndrdUserName, stndrdUserEmail) + } else { + log.Fatal(4, "Fail to get git user.name(%s): %s", err, stderr) } } // Set git some configurations. - if _, stderr, err = process.Exec("NewRepoContext(git config --global core.quotepath false)", + if _, stderr, err := process.Exec("NewRepoContext(git config --global core.quotepath false)", "git", "config", "--global", "core.quotepath", "false"); err != nil { log.Fatal(4, "Fail to execute 'git config --global core.quotepath false': %s", stderr) } @@ -305,30 +316,17 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str } repo.IsMirror = true return repo, UpdateRepository(repo) + } else { + os.RemoveAll(repoPath) } - // Clone from local repository. + // this command could for both migrate and mirror _, stderr, err := process.ExecTimeout(10*time.Minute, - fmt.Sprintf("MigrateRepository(git clone): %s", repoPath), - "git", "clone", repoPath, tmpDir) + fmt.Sprintf("MigrateRepository: %s", repoPath), + "git", "clone", "--mirror", "--bare", url, repoPath) if err != nil { return repo, errors.New("git clone: " + stderr) } - - // Add remote and fetch data. - if _, stderr, err = process.ExecDir(3*time.Minute, - tmpDir, fmt.Sprintf("MigrateRepository(git pull): %s", repoPath), - "git", "remote", "add", "-f", "--tags", "upstream", url); err != nil { - return repo, errors.New("git remote: " + stderr) - } - - // Push data to local repository. - if _, stderr, err = process.ExecDir(3*time.Minute, - tmpDir, fmt.Sprintf("MigrateRepository(git push): %s", repoPath), - "git", "push", "--tags", "origin", "refs/remotes/upstream/*:refs/heads/*"); err != nil { - return repo, errors.New("git push: " + stderr) - } - return repo, UpdateRepository(repo) } @@ -651,29 +649,54 @@ func RepoPath(userName, repoName string) string { } // TransferOwnership transfers all corresponding setting from old user to new one. -func TransferOwnership(u *User, newOwner string, repo *Repository) (err error) { +func TransferOwnership(u *User, newOwner string, repo *Repository) error { newUser, err := GetUserByName(newOwner) if err != nil { return err } + // Check if new owner has repository with same name. + has, err := IsRepositoryExist(newUser, repo.Name) + if err != nil { + return err + } else if has { + return ErrRepoAlreadyExist + } + sess := x.NewSession() defer sess.Close() if err = sess.Begin(); err != nil { return err } - if _, err = sess.Where("repo_name = ?", u.LowerName+"/"+repo.LowerName). - And("user_name = ?", u.LowerName).Update(&Access{UserName: newUser.LowerName}); err != nil { - sess.Rollback() - return err + owner := repo.Owner + oldRepoLink := path.Join(owner.LowerName, repo.LowerName) + // Delete all access first if current owner is an organization. + if owner.IsOrganization() { + if _, err = sess.Where("repo_name=?", oldRepoLink).Delete(new(Access)); err != nil { + sess.Rollback() + return fmt.Errorf("fail to delete current accesses: %v", err) + } + } else { + // Delete current owner access. + if _, err = sess.Where("repo_name=?", oldRepoLink).And("user_name=?", owner.LowerName). + Delete(new(Access)); err != nil { + sess.Rollback() + return fmt.Errorf("fail to delete access(owner): %v", err) + } + // In case new owner has access. + if _, err = sess.Where("repo_name=?", oldRepoLink).And("user_name=?", newUser.LowerName). + Delete(new(Access)); err != nil { + sess.Rollback() + return fmt.Errorf("fail to delete access(new user): %v", err) + } } - if _, err = sess.Where("repo_name = ?", u.LowerName+"/"+repo.LowerName).Update(&Access{ - RepoName: newUser.LowerName + "/" + repo.LowerName, - }); err != nil { + // Change accesses to new repository path. + if _, err = sess.Where("repo_name=?", oldRepoLink). + Update(&Access{RepoName: path.Join(newUser.LowerName, repo.LowerName)}); err != nil { sess.Rollback() - return err + return fmt.Errorf("fail to update access(change reponame): %v", err) } // Update repository. @@ -689,17 +712,17 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) (err error) { return err } - if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", u.Id); err != nil { + if _, err = sess.Exec("UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?", owner.Id); err != nil { sess.Rollback() return err } + mode := WRITABLE + if repo.IsMirror { + mode = READABLE + } // New owner is organization. if newUser.IsOrganization() { - mode := WRITABLE - if repo.IsMirror { - mode = READABLE - } access := &Access{ RepoName: path.Join(newUser.LowerName, repo.LowerName), Mode: mode, @@ -724,12 +747,6 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) (err error) { } } - if _, err = sess.Exec( - "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?", u.Id); err != nil { - sess.Rollback() - return err - } - // Update owner team info and count. t.RepoIds += "$" + com.ToStr(repo.Id) + "|" t.NumRepos++ @@ -737,10 +754,20 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) (err error) { sess.Rollback() return err } + } else { + access := &Access{ + RepoName: path.Join(newUser.LowerName, repo.LowerName), + UserName: newUser.LowerName, + Mode: mode, + } + if _, err = sess.Insert(access); err != nil { + sess.Rollback() + return fmt.Errorf("fail to insert access: %v", err) + } } // Change repository directory name. - if err = os.Rename(RepoPath(u.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil { + if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil { sess.Rollback() return err } @@ -749,14 +776,8 @@ func TransferOwnership(u *User, newOwner string, repo *Repository) (err error) { return err } - // Add watch of new owner to repository. - if !newUser.IsOrganization() { - if err = WatchRepo(newUser.Id, repo.Id, true); err != nil { - log.Error(4, "WatchRepo", err) - } - } - if err = WatchRepo(u.Id, repo.Id, false); err != nil { - log.Error(4, "WatchRepo2", err) + if err = WatchRepo(newUser.Id, repo.Id, true); err != nil { + log.Error(4, "WatchRepo", err) } if err = TransferRepoAction(u, newUser, repo); err != nil { @@ -940,9 +961,9 @@ func GetRepositoryByRef(ref string) (*Repository, error) { } // GetRepositoryByName returns the repository by given name under user if exists. -func GetRepositoryByName(userId int64, repoName string) (*Repository, error) { +func GetRepositoryByName(uid int64, repoName string) (*Repository, error) { repo := &Repository{ - OwnerId: userId, + OwnerId: uid, LowerName: strings.ToLower(repoName), } has, err := x.Get(repo) @@ -979,8 +1000,8 @@ func GetRepositories(uid int64, private bool) ([]*Repository, error) { } // GetRecentUpdatedRepositories returns the list of repositories that are recently updated. -func GetRecentUpdatedRepositories() (repos []*Repository, err error) { - err = x.Where("is_private=?", false).Limit(5).Desc("updated").Find(&repos) +func GetRecentUpdatedRepositories(num int) (repos []*Repository, err error) { + err = x.Where("is_private=?", false).Limit(num).Desc("updated").Find(&repos) return repos, err } @@ -1081,6 +1102,13 @@ func SearchRepositoryByName(opt SearchOption) (repos []*Repository, err error) { return repos, err } +// __ __ __ .__ +// / \ / \_____ _/ |_ ____ | |__ +// \ \/\/ /\__ \\ __\/ ___\| | \ +// \ / / __ \| | \ \___| Y \ +// \__/\ / (____ /__| \___ >___| / +// \/ \/ \/ \/ + // Watch is connection request for receiving repository notifycation. type Watch struct { Id int64 @@ -1151,6 +1179,13 @@ func NotifyWatchers(act *Action) error { return nil } +// _________ __ +// / _____// |______ _______ +// \_____ \\ __\__ \\_ __ \ +// / \| | / __ \| | \/ +// /_______ /|__| (____ /__| +// \/ \/ + type Star struct { Id int64 Uid int64 `xorm:"UNIQUE(s)"` @@ -1165,16 +1200,20 @@ func StarRepo(uid, repoId int64, star bool) (err error) { } if _, err = x.Insert(&Star{Uid: uid, RepoId: repoId}); err != nil { return err + } else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoId); err != nil { + return err } - _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars + 1 WHERE id = ?", repoId) + _, err = x.Exec("UPDATE `user` SET num_stars = num_stars + 1 WHERE id = ?", uid) } else { if !IsStaring(uid, repoId) { return nil } if _, err = x.Delete(&Star{0, uid, repoId}); err != nil { return err + } else if _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoId); err != nil { + return err } - _, err = x.Exec("UPDATE `repository` SET num_stars = num_stars - 1 WHERE id = ?", repoId) + _, err = x.Exec("UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", uid) } return err } diff --git a/models/slack.go b/models/slack.go new file mode 100644 index 00000000..3dd40759 --- /dev/null +++ b/models/slack.go @@ -0,0 +1,125 @@ +// 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 models + +import ( + "encoding/json" + "errors" + "fmt" + "strings" +) + +const ( + SLACK_COLOR string = "#dd4b39" +) + +type Slack struct { + Domain string `json:"domain"` + Token string `json:"token"` + Channel string `json:"channel"` +} + +type SlackPayload struct { + Channel string `json:"channel"` + Text string `json:"text"` + Username string `json:"username"` + IconUrl string `json:"icon_url"` + UnfurlLinks int `json:"unfurl_links"` + LinkNames int `json:"link_names"` + Attachments []SlackAttachment `json:"attachments"` +} + +type SlackAttachment struct { + Color string `json:"color"` + Text string `json:"text"` +} + +func GetSlackURL(domain string, token string) string { + return fmt.Sprintf( + "https://%s.slack.com/services/hooks/incoming-webhook?token=%s", + domain, + token, + ) +} + +func (p SlackPayload) GetJSONPayload() ([]byte, error) { + data, err := json.Marshal(p) + if err != nil { + return []byte{}, err + } + return data, nil +} + +func GetSlackPayload(p *Payload, meta string) (*SlackPayload, error) { + slack := &Slack{} + slackPayload := &SlackPayload{} + if err := json.Unmarshal([]byte(meta), &slack); err != nil { + return slackPayload, errors.New("GetSlackPayload meta json:" + err.Error()) + } + + // TODO: handle different payload types: push, new branch, delete branch etc. + // when they are added to gogs. Only handles push now + return getSlackPushPayload(p, slack) +} + +func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) { + // n new commits + refSplit := strings.Split(p.Ref, "/") + branchName := refSplit[len(refSplit)-1] + var commitString string + + if len(p.Commits) == 1 { + commitString = "1 new commit" + if p.CompareUrl != "" { + commitString = SlackLinkFormatter(p.CompareUrl, commitString) + } + } else { + commitString = fmt.Sprintf("%d new commits", len(p.Commits)) + if p.CompareUrl != "" { + commitString = SlackLinkFormatter(p.CompareUrl, commitString) + } + } + + repoLink := SlackLinkFormatter(p.Repo.Url, p.Repo.Name) + branchLink := SlackLinkFormatter(p.Repo.Url+"/src/"+branchName, branchName) + text := fmt.Sprintf("[%s:%s] %s pushed by %s", repoLink, branchLink, commitString, p.Pusher.Name) + var attachmentText string + + // for each commit, generate attachment text + for i, commit := range p.Commits { + attachmentText += fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.Url, commit.Id[:7]), SlackTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name)) + // add linebreak to each commit but the last + if i < len(p.Commits)-1 { + attachmentText += "\n" + } + } + + slackAttachments := []SlackAttachment{{Color: SLACK_COLOR, Text: attachmentText}} + + return &SlackPayload{ + Channel: slack.Channel, + Text: text, + Username: "gogs", + IconUrl: "https://raw.githubusercontent.com/gogits/gogs/master/public/img/favicon.png", + UnfurlLinks: 0, + LinkNames: 0, + Attachments: slackAttachments, + }, nil +} + +// see: https://api.slack.com/docs/formatting +func SlackTextFormatter(s string) string { + // take only first line of commit + first := strings.Split(s, "\n")[0] + // replace & < > + first = strings.Replace(first, "&", "&", -1) + first = strings.Replace(first, "<", "<", -1) + first = strings.Replace(first, ">", ">", -1) + return first +} + +func SlackLinkFormatter(url string, text string) string { + return fmt.Sprintf("<%s|%s>", url, SlackTextFormatter(text)) +} diff --git a/models/update.go b/models/update.go index 68a92ada..d939a908 100644 --- a/models/update.go +++ b/models/update.go @@ -23,6 +23,10 @@ type UpdateTask struct { NewCommitId string } +const ( + MAX_COMMITS int = 5 +) + func AddUpdateTask(task *UpdateTask) error { _, err := x.Insert(task) return err @@ -101,7 +105,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName commit := &base.PushCommits{} if err = CommitRepoAction(userId, ru.Id, userName, actEmail, - repos.Id, repoUserName, repoName, refName, commit); err != nil { + repos.Id, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil { log.GitLogger.Fatal(4, "runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err) } return err @@ -132,7 +136,6 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName // if commits push commits := make([]*base.PushCommit, 0) - var maxCommits = 2 var actEmail string for e := l.Front(); e != nil; e = e.Next() { commit := e.Value.(*git.Commit) @@ -145,14 +148,14 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName commit.Message(), commit.Author.Email, commit.Author.Name}) - if len(commits) >= maxCommits { + if len(commits) >= MAX_COMMITS { break } } //commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()}) if err = CommitRepoAction(userId, ru.Id, userName, actEmail, - repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil { + repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}, oldCommitId, newCommitId); err != nil { return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err) } return nil diff --git a/models/user.go b/models/user.go index 96881ea3..ee8f8586 100644 --- a/models/user.go +++ b/models/user.go @@ -5,6 +5,7 @@ package models import ( + "container/list" "crypto/sha256" "encoding/hex" "errors" @@ -82,22 +83,22 @@ type User struct { // DashboardLink returns the user dashboard page link. func (u *User) DashboardLink() string { if u.IsOrganization() { - return "/org/" + u.Name + "/dashboard/" + return setting.AppSubUrl + "/org/" + u.Name + "/dashboard/" } - return "/" + return setting.AppSubUrl + "/" } // HomeLink returns the user home page link. func (u *User) HomeLink() string { - return "/user/" + u.Name + return setting.AppSubUrl + "/" + u.Name } // AvatarLink returns user gravatar link. func (u *User) AvatarLink() string { if setting.DisableGravatar { - return "/img/avatar_default.jpg" + return setting.AppSubUrl + "/img/avatar_default.jpg" } else if setting.Service.EnableCacheAvatar { - return "/avatar/" + u.Avatar + return setting.AppSubUrl + "/avatar/" + u.Avatar } return "//1.gravatar.com/avatar/" + u.Avatar } @@ -167,6 +168,14 @@ func (u *User) GetOrganizations() error { return nil } +// GetFullNameFallback returns Full Name if set, otherwise username +func (u *User) GetFullNameFallback() string { + if u.FullName == "" { + return u.Name + } + return u.FullName +} + // IsUserExist checks if given user name exist, // the user name should be noncased unique. func IsUserExist(name string) (bool, error) { @@ -505,6 +514,49 @@ func GetUserIdsByNames(names []string) []int64 { return ids } +// UserCommit represtns a commit with validation of user. +type UserCommit struct { + UserName string + *git.Commit +} + +// ValidateCommitWithEmail chceck if author's e-mail of commit is corresponsind to a user. +func ValidateCommitWithEmail(c *git.Commit) (uname string) { + u, err := GetUserByEmail(c.Author.Email) + if err == nil { + uname = u.Name + } + return uname +} + +// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users. +func ValidateCommitsWithEmails(oldCommits *list.List) *list.List { + emails := map[string]string{} + newCommits := list.New() + e := oldCommits.Front() + for e != nil { + c := e.Value.(*git.Commit) + + uname := "" + if v, ok := emails[c.Author.Email]; !ok { + u, err := GetUserByEmail(c.Author.Email) + if err == nil { + uname = u.Name + } + emails[c.Author.Email] = uname + } else { + uname = v + } + + newCommits.PushBack(UserCommit{ + UserName: uname, + Commit: c, + }) + e = e.Next() + } + return newCommits +} + // GetUserByEmail returns the user object by given e-mail if exists. func GetUserByEmail(email string) (*User, error) { if len(email) == 0 { @@ -548,27 +600,27 @@ type Follow struct { // FollowUser marks someone be another's follower. func FollowUser(userId int64, followId int64) (err error) { - session := x.NewSession() - defer session.Close() - session.Begin() + sess := x.NewSession() + defer sess.Close() + sess.Begin() - if _, err = session.Insert(&Follow{UserId: userId, FollowId: followId}); err != nil { - session.Rollback() + if _, err = sess.Insert(&Follow{UserId: userId, FollowId: followId}); err != nil { + sess.Rollback() return err } rawSql := "UPDATE `user` SET num_followers = num_followers + 1 WHERE id = ?" - if _, err = session.Exec(rawSql, followId); err != nil { - session.Rollback() + if _, err = sess.Exec(rawSql, followId); err != nil { + sess.Rollback() return err } rawSql = "UPDATE `user` SET num_followings = num_followings + 1 WHERE id = ?" - if _, err = session.Exec(rawSql, userId); err != nil { - session.Rollback() + if _, err = sess.Exec(rawSql, userId); err != nil { + sess.Rollback() return err } - return session.Commit() + return sess.Commit() } // UnFollowUser unmarks someone be another's follower. diff --git a/models/webhook.go b/models/webhook.go index ced79366..9508c98a 100644 --- a/models/webhook.go +++ b/models/webhook.go @@ -7,6 +7,7 @@ package models import ( "encoding/json" "errors" + "io/ioutil" "time" "github.com/gogits/gogs/modules/httplib" @@ -33,15 +34,18 @@ type HookEvent struct { // Webhook represents a web hook object. type Webhook struct { - Id int64 - RepoId int64 - Url string `xorm:"TEXT"` - ContentType HookContentType - Secret string `xorm:"TEXT"` - Events string `xorm:"TEXT"` - *HookEvent `xorm:"-"` - IsSsl bool - IsActive bool + Id int64 + RepoId int64 + Url string `xorm:"TEXT"` + ContentType HookContentType + Secret string `xorm:"TEXT"` + Events string `xorm:"TEXT"` + *HookEvent `xorm:"-"` + IsSsl bool + IsActive bool + HookTaskType HookTaskType + Meta string `xorm:"TEXT"` // store hook-specific attributes + OrgId int64 } // GetEvent handles conversion from Events to HookEvent. @@ -52,6 +56,14 @@ func (w *Webhook) GetEvent() { } } +func (w *Webhook) GetSlackHook() *Slack { + s := &Slack{} + if err := json.Unmarshal([]byte(w.Meta), s); err != nil { + log.Error(4, "webhook.GetSlackHook(%d): %v", w.Id, err) + } + return s +} + // UpdateEvent handles conversion from HookEvent to Events. func (w *Webhook) UpdateEvent() error { data, err := json.Marshal(w.HookEvent) @@ -87,7 +99,7 @@ func GetWebhookById(hookId int64) (*Webhook, error) { // GetActiveWebhooksByRepoId returns all active webhooks of repository. func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) { - err = x.Find(&ws, &Webhook{RepoId: repoId, IsActive: true}) + err = x.Where("repo_id=?", repoId).And("is_active=?", true).Find(&ws) return ws, err } @@ -109,6 +121,18 @@ func DeleteWebhook(hookId int64) error { return err } +// GetWebhooksByOrgId returns all webhooks for an organization. +func GetWebhooksByOrgId(orgId int64) (ws []*Webhook, err error) { + err = x.Find(&ws, &Webhook{OrgId: orgId}) + 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 +} + // ___ ___ __ ___________ __ // / | \ ____ ____ | | _\__ ___/____ _____| | __ // / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ / @@ -119,8 +143,8 @@ func DeleteWebhook(hookId int64) error { type HookTaskType int const ( - WEBHOOK HookTaskType = iota + 1 - SERVICE + GOGS HookTaskType = iota + 1 + SLACK ) type HookEventType string @@ -130,8 +154,9 @@ const ( ) type PayloadAuthor struct { - Name string `json:"name"` - Email string `json:"email"` + Name string `json:"name"` + Email string `json:"email"` + UserName string `json:"username"` } type PayloadCommit struct { @@ -148,17 +173,32 @@ type PayloadRepo struct { Description string `json:"description"` Website string `json:"website"` Watchers int `json:"watchers"` - Owner *PayloadAuthor `json:"author"` + Owner *PayloadAuthor `json:"owner"` Private bool `json:"private"` } +type BasePayload interface { + GetJSONPayload() ([]byte, error) +} + // Payload represents a payload information of hook. type Payload struct { - Secret string `json:"secret"` - Ref string `json:"ref"` - Commits []*PayloadCommit `json:"commits"` - Repo *PayloadRepo `json:"repository"` - Pusher *PayloadAuthor `json:"pusher"` + Secret string `json:"secret"` + Ref string `json:"ref"` + Commits []*PayloadCommit `json:"commits"` + Repo *PayloadRepo `json:"repository"` + Pusher *PayloadAuthor `json:"pusher"` + Before string `json:"before"` + After string `json:"after"` + CompareUrl string `json:"compare_url"` +} + +func (p Payload) GetJSONPayload() ([]byte, error) { + data, err := json.Marshal(p) + if err != nil { + return []byte{}, err + } + return data, nil } // HookTask represents a hook task. @@ -167,19 +207,19 @@ type HookTask struct { Uuid string Type HookTaskType Url string - *Payload `xorm:"-"` + BasePayload `xorm:"-"` PayloadContent string `xorm:"TEXT"` ContentType HookContentType EventType HookEventType IsSsl bool - IsDeliveried bool + IsDelivered bool IsSucceed bool } // CreateHookTask creates a new hook task, // it handles conversion from Payload to PayloadContent. func CreateHookTask(t *HookTask) error { - data, err := json.Marshal(t.Payload) + data, err := t.BasePayload.GetJSONPayload() if err != nil { return err } @@ -191,14 +231,15 @@ func CreateHookTask(t *HookTask) error { // UpdateHookTask updates information of hook task. func UpdateHookTask(t *HookTask) error { - _, err := x.AllCols().Update(t) + _, err := x.Id(t.Id).AllCols().Update(t) return err } // DeliverHooks checks and delivers undelivered hooks. func DeliverHooks() { + tasks := make([]*HookTask, 0, 10) timeout := time.Duration(setting.WebhookDeliverTimeout) * time.Second - x.Where("is_deliveried=?", false).Iterate(new(HookTask), + x.Where("is_delivered=?", false).Iterate(new(HookTask), func(idx int, bean interface{}) error { t := bean.(*HookTask) req := httplib.Post(t.Url).SetTimeout(timeout, timeout). @@ -212,21 +253,50 @@ func DeliverHooks() { req.Param("payload", t.PayloadContent) } - t.IsDeliveried = true + t.IsDelivered = true // TODO: record response. - if _, err := req.Response(); err != nil { - log.Error(4, "Delivery: %v", err) - } else { - t.IsSucceed = true + switch t.Type { + case GOGS: + { + if _, err := req.Response(); err != nil { + log.Error(4, "Delivery: %v", err) + } else { + t.IsSucceed = true + } + } + case SLACK: + { + if res, err := req.Response(); err != nil { + log.Error(4, "Delivery: %v", err) + } else { + defer res.Body.Close() + contents, err := ioutil.ReadAll(res.Body) + if err != nil { + log.Error(4, "%s", err) + } else { + if string(contents) != "ok" { + log.Error(4, "slack failed with: %s", string(contents)) + } else { + t.IsSucceed = true + } + } + } + } } - if err := UpdateHookTask(t); err != nil { - log.Error(4, "UpdateHookTask: %v", err) - return nil - } + tasks = append(tasks, t) - log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent) + if t.IsSucceed { + log.Trace("Hook delivered(%s): %s", t.Uuid, t.PayloadContent) + } return nil }) + + // Update hook task status. + for _, t := range tasks { + if err := UpdateHookTask(t); err != nil { + log.Error(4, "UpdateHookTask(%d): %v", t.Id, err) + } + } } |