mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-31 21:48:19 +00:00
feat(webhook): fire repository event on repo rename (#38641)
Repository create and delete already fire `repository` webhooks, rename did not. This adds the `renamed` action with `changes.name.from` carrying the previous name, and renders it in the chat converters. Actions workflows are unaffected, they still do not trigger on rename. AI assistance was used for the implementation and tests. Fixes https://github.com/go-gitea/gitea/issues/34891. Co-authored-by: roman s <roman.sukach@dust-labs.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -424,6 +424,8 @@ type ChangesPayload struct {
|
||||
Body *ChangesFromPayload `json:"body,omitempty"`
|
||||
// Changes made to the reference
|
||||
Ref *ChangesFromPayload `json:"ref,omitempty"`
|
||||
// Changes made to the repository name
|
||||
Name *ChangesFromPayload `json:"name,omitempty"`
|
||||
// Changes made to the labels added
|
||||
AddedLabels []*Label `json:"added_labels"`
|
||||
// Changes made to the labels removed
|
||||
@@ -508,6 +510,8 @@ const (
|
||||
HookRepoCreated HookRepoAction = "created"
|
||||
// HookRepoDeleted deleted
|
||||
HookRepoDeleted HookRepoAction = "deleted"
|
||||
// HookRepoRenamed renamed
|
||||
HookRepoRenamed HookRepoAction = "renamed"
|
||||
)
|
||||
|
||||
// RepositoryPayload payload for repository webhooks
|
||||
@@ -520,6 +524,8 @@ type RepositoryPayload struct {
|
||||
Organization *User `json:"organization"`
|
||||
// The user who performed the action
|
||||
Sender *User `json:"sender"`
|
||||
// Changes made to the repository
|
||||
Changes *ChangesPayload `json:"changes,omitempty"`
|
||||
}
|
||||
|
||||
// JSONPayload JSON representation of the payload
|
||||
|
||||
@@ -2298,7 +2298,7 @@
|
||||
"repo.settings.event_force_push": "Force Push",
|
||||
"repo.settings.event_push_desc": "Git push to a repository.",
|
||||
"repo.settings.event_repository": "Repository",
|
||||
"repo.settings.event_repository_desc": "Repository created or deleted.",
|
||||
"repo.settings.event_repository_desc": "Repository created, deleted, or renamed.",
|
||||
"repo.settings.event_header_issue": "Issue Events",
|
||||
"repo.settings.event_issues": "Issues",
|
||||
"repo.settings.event_issues_desc": "Issue opened, closed, reopened, edited or deleted.",
|
||||
|
||||
@@ -152,6 +152,9 @@ func (dc dingtalkConvertor) Repository(p *api.RepositoryPayload) (DingtalkPayloa
|
||||
Content: title,
|
||||
},
|
||||
}, nil
|
||||
case api.HookRepoRenamed:
|
||||
title := fmt.Sprintf("[%s] Repository renamed from %s", p.Repository.FullName, getRepoRenamedFrom(p))
|
||||
return createDingtalkPayload(title, title, "view repository", p.Repository.HTMLURL), nil
|
||||
}
|
||||
|
||||
return DingtalkPayload{}, nil
|
||||
|
||||
@@ -241,6 +241,10 @@ func (d discordConvertor) Repository(p *api.RepositoryPayload) (DiscordPayload,
|
||||
case api.HookRepoDeleted:
|
||||
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
|
||||
color = redColor
|
||||
case api.HookRepoRenamed:
|
||||
title = fmt.Sprintf("[%s] Repository renamed from %s", p.Repository.FullName, getRepoRenamedFrom(p))
|
||||
url = p.Repository.HTMLURL
|
||||
color = greenColor
|
||||
}
|
||||
|
||||
return d.createPayload(p.Sender, title, "", url, color), nil
|
||||
|
||||
@@ -148,6 +148,9 @@ func (fc feishuConvertor) Repository(p *api.RepositoryPayload) (FeishuPayload, e
|
||||
case api.HookRepoDeleted:
|
||||
text = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
|
||||
return newFeishuTextPayload(text), nil
|
||||
case api.HookRepoRenamed:
|
||||
text = fmt.Sprintf("[%s] Repository renamed from %s", p.Repository.FullName, getRepoRenamedFrom(p))
|
||||
return newFeishuTextPayload(text), nil
|
||||
}
|
||||
|
||||
return FeishuPayload{}, nil
|
||||
|
||||
@@ -293,6 +293,13 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo
|
||||
return text, issueTitle, color
|
||||
}
|
||||
|
||||
func getRepoRenamedFrom(p *api.RepositoryPayload) string {
|
||||
if p.Changes == nil || p.Changes.Name == nil {
|
||||
return ""
|
||||
}
|
||||
return p.Changes.Name.From
|
||||
}
|
||||
|
||||
func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
|
||||
refLink := linkFormatter(p.Package.HTMLURL, p.Package.Name+":"+p.Package.Version)
|
||||
|
||||
|
||||
@@ -227,6 +227,8 @@ func (m matrixConvertor) Repository(p *api.RepositoryPayload) (MatrixPayload, er
|
||||
text = fmt.Sprintf("[%s] Repository created by %s", repoLink, senderLink)
|
||||
case api.HookRepoDeleted:
|
||||
text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink)
|
||||
case api.HookRepoRenamed:
|
||||
text = fmt.Sprintf("[%s] Repository renamed from %s by %s", repoLink, getRepoRenamedFrom(p), senderLink)
|
||||
}
|
||||
return m.newPayload(text)
|
||||
}
|
||||
|
||||
@@ -259,6 +259,10 @@ func (m msteamsConvertor) Repository(p *api.RepositoryPayload) (MSTeamsPayload,
|
||||
case api.HookRepoDeleted:
|
||||
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
|
||||
color = yellowColor
|
||||
case api.HookRepoRenamed:
|
||||
title = fmt.Sprintf("[%s] Repository renamed from %s", p.Repository.FullName, getRepoRenamedFrom(p))
|
||||
url = p.Repository.HTMLURL
|
||||
color = greenColor
|
||||
}
|
||||
|
||||
return createMSTeamsPayload(
|
||||
|
||||
@@ -134,6 +134,18 @@ func (m *webhookNotifier) DeleteRepository(ctx context.Context, doer *user_model
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) RenameRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldRepoName string) {
|
||||
if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventRepository, &api.RepositoryPayload{
|
||||
Action: api.HookRepoRenamed,
|
||||
Repository: convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner}),
|
||||
Organization: convert.ToUser(ctx, repo.MustOwner(ctx), nil),
|
||||
Sender: convert.ToUser(ctx, doer, nil),
|
||||
Changes: &api.ChangesPayload{Name: &api.ChangesFromPayload{From: oldRepoName}},
|
||||
}); err != nil {
|
||||
log.Error("PrepareWebhooks [repo_id: %d]: %v", repo.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *webhookNotifier) MigrateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) {
|
||||
// Add to hook queue for created repo after session commit.
|
||||
if err := PrepareWebhooks(ctx, EventSource{Repository: repo}, webhook_module.HookEventRepository, &api.RepositoryPayload{
|
||||
|
||||
@@ -276,6 +276,8 @@ func (s slackConvertor) Repository(p *api.RepositoryPayload) (SlackPayload, erro
|
||||
text = fmt.Sprintf("[%s] Repository created by %s", repoLink, senderLink)
|
||||
case api.HookRepoDeleted:
|
||||
text = fmt.Sprintf("[%s] Repository deleted by %s", repoLink, senderLink)
|
||||
case api.HookRepoRenamed:
|
||||
text = fmt.Sprintf("[%s] Repository renamed from %s by %s", repoLink, getRepoRenamedFrom(p), senderLink)
|
||||
}
|
||||
|
||||
return s.createPayload(text, nil), nil
|
||||
|
||||
@@ -114,6 +114,12 @@ func TestSlackPayload(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Repository created by <https://try.gitea.io/user1|user1>", pl.Text)
|
||||
|
||||
p.Action = api.HookRepoRenamed
|
||||
p.Changes = &api.ChangesPayload{Name: &api.ChangesFromPayload{From: "old-repo"}}
|
||||
pl, err = sc.Repository(p)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Repository renamed from old-repo by <https://try.gitea.io/user1|user1>", pl.Text)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
|
||||
@@ -154,6 +154,9 @@ func (t telegramConvertor) Repository(p *api.RepositoryPayload) (TelegramPayload
|
||||
case api.HookRepoDeleted:
|
||||
title = fmt.Sprintf("[%s] Repository deleted", html.EscapeString(p.Repository.FullName))
|
||||
return createTelegramPayloadHTML(title), nil
|
||||
case api.HookRepoRenamed:
|
||||
title = fmt.Sprintf("[%s] Repository renamed from %s", htmlLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName), html.EscapeString(getRepoRenamedFrom(p)))
|
||||
return createTelegramPayloadHTML(title), nil
|
||||
}
|
||||
return TelegramPayload{}, nil
|
||||
}
|
||||
|
||||
@@ -150,6 +150,9 @@ func (wc wechatworkConvertor) Repository(p *api.RepositoryPayload) (WechatworkPa
|
||||
case api.HookRepoDeleted:
|
||||
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
|
||||
return newWechatworkMarkdownPayload(title), nil
|
||||
case api.HookRepoRenamed:
|
||||
title = fmt.Sprintf("[%s] Repository renamed from %s", p.Repository.FullName, getRepoRenamedFrom(p))
|
||||
return newWechatworkMarkdownPayload(title), nil
|
||||
}
|
||||
|
||||
return WechatworkPayload{}, nil
|
||||
|
||||
@@ -859,6 +859,20 @@ func Test_WebhookRepository(t *testing.T) {
|
||||
assert.Equal(t, "org3", payloads[0].Organization.UserName)
|
||||
assert.Equal(t, "repo_new", payloads[0].Repository.Name)
|
||||
assert.Equal(t, "org3/repo_new", payloads[0].Repository.FullName)
|
||||
|
||||
// 4. rename the repository and validate the webhook is triggered again
|
||||
newName := "repo_renamed"
|
||||
req := NewRequestWithJSON(t, "PATCH", "/api/v1/repos/org3/repo_new", &api.EditRepoOption{
|
||||
Name: &newName,
|
||||
}).AddTokenAuth(getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
require.Len(t, payloads, 2)
|
||||
assert.Equal(t, api.HookRepoRenamed, payloads[1].Action)
|
||||
assert.Equal(t, newName, payloads[1].Repository.Name)
|
||||
assert.Equal(t, "org3/"+newName, payloads[1].Repository.FullName)
|
||||
require.NotNil(t, payloads[1].Changes.Name)
|
||||
assert.Equal(t, "repo_new", payloads[1].Changes.Name.From)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user