diff --git a/docs/content/doc/features/comparison.en-us.md b/docs/content/doc/features/comparison.en-us.md
index 745c5d37bc729..a703766855f4b 100644
--- a/docs/content/doc/features/comparison.en-us.md
+++ b/docs/content/doc/features/comparison.en-us.md
@@ -50,7 +50,7 @@ _Symbols used in table:_
| Repository Tokens with write rights | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Built-in Container Registry | [✘](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| External git mirroring | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
-| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
+| FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| Built-in CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| Subgroups: groups within groups | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ |
@@ -66,7 +66,6 @@ _Symbols used in table:_
| Granular user roles (Code, Issues, Wiki etc) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| Verified Committer | ⁄ | ✘ | ? | ✓ | ✓ | ✓ | ✘ |
| GPG Signed Commits | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
-| SSH Signed Commits | ✓ | ✘ | ✘ | ✘ | ✘ | ? | ? |
| Reject unsigned commits | [✓](https://github.com/go-gitea/gitea/pull/9708) | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Repository Activity page | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Branch manager | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
diff --git a/docs/content/doc/features/comparison.zh-cn.md b/docs/content/doc/features/comparison.zh-cn.md
index 98a50f5dc2abd..8ccdebe8cd94d 100644
--- a/docs/content/doc/features/comparison.zh-cn.md
+++ b/docs/content/doc/features/comparison.zh-cn.md
@@ -48,7 +48,7 @@ _表格中的符号含义:_
| 仓库写权限令牌 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ |
| 内置容器 Registry | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| 外部 Git 镜像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
-| WebAuthn (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ? |
+| FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| 内置 CI/CD | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| 子组织:组织内的组织 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ |
@@ -64,7 +64,6 @@ _表格中的符号含义:_
| 细粒度用户角色 (例如 Code, Issues, Wiki) | ✓ | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| 提交人的身份验证 | ✘ | ✘ | ? | ✓ | ✓ | ✓ | ✘ |
| GPG 签名的提交 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
-| SSH 签名的提交 | ✓ | ✘ | ✘ | ✘ | ✘ | ? | ? |
| 拒绝未用通过验证的提交 | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ |
| 仓库活跃度页面 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
| 分支管理 | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✓ |
diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go
index 1d19ebd24e76b..8a9a3ffd1d6aa 100644
--- a/models/webhook/hooktask.go
+++ b/models/webhook/hooktask.go
@@ -116,6 +116,9 @@ type HookTask struct {
RequestInfo *HookRequest `xorm:"-"`
ResponseContent string `xorm:"TEXT"`
ResponseInfo *HookResponse `xorm:"-"`
+
+ // Used for Auth Headers.
+ BearerToken string
}
func init() {
diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go
index ffc9b72b64d88..4e6d0ebf68f82 100644
--- a/models/webhook/webhook.go
+++ b/models/webhook/webhook.go
@@ -158,6 +158,7 @@ const (
DINGTALK HookType = "dingtalk"
TELEGRAM HookType = "telegram"
MSTEAMS HookType = "msteams"
+ TEAMCITY HookType = "teamcity"
FEISHU HookType = "feishu"
MATRIX HookType = "matrix"
WECHATWORK HookType = "wechatwork"
diff --git a/modules/queue/workerpool.go b/modules/queue/workerpool.go
index 100197c5e1f1f..4b36ca6f76bfa 100644
--- a/modules/queue/workerpool.go
+++ b/modules/queue/workerpool.go
@@ -115,9 +115,6 @@ func (p *WorkerPool) hasNoWorkerScaling() bool {
return p.numberOfWorkers == 0 && (p.boostTimeout == 0 || p.boostWorkers == 0 || p.maxNumberOfWorkers == 0)
}
-// zeroBoost will add a temporary boost worker for a no worker queue
-// p.lock must be locked at the start of this function BUT it will be unlocked by the end of this function
-// (This is because addWorkers has to be called whilst unlocked)
func (p *WorkerPool) zeroBoost() {
ctx, cancel := context.WithTimeout(p.baseCtx, p.boostTimeout)
mq := GetManager().GetManagedQueue(p.qid)
diff --git a/modules/setting/webhook.go b/modules/setting/webhook.go
index 0bfd7dcb4dd3f..755a9d88422cc 100644
--- a/modules/setting/webhook.go
+++ b/modules/setting/webhook.go
@@ -36,7 +36,7 @@ func newWebhookService() {
Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5)
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool()
Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString("")
- Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist"}
+ Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "teamcity", "feishu", "matrix", "wechatwork", "packagist"}
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("")
if Webhook.ProxyURL != "" {
diff --git a/modules/structs/hook.go b/modules/structs/hook.go
index e4d7652c72a99..76f6943f2e029 100644
--- a/modules/structs/hook.go
+++ b/modules/structs/hook.go
@@ -40,7 +40,7 @@ type CreateHookOptionConfig map[string]string
// CreateHookOption options when create a hook
type CreateHookOption struct {
// required: true
- // enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist
+ // enum: dingtalk,discord,gitea,gogs,msteams,teamcity,slack,telegram,feishu,wechatwork,packagist
Type string `json:"type" binding:"Required"`
// required: true
Config CreateHookOptionConfig `json:"config" binding:"Required"`
diff --git a/options/locale/locale_bg-BG.ini b/options/locale/locale_bg-BG.ini
index 4354ed1cdeb12..1cd0edecb175f 100644
--- a/options/locale/locale_bg-BG.ini
+++ b/options/locale/locale_bg-BG.ini
@@ -891,6 +891,9 @@ settings.hook_type=Тип на куката
settings.slack_token=API ключ
settings.slack_domain=Домейн
settings.slack_channel=Канал
+settings.teamcity_host_url = Host URL
+settings.teamcity_vcs_root_id = VCS Root ID
+settings.teamcity_auth_token = Authentication Token
settings.deploy_keys=Ключове за внедряване
settings.add_deploy_key=Добави ключ за внедряване
settings.title=Заглавие
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index e91016bdc0506..7ce0f6737c8ea 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1943,6 +1943,9 @@ settings.hook_type = Hook Type
settings.slack_token = Token
settings.slack_domain = Domain
settings.slack_channel = Channel
+settings.teamcity_host_url = Host URL
+settings.teamcity_vcs_root_id = VCS Root ID
+settings.teamcity_auth_token = Authentication Token
settings.add_web_hook_desc = Integrate %s into your repository.
settings.web_hook_name_gitea = Gitea
settings.web_hook_name_gogs = Gogs
@@ -1952,6 +1955,7 @@ settings.web_hook_name_dingtalk = DingTalk
settings.web_hook_name_telegram = Telegram
settings.web_hook_name_matrix = Matrix
settings.web_hook_name_msteams = Microsoft Teams
+settings.web_hook_name_teamcity = TeamCity
settings.web_hook_name_feishu_or_larksuite = Feishu / Lark Suite
settings.web_hook_name_feishu = Feishu
settings.web_hook_name_larksuite = Lark Suite
diff --git a/public/img/teamcity.svg b/public/img/teamcity.svg
new file mode 100644
index 0000000000000..ca14b3dc1d44f
--- /dev/null
+++ b/public/img/teamcity.svg
@@ -0,0 +1,64 @@
+
+
+
\ No newline at end of file
diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go
index fb984de7f5857..9b93c446a483b 100644
--- a/routers/web/repo/webhook.go
+++ b/routers/web/repo/webhook.go
@@ -535,6 +535,66 @@ func MSTeamsHooksNewPost(ctx *context.Context) {
ctx.Redirect(orCtx.Link)
}
+// TeamCityHooksNewPost response for creating TeamCity hook
+func TeamCityHooksNewPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*forms.NewTeamCityHookForm)
+ ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
+ ctx.Data["PageIsSettingHooks"] = true
+ ctx.Data["PageIsSettingHooksNew"] = true
+ ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}}
+ ctx.Data["HookType"] = webhook.TEAMCITY
+
+ orCtx, err := getOrgRepoCtx(ctx)
+ if err != nil {
+ ctx.ServerError("getOrgRepoCtx", err)
+ }
+ ctx.Data["BaseLink"] = orCtx.Link
+
+ if ctx.HasError() {
+ ctx.HTML(200, orCtx.NewTemplate)
+ return
+ }
+
+ meta, err := json.Marshal(&webhook_service.TeamCityMeta{
+ HostURL: form.HostURL,
+ AuthToken: form.AuthToken,
+ VcsRootID: form.VcsRootID,
+ })
+ if err != nil {
+ ctx.ServerError("Marshal", err)
+ return
+ }
+
+ payloadURL, err := buildTeamCityURL(form)
+ if err != nil {
+ ctx.ServerError("buildTeamCityURL", err)
+ return
+ }
+
+ w := &webhook.Webhook{
+ RepoID: orCtx.RepoID,
+ URL: payloadURL,
+ ContentType: webhook.ContentTypeForm,
+ HookEvent: ParseHookEvent(form.WebhookForm),
+ IsActive: form.Active,
+ Type: webhook.TEAMCITY,
+ HTTPMethod: http.MethodPost,
+ Meta: string(meta),
+ OrgID: orCtx.OrgID,
+ IsSystemWebhook: orCtx.IsSystemWebhook,
+ }
+ if err := w.UpdateEvent(); err != nil {
+ ctx.ServerError("UpdateEvent", err)
+ return
+ } else if err := webhook.CreateWebhook(db.DefaultContext, w); err != nil {
+ ctx.ServerError("CreateWebhook", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
+ ctx.Redirect(orCtx.Link)
+}
+
// SlackHooksNewPost response for creating slack hook
func SlackHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewSlackHookForm)
@@ -770,6 +830,8 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) {
ctx.Data["DiscordHook"] = webhook_service.GetDiscordHook(w)
case webhook.TELEGRAM:
ctx.Data["TelegramHook"] = webhook_service.GetTelegramHook(w)
+ case webhook.TEAMCITY:
+ ctx.Data["TeamCityHook"] = webhook_service.GetTeamCityHook(w)
case webhook.MATRIX:
ctx.Data["MatrixHook"] = webhook_service.GetMatrixHook(w)
case webhook.PACKAGIST:
@@ -1126,6 +1188,70 @@ func MSTeamsHooksEditPost(ctx *context.Context) {
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}
+// TeamCityHooksEditPost response for editing teamcity hook
+func TeamCityHooksEditPost(ctx *context.Context) {
+ form := web.GetForm(ctx).(*forms.NewTeamCityHookForm)
+
+ ctx.Data["Title"] = ctx.Tr("repo.settings")
+ ctx.Data["PageIsSettingHooks"] = true
+ ctx.Data["PageIsSettingHooksEdit"] = true
+
+ orCtx, w := checkWebhook(ctx)
+
+ if ctx.Written() {
+ return
+ }
+
+ ctx.Data["Webhook"] = w
+
+ if ctx.HasError() {
+ ctx.HTML(200, orCtx.NewTemplate)
+ return
+ }
+
+ meta, err := json.Marshal(&webhook_service.TeamCityMeta{
+ HostURL: form.HostURL,
+ AuthToken: form.AuthToken,
+ VcsRootID: form.VcsRootID,
+ })
+ if err != nil {
+ ctx.ServerError("Marshal", err)
+ return
+ }
+
+ w.URL, err = buildTeamCityURL(form)
+ if err != nil {
+ ctx.ServerError("buildTeamCityURL", err)
+ return
+ }
+
+ w.HTTPMethod = http.MethodPost
+ w.Meta = string(meta)
+ w.HookEvent = ParseHookEvent(form.WebhookForm)
+ w.IsActive = form.Active
+
+ if err := w.UpdateEvent(); err != nil {
+ ctx.ServerError("UpdateEvent", err)
+ return
+ } else if err := webhook.UpdateWebhook(w); err != nil {
+ ctx.ServerError("UpdateWebhook", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
+ ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
+}
+
+// buildTeamCityURL returns the correct REST API url for a TeamCity POST request.
+func buildTeamCityURL(meta *forms.NewTeamCityHookForm) (string, error) {
+ tcURL, err := url.Parse(meta.HostURL)
+ if err != nil {
+ return "", err
+ }
+
+ return fmt.Sprintf("%s/app/rest/vcs-root-instances/commitHookNotification?locator=vcsRoot:%s", tcURL, meta.VcsRootID), nil
+}
+
// FeishuHooksEditPost response for editing feishu hook
func FeishuHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewFeishuHookForm)
diff --git a/routers/web/web.go b/routers/web/web.go
index 60a379aef8580..dd532f029e524 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -447,6 +447,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/telegram/{id}", bindIgnErr(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/matrix/{id}", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
+ m.Post("/teamcity/{id}", bindIgnErr(forms.NewTeamCityHookForm{}), repo.TeamCityHooksEditPost)
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
@@ -462,6 +463,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/telegram/new", bindIgnErr(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/matrix/new", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
+ m.Post("/teamcity/new", bindIgnErr(forms.NewTeamCityHookForm{}), repo.TeamCityHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
@@ -561,6 +563,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/telegram/new", bindIgnErr(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/matrix/new", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
+ m.Post("/teamcity/new", bindIgnErr(forms.NewTeamCityHookForm{}), repo.TeamCityHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Group("/{id}", func() {
@@ -575,6 +578,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/telegram/{id}", bindIgnErr(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/matrix/{id}", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
+ m.Post("/teamcity/{id}", bindIgnErr(forms.NewTeamCityHookForm{}), repo.TeamCityHooksEditPost)
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
}, webhooksEnabled)
@@ -658,6 +662,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/telegram/new", bindIgnErr(forms.NewTelegramHookForm{}), repo.TelegramHooksNewPost)
m.Post("/matrix/new", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksNewPost)
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
+ m.Post("/teamcity/new", bindIgnErr(forms.NewTeamCityHookForm{}), repo.TeamCityHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
@@ -674,6 +679,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/telegram/{id}", bindIgnErr(forms.NewTelegramHookForm{}), repo.TelegramHooksEditPost)
m.Post("/matrix/{id}", bindIgnErr(forms.NewMatrixHookForm{}), repo.MatrixHooksEditPost)
m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
+ m.Post("/teamcity/{id}", bindIgnErr(forms.NewTeamCityHookForm{}), repo.TeamCityHooksEditPost)
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index b32bd3cafd9d3..2963aafdc5fcc 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -372,6 +372,19 @@ func (f *NewMSTeamsHookForm) Validate(req *http.Request, errs binding.Errors) bi
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}
+type NewTeamCityHookForm struct {
+ HostURL string `binding:"Required;ValidUrl"`
+ AuthToken string `binding:"Required"`
+ VcsRootID string `binding:"Required"`
+ WebhookForm
+}
+
+// Validate validates the fields
+func (f *NewTeamCityHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
// NewFeishuHookForm form for creating feishu hook
type NewFeishuHookForm struct {
PayloadURL string `binding:"Required;ValidUrl"`
diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go
index 5639a08f96401..6f285ec467c63 100644
--- a/services/mirror/mirror.go
+++ b/services/mirror/mirror.go
@@ -59,13 +59,11 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
handler := func(idx int, bean interface{}, limit int) error {
var item SyncRequest
- var repo *repo_model.Repository
if m, ok := bean.(*repo_model.Mirror); ok {
if m.Repo == nil {
log.Error("Disconnected mirror found: %d", m.ID)
return nil
}
- repo = m.Repo
item = SyncRequest{
Type: PullMirrorType,
RepoID: m.RepoID,
@@ -75,7 +73,6 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
log.Error("Disconnected push-mirror found: %d", m.ID)
return nil
}
- repo = m.Repo
item = SyncRequest{
Type: PushMirrorType,
RepoID: m.RepoID,
@@ -92,16 +89,17 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
default:
}
+ // Check if this request is already in the queue
+ has, err := mirrorQueue.Has(&item)
+ if err != nil {
+ return err
+ }
+ if has {
+ return nil
+ }
+
// Push to the Queue
if err := mirrorQueue.Push(&item); err != nil {
- if err == queue.ErrAlreadyInQueue {
- if item.Type == PushMirrorType {
- log.Trace("PushMirrors for %-v already queued for sync", repo)
- } else {
- log.Trace("PullMirrors for %-v already queued for sync", repo)
- }
- return nil
- }
return err
}
@@ -112,29 +110,23 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error {
return nil
}
- pullMirrorsRequested := 0
if pullLimit != 0 {
- requested = 0
if err := repo_model.MirrorsIterate(func(idx int, bean interface{}) error {
return handler(idx, bean, pullLimit)
}); err != nil && err != errLimit {
log.Error("MirrorsIterate: %v", err)
return err
}
- pullMirrorsRequested, requested = requested, 0
}
- pushMirrorsRequested := 0
if pushLimit != 0 {
- requested = 0
if err := repo_model.PushMirrorsIterate(func(idx int, bean interface{}) error {
return handler(idx, bean, pushLimit)
}); err != nil && err != errLimit {
log.Error("PushMirrorsIterate: %v", err)
return err
}
- pushMirrorsRequested, requested = requested, 0
}
- log.Trace("Finished: Update: %d pull mirrors and %d push mirrors queued", pullMirrorsRequested, pushMirrorsRequested)
+ log.Trace("Finished: Update")
return nil
}
diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go
index 88b709cb41e74..45b263f288105 100644
--- a/services/webhook/deliver.go
+++ b/services/webhook/deliver.go
@@ -116,6 +116,11 @@ func Deliver(t *webhook_model.HookTask) error {
event := t.EventType.Event()
eventType := string(t.EventType)
+
+ if t.BearerToken != "" {
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", t.BearerToken))
+ }
+
req.Header.Add("X-Gitea-Delivery", t.UUID)
req.Header.Add("X-Gitea-Event", event)
req.Header.Add("X-Gitea-Event-Type", eventType)
diff --git a/services/webhook/teamcity.go b/services/webhook/teamcity.go
new file mode 100644
index 0000000000000..79238831bdb76
--- /dev/null
+++ b/services/webhook/teamcity.go
@@ -0,0 +1,36 @@
+// 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 webhook
+
+package webhook
+
+import (
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ api "code.gitea.io/gitea/modules/structs"
+)
+
+type (
+ // TeamCityMeta contains metadata for the TeamCity WebHook
+ TeamCityMeta struct {
+ HostURL string `json:"host_url"`
+ AuthToken string `json:"auth_token"`
+ VcsRootID string `json:"vcs_root_id"`
+ }
+)
+
+// GetTeamCityPayload returns the payload as-is
+func GetTeamCityPayload(p api.Payloader, event webhook_model.HookEventType, meta string) (api.Payloader, error) {
+ // TeamCity requests API doesn't take a body on POST, so no need to alter it in any way.
+ return p, nil
+}
+
+// GetTeamCityHook returns TeamCity metadata
+func GetTeamCityHook(w *webhook_model.Webhook) *TeamCityMeta {
+ s := &TeamCityMeta{}
+ if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
+ log.Error("webhook.GetTeamCityHook(%d): %v", w.ID, err)
+ }
+ return s
+}
diff --git a/services/webhook/teamcity_test.go b/services/webhook/teamcity_test.go
new file mode 100644
index 0000000000000..987c92b778920
--- /dev/null
+++ b/services/webhook/teamcity_test.go
@@ -0,0 +1,39 @@
+// 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 webhook
+
+package webhook
+
+import (
+ "testing"
+
+ webhook_model "code.gitea.io/gitea/models/webhook"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGetTeamCityPayload(t *testing.T) {
+ t.Run("Payload isn't altered.", func(t *testing.T) {
+ p := createTestPayload()
+
+ pl, err := GetTeamCityPayload(p, webhook_model.HookEventPush, "")
+ require.NoError(t, err)
+ require.Equal(t, p, pl)
+ })
+}
+
+func TestWebhook_GetTeamCityHook(t *testing.T) {
+ t.Run("GetTeamCityHook", func(t *testing.T) {
+ w := &webhook_model.Webhook{
+ Meta: `{"host_url": "http://localhost.com", "auth_token": "testToken", "vcs_root_id" :"fooVCS"}`,
+ }
+
+ teamcityHook := GetTeamCityHook(w)
+ assert.Equal(t, *teamcityHook, TeamCityMeta{
+ HostURL: "http://localhost.com",
+ AuthToken: "testToken",
+ VcsRootID: "fooVCS",
+ })
+ })
+}
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index 607fac963452f..7497ac3ba0026 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -46,6 +46,10 @@ var webhooks = map[webhook_model.HookType]*webhook{
name: webhook_model.MSTEAMS,
payloadCreator: GetMSTeamsPayload,
},
+ webhook_model.TEAMCITY: {
+ name: webhook_model.TEAMCITY,
+ payloadCreator: GetTeamCityPayload,
+ },
webhook_model.FEISHU: {
name: webhook_model.FEISHU,
payloadCreator: GetFeishuPayload,
@@ -170,11 +174,19 @@ func prepareWebhook(w *webhook_model.Webhook, repo *repo_model.Repository, event
payloader = p
}
+ // Load any auth/bearer tokens...
+ var authToken string
+ switch w.Type {
+ case webhook_model.TEAMCITY:
+ authToken = GetTeamCityHook(w).AuthToken
+ }
+
if err = webhook_model.CreateHookTask(&webhook_model.HookTask{
- RepoID: repo.ID,
- HookID: w.ID,
- Payloader: payloader,
- EventType: event,
+ RepoID: repo.ID,
+ HookID: w.ID,
+ BearerToken: authToken,
+ Payloader: payloader,
+ EventType: event,
}); err != nil {
return fmt.Errorf("CreateHookTask: %v", err)
}
diff --git a/templates/admin/hook_new.tmpl b/templates/admin/hook_new.tmpl
index 049e54ef833cf..506d3380b9373 100644
--- a/templates/admin/hook_new.tmpl
+++ b/templates/admin/hook_new.tmpl
@@ -28,6 +28,8 @@
{{else if eq .HookType "msteams"}}
+ {{else if eq .HookType "teamcity"}}
+
{{else if eq .HookType "feishu"}}
{{else if eq .HookType "matrix"}}
@@ -47,6 +49,7 @@
{{template "repo/settings/webhook/dingtalk" .}}
{{template "repo/settings/webhook/telegram" .}}
{{template "repo/settings/webhook/msteams" .}}
+ {{template "repo/settings/webhook/teamcity" .}}
{{template "repo/settings/webhook/feishu" .}}
{{template "repo/settings/webhook/matrix" .}}
{{template "repo/settings/webhook/wechatwork" .}}
diff --git a/templates/org/settings/hook_new.tmpl b/templates/org/settings/hook_new.tmpl
index 5e8ebb51e9427..0aad537d4a447 100644
--- a/templates/org/settings/hook_new.tmpl
+++ b/templates/org/settings/hook_new.tmpl
@@ -23,6 +23,8 @@
{{else if eq .HookType "msteams"}}
+ {{else if eq .HookType "teamcity"}}
+
{{else if eq .HookType "feishu"}}
{{else if eq .HookType "matrix"}}
@@ -42,6 +44,7 @@
{{template "repo/settings/webhook/dingtalk" .}}
{{template "repo/settings/webhook/telegram" .}}
{{template "repo/settings/webhook/msteams" .}}
+ {{template "repo/settings/webhook/teamcity" .}}
{{template "repo/settings/webhook/feishu" .}}
{{template "repo/settings/webhook/matrix" .}}
{{template "repo/settings/webhook/wechatwork" .}}
diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl
index b1a3771bdba11..5e065cbe52761 100644
--- a/templates/repo/settings/webhook/base_list.tmpl
+++ b/templates/repo/settings/webhook/base_list.tmpl
@@ -25,6 +25,9 @@
{{.i18n.Tr "repo.settings.web_hook_name_msteams"}}
+
+
{{.i18n.Tr "repo.settings.web_hook_name_teamcity"}}
+
{{.i18n.Tr "repo.settings.web_hook_name_feishu_or_larksuite"}}
diff --git a/templates/repo/settings/webhook/new.tmpl b/templates/repo/settings/webhook/new.tmpl
index a438a4c71a3d7..3d8b4602a77a6 100644
--- a/templates/repo/settings/webhook/new.tmpl
+++ b/templates/repo/settings/webhook/new.tmpl
@@ -21,6 +21,8 @@
{{else if eq .HookType "msteams"}}
+ {{else if eq .HookType "teamcity"}}
+
{{else if eq .HookType "feishu"}}
{{else if eq .HookType "matrix"}}
@@ -40,6 +42,7 @@
{{template "repo/settings/webhook/dingtalk" .}}
{{template "repo/settings/webhook/telegram" .}}
{{template "repo/settings/webhook/msteams" .}}
+ {{template "repo/settings/webhook/teamcity" .}}
{{template "repo/settings/webhook/feishu" .}}
{{template "repo/settings/webhook/matrix" .}}
{{template "repo/settings/webhook/wechatwork" .}}
diff --git a/templates/repo/settings/webhook/teamcity.tmpl b/templates/repo/settings/webhook/teamcity.tmpl
new file mode 100644
index 0000000000000..3ccf6245b1a9d
--- /dev/null
+++ b/templates/repo/settings/webhook/teamcity.tmpl
@@ -0,0 +1,19 @@
+{{if eq .HookType "teamcity"}}
+
{{.i18n.Tr "repo.settings.add_web_hook_desc" "https://jetbrains.com/teamcity" (.i18n.Tr "repo.settings.web_hook_name_teamcity") | Str2html}}
+ +{{end}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 497636e78140a..0bce0752e2bae 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -13514,6 +13514,7 @@ "gitea", "gogs", "msteams", + "teamcity", "slack", "telegram", "feishu",