diff --git a/models/action.go b/models/action.go index 0253e2add..ab8a657e0 100644 --- a/models/action.go +++ b/models/action.go @@ -17,7 +17,6 @@ import ( "unicode" "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -441,6 +440,9 @@ func (pc *PushCommits) ToAPIPayloadCommits(repoLink string) []*api.PayloadCommit // AvatarLink tries to match user in database with e-mail // in order to show custom avatar, and falls back to general avatar link. func (pc *PushCommits) AvatarLink(email string) string { + if pc.avatars == nil { + pc.avatars = make(map[string]string) + } avatar, ok := pc.avatars[email] if ok { return avatar @@ -655,200 +657,6 @@ func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit, bra return nil } -// CommitRepoActionOptions represent options of a new commit action. -type CommitRepoActionOptions struct { - PusherName string - RepoOwnerID int64 - RepoName string - RefFullName string - OldCommitID string - NewCommitID string - Commits *PushCommits -} - -// CommitRepoAction adds new commit action to the repository, and prepare -// corresponding webhooks. -func CommitRepoAction(opts CommitRepoActionOptions) error { - pusher, err := GetUserByName(opts.PusherName) - if err != nil { - return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) - } - - repo, err := GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) - if err != nil { - return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) - } - - refName := git.RefEndName(opts.RefFullName) - - // Change default branch and empty status only if pushed ref is non-empty branch. - if repo.IsEmpty && opts.NewCommitID != git.EmptySHA && strings.HasPrefix(opts.RefFullName, git.BranchPrefix) { - repo.DefaultBranch = refName - repo.IsEmpty = false - if refName != "master" { - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - return err - } - if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { - if !git.IsErrUnsupportedVersion(err) { - return err - } - } - } - } - - // Change repository empty status and update last updated time. - if err = UpdateRepository(repo, false); err != nil { - return fmt.Errorf("UpdateRepository: %v", err) - } - - isNewBranch := false - opType := ActionCommitRepo - // Check it's tag push or branch. - if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { - opType = ActionPushTag - if opts.NewCommitID == git.EmptySHA { - opType = ActionDeleteTag - } - opts.Commits = &PushCommits{} - } else if opts.NewCommitID == git.EmptySHA { - opType = ActionDeleteBranch - opts.Commits = &PushCommits{} - } else { - // if not the first commit, set the compare URL. - if opts.OldCommitID == git.EmptySHA { - isNewBranch = true - } else { - opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) - } - - if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, refName); err != nil { - log.Error("updateIssuesCommit: %v", err) - } - } - - if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { - opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] - } - - data, err := json.Marshal(opts.Commits) - if err != nil { - return fmt.Errorf("Marshal: %v", err) - } - - if err = NotifyWatchers(&Action{ - ActUserID: pusher.ID, - ActUser: pusher, - OpType: opType, - Content: string(data), - RepoID: repo.ID, - Repo: repo, - 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(AccessModeNone) - - var shaSum string - var isHookEventPush = false - switch opType { - case ActionCommitRepo: // Push - isHookEventPush = true - - if isNewBranch { - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) - } - - shaSum, err = gitRepo.GetBranchCommitID(refName) - if err != nil { - log.Error("GetBranchCommitID[%s]: %v", opts.RefFullName, err) - } - if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{ - Ref: refName, - Sha: shaSum, - RefType: "branch", - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks: %v", err) - } - } - - case ActionDeleteBranch: // Delete Branch - isHookEventPush = true - - if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{ - Ref: refName, - RefType: "branch", - PusherType: api.PusherTypeUser, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err) - } - - case ActionPushTag: // Create - isHookEventPush = true - - gitRepo, err := git.OpenRepository(repo.RepoPath()) - if err != nil { - log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) - } - shaSum, err = gitRepo.GetTagCommitID(refName) - if err != nil { - log.Error("GetTagCommitID[%s]: %v", opts.RefFullName, err) - } - if err = PrepareWebhooks(repo, HookEventCreate, &api.CreatePayload{ - Ref: refName, - Sha: shaSum, - RefType: "tag", - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks: %v", err) - } - case ActionDeleteTag: // Delete Tag - isHookEventPush = true - - if err = PrepareWebhooks(repo, HookEventDelete, &api.DeletePayload{ - Ref: refName, - RefType: "tag", - PusherType: api.PusherTypeUser, - Repo: apiRepo, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err) - } - } - - if isHookEventPush { - if err = PrepareWebhooks(repo, HookEventPush, &api.PushPayload{ - Ref: opts.RefFullName, - Before: opts.OldCommitID, - After: opts.NewCommitID, - CompareURL: setting.AppURL + opts.Commits.CompareURL, - Commits: opts.Commits.ToAPIPayloadCommits(repo.HTMLURL()), - Repo: apiRepo, - Pusher: apiPusher, - Sender: apiPusher, - }); err != nil { - return fmt.Errorf("PrepareWebhooks: %v", err) - } - } - - return nil -} - func transferRepoAction(e Engine, doer, oldOwner *User, repo *Repository) (err error) { if err = notifyWatchers(e, &Action{ ActUserID: doer.ID, diff --git a/models/action_test.go b/models/action_test.go index da50ebce8..740976885 100644 --- a/models/action_test.go +++ b/models/action_test.go @@ -6,7 +6,6 @@ import ( "strings" "testing" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -409,119 +408,6 @@ func TestUpdateIssuesCommit_AnotherRepoNoPermission(t *testing.T) { CheckConsistencyFor(t, &Action{}) } -func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *Action) { - AssertNotExistsBean(t, actionBean) - assert.NoError(t, CommitRepoAction(opts)) - AssertExistsAndLoadBean(t, actionBean) - CheckConsistencyFor(t, &Action{}) -} - -func TestCommitRepoAction(t *testing.T) { - samples := []struct { - userID int64 - repositoryID int64 - commitRepoActionOptions CommitRepoActionOptions - action Action - }{ - { - userID: 2, - repositoryID: 2, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: "refName", - OldCommitID: "oldCommitID", - NewCommitID: "newCommitID", - Commits: &PushCommits{ - avatars: make(map[string]string), - Commits: []*PushCommit{ - { - Sha1: "abcdef1", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user4@example.com", - AuthorName: "User Four", - Message: "message1", - }, - { - Sha1: "abcdef2", - CommitterEmail: "user2@example.com", - CommitterName: "User Two", - AuthorEmail: "user2@example.com", - AuthorName: "User Two", - Message: "message2", - }, - }, - Len: 2, - }, - }, - action: Action{ - OpType: ActionCommitRepo, - RefName: "refName", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: git.TagPrefix + "v1.1", - OldCommitID: git.EmptySHA, - NewCommitID: "newCommitID", - Commits: &PushCommits{}, - }, - action: Action{ - OpType: ActionPushTag, - RefName: "v1.1", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: git.TagPrefix + "v1.1", - OldCommitID: "oldCommitID", - NewCommitID: git.EmptySHA, - Commits: &PushCommits{}, - }, - action: Action{ - OpType: ActionDeleteTag, - RefName: "v1.1", - }, - }, - { - userID: 2, - repositoryID: 1, - commitRepoActionOptions: CommitRepoActionOptions{ - RefFullName: git.BranchPrefix + "feature/1", - OldCommitID: "oldCommitID", - NewCommitID: git.EmptySHA, - Commits: &PushCommits{}, - }, - action: Action{ - OpType: ActionDeleteBranch, - RefName: "feature/1", - }, - }, - } - - for _, s := range samples { - PrepareTestEnv(t) - - user := AssertExistsAndLoadBean(t, &User{ID: s.userID}).(*User) - repo := AssertExistsAndLoadBean(t, &Repository{ID: s.repositoryID, OwnerID: user.ID}).(*Repository) - repo.Owner = user - - s.commitRepoActionOptions.PusherName = user.Name - s.commitRepoActionOptions.RepoOwnerID = user.ID - s.commitRepoActionOptions.RepoName = repo.Name - - s.action.ActUserID = user.ID - s.action.RepoID = repo.ID - s.action.Repo = repo - s.action.IsPrivate = repo.IsPrivate - - testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action) - } -} - func TestTransferRepoAction(t *testing.T) { assert.NoError(t, PrepareTestDatabase()) diff --git a/models/unit_tests.go b/models/unit_tests.go index 330dc5ee4..9dadf2e12 100644 --- a/models/unit_tests.go +++ b/models/unit_tests.go @@ -65,6 +65,13 @@ func MainTest(m *testing.M, pathToGiteaRoot string) { fatalTestError("url.Parse: %v\n", err) } + if err = removeAllWithRetry(setting.RepoRootPath); err != nil { + fatalTestError("os.RemoveAll: %v\n", err) + } + if err = com.CopyDir(filepath.Join(pathToGiteaRoot, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil { + fatalTestError("com.CopyDir: %v\n", err) + } + exitStatus := m.Run() if err = removeAllWithRetry(setting.RepoRootPath); err != nil { fatalTestError("os.RemoveAll: %v\n", err) diff --git a/modules/repofiles/action.go b/modules/repofiles/action.go new file mode 100644 index 000000000..ac5df9a14 --- /dev/null +++ b/modules/repofiles/action.go @@ -0,0 +1,211 @@ +// Copyright 2019 The Gitea 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 repofiles + +import ( + "encoding/json" + "fmt" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" +) + +// CommitRepoActionOptions represent options of a new commit action. +type CommitRepoActionOptions struct { + PusherName string + RepoOwnerID int64 + RepoName string + RefFullName string + OldCommitID string + NewCommitID string + Commits *models.PushCommits +} + +// CommitRepoAction adds new commit action to the repository, and prepare +// corresponding webhooks. +func CommitRepoAction(opts CommitRepoActionOptions) error { + pusher, err := models.GetUserByName(opts.PusherName) + if err != nil { + return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err) + } + + repo, err := models.GetRepositoryByName(opts.RepoOwnerID, opts.RepoName) + if err != nil { + return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err) + } + + refName := git.RefEndName(opts.RefFullName) + + // Change default branch and empty status only if pushed ref is non-empty branch. + if repo.IsEmpty && opts.NewCommitID != git.EmptySHA && strings.HasPrefix(opts.RefFullName, git.BranchPrefix) { + repo.DefaultBranch = refName + repo.IsEmpty = false + if refName != "master" { + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + return err + } + if err := gitRepo.SetDefaultBranch(repo.DefaultBranch); err != nil { + if !git.IsErrUnsupportedVersion(err) { + return err + } + } + } + } + + // Change repository empty status and update last updated time. + if err = models.UpdateRepository(repo, false); err != nil { + return fmt.Errorf("UpdateRepository: %v", err) + } + + isNewBranch := false + opType := models.ActionCommitRepo + // Check it's tag push or branch. + if strings.HasPrefix(opts.RefFullName, git.TagPrefix) { + opType = models.ActionPushTag + if opts.NewCommitID == git.EmptySHA { + opType = models.ActionDeleteTag + } + opts.Commits = &models.PushCommits{} + } else if opts.NewCommitID == git.EmptySHA { + opType = models.ActionDeleteBranch + opts.Commits = &models.PushCommits{} + } else { + // if not the first commit, set the compare URL. + if opts.OldCommitID == git.EmptySHA { + isNewBranch = true + } else { + opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID) + } + + if err = models.UpdateIssuesCommit(pusher, repo, opts.Commits.Commits, refName); err != nil { + log.Error("updateIssuesCommit: %v", err) + } + } + + if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum { + opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum] + } + + data, err := json.Marshal(opts.Commits) + if err != nil { + return fmt.Errorf("Marshal: %v", err) + } + + if err = models.NotifyWatchers(&models.Action{ + ActUserID: pusher.ID, + ActUser: pusher, + OpType: opType, + Content: string(data), + RepoID: repo.ID, + Repo: repo, + RefName: refName, + IsPrivate: repo.IsPrivate, + }); err != nil { + return fmt.Errorf("NotifyWatchers: %v", err) + } + + defer func() { + go models.HookQueue.Add(repo.ID) + }() + + apiPusher := pusher.APIFormat() + apiRepo := repo.APIFormat(models.AccessModeNone) + + var shaSum string + var isHookEventPush = false + switch opType { + case models.ActionCommitRepo: // Push + isHookEventPush = true + + if isNewBranch { + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) + } + + shaSum, err = gitRepo.GetBranchCommitID(refName) + if err != nil { + log.Error("GetBranchCommitID[%s]: %v", opts.RefFullName, err) + } + if err = models.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ + Ref: refName, + Sha: shaSum, + RefType: "branch", + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks: %v", err) + } + } + + case models.ActionDeleteBranch: // Delete Branch + isHookEventPush = true + + if err = models.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{ + Ref: refName, + RefType: "branch", + PusherType: api.PusherTypeUser, + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks.(delete branch): %v", err) + } + + case models.ActionPushTag: // Create + isHookEventPush = true + + gitRepo, err := git.OpenRepository(repo.RepoPath()) + if err != nil { + log.Error("OpenRepository[%s]: %v", repo.RepoPath(), err) + } + shaSum, err = gitRepo.GetTagCommitID(refName) + if err != nil { + log.Error("GetTagCommitID[%s]: %v", opts.RefFullName, err) + } + if err = models.PrepareWebhooks(repo, models.HookEventCreate, &api.CreatePayload{ + Ref: refName, + Sha: shaSum, + RefType: "tag", + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks: %v", err) + } + case models.ActionDeleteTag: // Delete Tag + isHookEventPush = true + + if err = models.PrepareWebhooks(repo, models.HookEventDelete, &api.DeletePayload{ + Ref: refName, + RefType: "tag", + PusherType: api.PusherTypeUser, + Repo: apiRepo, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks.(delete tag): %v", err) + } + } + + if isHookEventPush { + if err = models.PrepareWebhooks(repo, models.HookEventPush, &api.PushPayload{ + Ref: opts.RefFullName, + Before: opts.OldCommitID, + After: opts.NewCommitID, + CompareURL: setting.AppURL + opts.Commits.CompareURL, + Commits: opts.Commits.ToAPIPayloadCommits(repo.HTMLURL()), + Repo: apiRepo, + Pusher: apiPusher, + Sender: apiPusher, + }); err != nil { + return fmt.Errorf("PrepareWebhooks: %v", err) + } + } + + return nil +} diff --git a/modules/repofiles/action_test.go b/modules/repofiles/action_test.go new file mode 100644 index 000000000..322c668da --- /dev/null +++ b/modules/repofiles/action_test.go @@ -0,0 +1,126 @@ +// Copyright 2019 The Gitea 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 repofiles + +import ( + "testing" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/git" + + "github.com/stretchr/testify/assert" +) + +func testCorrectRepoAction(t *testing.T, opts CommitRepoActionOptions, actionBean *models.Action) { + models.AssertNotExistsBean(t, actionBean) + assert.NoError(t, CommitRepoAction(opts)) + models.AssertExistsAndLoadBean(t, actionBean) + models.CheckConsistencyFor(t, &models.Action{}) +} + +func TestCommitRepoAction(t *testing.T) { + samples := []struct { + userID int64 + repositoryID int64 + commitRepoActionOptions CommitRepoActionOptions + action models.Action + }{ + { + userID: 2, + repositoryID: 2, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: "refName", + OldCommitID: "oldCommitID", + NewCommitID: "newCommitID", + Commits: &models.PushCommits{ + Commits: []*models.PushCommit{ + { + Sha1: "abcdef1", + CommitterEmail: "user2@example.com", + CommitterName: "User Two", + AuthorEmail: "user4@example.com", + AuthorName: "User Four", + Message: "message1", + }, + { + Sha1: "abcdef2", + CommitterEmail: "user2@example.com", + CommitterName: "User Two", + AuthorEmail: "user2@example.com", + AuthorName: "User Two", + Message: "message2", + }, + }, + Len: 2, + }, + }, + action: models.Action{ + OpType: models.ActionCommitRepo, + RefName: "refName", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: git.TagPrefix + "v1.1", + OldCommitID: git.EmptySHA, + NewCommitID: "newCommitID", + Commits: &models.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionPushTag, + RefName: "v1.1", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: git.TagPrefix + "v1.1", + OldCommitID: "oldCommitID", + NewCommitID: git.EmptySHA, + Commits: &models.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionDeleteTag, + RefName: "v1.1", + }, + }, + { + userID: 2, + repositoryID: 1, + commitRepoActionOptions: CommitRepoActionOptions{ + RefFullName: git.BranchPrefix + "feature/1", + OldCommitID: "oldCommitID", + NewCommitID: git.EmptySHA, + Commits: &models.PushCommits{}, + }, + action: models.Action{ + OpType: models.ActionDeleteBranch, + RefName: "feature/1", + }, + }, + } + + for _, s := range samples { + models.PrepareTestEnv(t) + + user := models.AssertExistsAndLoadBean(t, &models.User{ID: s.userID}).(*models.User) + repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: s.repositoryID, OwnerID: user.ID}).(*models.Repository) + repo.Owner = user + + s.commitRepoActionOptions.PusherName = user.Name + s.commitRepoActionOptions.RepoOwnerID = user.ID + s.commitRepoActionOptions.RepoName = repo.Name + + s.action.ActUserID = user.ID + s.action.RepoID = repo.ID + s.action.Repo = repo + s.action.IsPrivate = repo.IsPrivate + + testCorrectRepoAction(t, s.commitRepoActionOptions, &s.action) + } +} diff --git a/modules/repofiles/update.go b/modules/repofiles/update.go index 118970487..21df77606 100644 --- a/modules/repofiles/update.go +++ b/modules/repofiles/update.go @@ -497,7 +497,7 @@ func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOp commits = models.ListToPushCommits(l) } - if err := models.CommitRepoAction(models.CommitRepoActionOptions{ + if err := CommitRepoAction(CommitRepoActionOptions{ PusherName: opts.PusherName, RepoOwnerID: repo.OwnerID, RepoName: repo.Name,