diff --git a/src/main/resources/update/3_9.sql b/src/main/resources/update/3_9.sql new file mode 100644 index 0000000..546a46d --- /dev/null +++ b/src/main/resources/update/3_9.sql @@ -0,0 +1,22 @@ +DROP TABLE IF EXISTS WEB_HOOK_EVENT; + +CREATE TABLE WEB_HOOK_EVENT( + USER_NAME VARCHAR(100) NOT NULL, + REPOSITORY_NAME VARCHAR(100) NOT NULL, + URL VARCHAR(200) NOT NULL, + EVENT VARCHAR(30) NOT NULL +); + +ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL, EVENT); +ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, URL) REFERENCES WEB_HOOK (USER_NAME, REPOSITORY_NAME, URL) + ON DELETE CASCADE ON UPDATE CASCADE; + +CREATE TEMPORARY TABLE TMP_EVENTS (EVENT VARCHAR(30)); + +INSERT INTO TMP_EVENTS VALUES ('push'),('issue_comment'),('issues'),('pull_request'); + +INSERT INTO WEB_HOOK_EVENT (USER_NAME, REPOSITORY_NAME, URL, EVENT) + SELECT USER_NAME, REPOSITORY_NAME, URL, EVENT + FROM WEB_HOOK, TMP_EVENTS; + +DROP TABLE TMP_EVENTS; diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index cf5dfe8..916f6d4 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -14,6 +14,7 @@ import org.scalatra.i18n.Messages import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.Constants +import scala.util.{Success, Failure} import org.eclipse.jgit.lib.ObjectId @@ -43,10 +44,11 @@ )(CollaboratorForm.apply) // for web hook url addition - case class WebHookForm(url: String) + case class WebHookForm(url: String, events: Set[WebHook.Event]) - val webHookForm = mapping( - "url" -> trim(label("url", text(required, webHook))) + def webHookForm(update:Boolean) = mapping( + "url" -> trim(label("url", text(required, webHook(update)))), + "events" -> webhookEvents )(WebHookForm.apply) // for transfer ownership @@ -139,14 +141,23 @@ * Display the web hook page. */ get("/:owner/:repository/settings/hooks")(ownerOnly { repository => - html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info")) + html.hooks(getWebHooks(repository.owner, repository.name), repository, flash.get("info")) + }) + + /** + * Display the web hook edit page. + */ + get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository => + val webhook = WebHook(repository.owner, repository.name, "") + html.editHooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true) }) /** * Add the web hook URL. */ - post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) => - addWebHookURL(repository.owner, repository.name, form.url) + post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) => + addWebHook(repository.owner, repository.name, form.url, form.events) + flash += "info" -> s"Webhook ${form.url} created" redirect(s"/${repository.owner}/${repository.name}/settings/hooks") }) @@ -154,32 +165,77 @@ * Delete the web hook URL. */ get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository => - deleteWebHookURL(repository.owner, repository.name, params("url")) + deleteWebHook(repository.owner, repository.name, params("url")) + flash += "info" -> s"Webhook ${params("url")} deleted" redirect(s"/${repository.owner}/${repository.name}/settings/hooks") }) /** * Send the test request to registered web hook URLs. */ - post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) => + ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => import scala.collection.JavaConverters._ - val commits = if(repository.commitCount == 0) List.empty else git.log - .add(git.getRepository.resolve(repository.repository.defaultBranch)) - .setMaxCount(4) - .call.iterator.asScala.map(new CommitInfo(_)).toList + import scala.concurrent.duration._ + import scala.concurrent._ + import scala.util.control.NonFatal + import org.apache.http.util.EntityUtils + import scala.concurrent.ExecutionContext.Implicits.global - getAccountByUserName(repository.owner).foreach { ownerAccount => - callWebHook("push", - List(WebHook(repository.owner, repository.name, form.url)), - WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, (if(commits.isEmpty){Nil}else{commits.tail}), ownerAccount, - oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()), - newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())) - ) + val url = params("url") + val dummyPayload = { + val ownerAccount = getAccountByUserName(repository.owner).get + val commits = if(repository.commitCount == 0) List.empty else git.log + .add(git.getRepository.resolve(repository.repository.defaultBranch)) + .setMaxCount(4) + .call.iterator.asScala.map(new CommitInfo(_)).toList + val pushedCommit = commits.drop(1) + WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, pushedCommit, ownerAccount, + oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()), + newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())) } - flash += "url" -> form.url - flash += "info" -> "Test payload deployed!" + val dummyWebHookInfo = WebHook(repository.owner, repository.name, url) + + val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head + + def headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map{ h => Array(h.getName, h.getValue) } + val toErrorMap:PartialFunction[Throwable, Map[String,String]] = { + case e:java.net.UnknownHostException => Map("error"-> ("Unknown host "+ e.getMessage)) + case e:java.lang.IllegalArgumentException => Map("error"-> ("invalid url")) + case e:org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url")) + case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage)) + } + contentType = formats("json") + var result = Map( + "url" -> url, + "request" -> Await.result(reqFuture.map(req => Map( + "headers" -> headers(req.getAllHeaders), + "payload" -> json + )).recover(toErrorMap), 20 seconds), + "responce" -> Await.result(resFuture.map(res => Map( + "status" -> res.getStatusLine(), + "body" -> EntityUtils.toString(res.getEntity()), + "headers" -> headers(res.getAllHeaders()) + )).recover(toErrorMap), 20 seconds)) + org.json4s.jackson.Serialization.write(result) } + }) + + /** + * Display the web hook edit page. + */ + get("/:owner/:repository/settings/hooks/edit/:url")(ownerOnly { repository => + getWebHook(repository.owner, repository.name, params("url")).map{ case (webhook, events) => + html.editHooks(webhook, events, repository, flash.get("info"), false) + } getOrElse NotFound + }) + + /** + * Update web hook settings. + */ + post("/:owner/:repository/settings/hooks/edit/:url", webHookForm(true))(ownerOnly { (form, repository) => + updateWebHook(repository.owner, repository.name, form.url, form.events) + flash += "info" -> s"webhook ${form.url} updated" redirect(s"/${repository.owner}/${repository.name}/settings/hooks") }) @@ -229,9 +285,28 @@ /** * Provides duplication check for web hook url. */ - private def webHook: Constraint = new Constraint(){ + private def webHook(needExists: Boolean): Constraint = new Constraint(){ override def validate(name: String, value: String, messages: Messages): Option[String] = - getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.") + if(getWebHook(params("owner"), params("repository"), value).isDefined != needExists){ + Some(if(needExists){ + "URL had not been registered yet." + }else{ + "URL had been registered already." + }) + } else { + None + } + } + + private def webhookEvents = new ValueType[Set[WebHook.Event]]{ + def convert(name: String, params: Map[String, String], messages: Messages): Set[WebHook.Event] = WebHook.Event.values.flatMap{ t => + params.get(name+"."+t.name).map(_ => t) + }.toSet + def validate(name: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){ + Seq(name -> messages("error.required").format(name)) + }else{ + Nil + } } /** diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 989af69..c6bcad7 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -11,7 +11,7 @@ import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.Directory._ -import gitbucket.core.model.{Account, CommitState} +import gitbucket.core.model.{Account, CommitState, WebHook} import gitbucket.core.service.CommitStatusService import gitbucket.core.service.WebHookService._ import gitbucket.core.view @@ -667,7 +667,7 @@ // call web hook callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount) val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) - callWebHookOf(repository.owner, repository.name, "push") { + callWebHookOf(repository.owner, repository.name, WebHook.Push) { getAccountByUserName(repository.owner).map{ ownerAccount => WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount, oldId = headTip, newId = commitId) diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala index 7ba5584..e173630 100644 --- a/src/main/scala/gitbucket/core/model/Profile.scala +++ b/src/main/scala/gitbucket/core/model/Profile.scala @@ -48,6 +48,7 @@ with RepositoryComponent with SshKeyComponent with WebHookComponent + with WebHookEventComponent with PluginComponent object Profile extends CoreProfile diff --git a/src/main/scala/gitbucket/core/model/WebHook.scala b/src/main/scala/gitbucket/core/model/WebHook.scala index b6897da..3889c00 100644 --- a/src/main/scala/gitbucket/core/model/WebHook.scala +++ b/src/main/scala/gitbucket/core/model/WebHook.scala @@ -7,7 +7,7 @@ class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate { val url = column[String]("URL") - def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply) + def * = (userName, repositoryName, url) <> ((WebHook.apply _).tupled, WebHook.unapply) def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) } @@ -18,3 +18,32 @@ repositoryName: String, url: String ) + +object WebHook { + sealed class Event(var name: String) + case object CommitComment extends Event("commit_comment") + case object Create extends Event("create") + case object Delete extends Event("delete") + case object Deployment extends Event("deployment") + case object DeploymentStatus extends Event("deployment_status") + case object Fork extends Event("fork") + case object Gollum extends Event("gollum") + case object IssueComment extends Event("issue_comment") + case object Issues extends Event("issues") + case object Member extends Event("member") + case object PageBuild extends Event("page_build") + case object Public extends Event("public") + case object PullRequest extends Event("pull_request") + case object PullRequestReviewComment extends Event("pull_request_review_comment") + case object Push extends Event("push") + case object Release extends Event("release") + case object Status extends Event("status") + case object TeamAdd extends Event("team_add") + case object Watch extends Event("watch") + object Event{ + val values = List(CommitComment,Create,Delete,Deployment,DeploymentStatus,Fork,Gollum,IssueComment,Issues,Member,PageBuild,Public,PullRequest,PullRequestReviewComment,Push,Release,Status,TeamAdd,Watch) + private val map:Map[String,Event] = values.map(e => e.name -> e).toMap + def valueOf(name: String): Event = map(name) + def valueOpt(name: String): Option[Event] = map.get(name) + } +} diff --git a/src/main/scala/gitbucket/core/model/WebHookEvent.scala b/src/main/scala/gitbucket/core/model/WebHookEvent.scala new file mode 100644 index 0000000..cc960e7 --- /dev/null +++ b/src/main/scala/gitbucket/core/model/WebHookEvent.scala @@ -0,0 +1,30 @@ +package gitbucket.core.model + +trait WebHookEventComponent extends TemplateComponent { self: Profile => + import profile.simple._ + import gitbucket.core.model.Profile.WebHooks + + lazy val WebHookEvents = TableQuery[WebHookEvents] + + implicit val typedType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_)) + + class WebHookEvents(tag: Tag) extends Table[WebHookEvent](tag, "WEB_HOOK_EVENT") with BasicTemplate { + val url = column[String]("URL") + val event = column[WebHook.Event]("EVENT") + def * = (userName, repositoryName, url, event) <> ((WebHookEvent.apply _).tupled, WebHookEvent.unapply) + + def byWebHook(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) + def byWebHook(owner: Column[String], repository: Column[String], url: Column[String]) = + byRepository(userName, repositoryName) && (this.url === url) + def byWebHook(webhook: WebHooks) = + byRepository(webhook.userName, webhook.repositoryName) && (this.url === webhook.url) + def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = byWebHook(owner, repository, url) && (this.event === event.bind) + } +} + +case class WebHookEvent( + userName: String, + repositoryName: String, + url: String, + event: WebHook.Event +) diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala index da5cdc8..c168395 100644 --- a/src/main/scala/gitbucket/core/service/WebHookService.scala +++ b/src/main/scala/gitbucket/core/service/WebHookService.scala @@ -1,7 +1,7 @@ package gitbucket.core.service import gitbucket.core.api._ -import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment} +import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent} import gitbucket.core.model.Profile._ import profile.simple._ import gitbucket.core.util.JGitUtil.CommitInfo @@ -14,6 +14,9 @@ import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.ObjectId import org.slf4j.LoggerFactory +import scala.concurrent._ +import org.apache.http.HttpRequest +import org.apache.http.HttpResponse trait WebHookService { @@ -21,46 +24,91 @@ private val logger = LoggerFactory.getLogger(classOf[WebHookService]) - def getWebHookURLs(owner: String, repository: String)(implicit s: Session): List[WebHook] = - WebHooks.filter(_.byRepository(owner, repository)).sortBy(_.url).list + /** get All WebHook informations of repository */ + def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(WebHook, Set[WebHook.Event])] = + WebHooks.filter(_.byRepository(owner, repository)) + .innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) } + .map{ case (w,t) => w -> t.event } + .list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url) - def addWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit = + /** get All WebHook informations of repository event */ + def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] = + WebHookEvents.filter(t => t.byRepository(owner, repository) && t.event === event.bind) + .list.map(t => WebHook(t.userName, t.repositoryName, t.url)) + + /** get All WebHook information from repository to url */ + def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(WebHook, Set[WebHook.Event])] = + WebHooks + .filter(_.byPrimaryKey(owner, repository, url)) + .innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) } + .map{ case (w,t) => w -> t.event } + .list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption + + def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = { WebHooks insert WebHook(owner, repository, url) - - def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit = - WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete - - def callWebHookOf(owner: String, repository: String, eventName: String)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: JsonFormat.Context): Unit = { - val webHookURLs = getWebHookURLs(owner, repository) - if(webHookURLs.nonEmpty){ - makePayload.map(callWebHook(eventName, webHookURLs, _)) + events.toSet.map{ event: WebHook.Event => + WebHookEvents insert WebHookEvent(owner, repository, url, event) } } - def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: JsonFormat.Context): Unit = { - import org.apache.http.client.methods.HttpPost + def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = { + WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete + events.toSet.map{ event: WebHook.Event => + WebHookEvents insert WebHookEvent(owner, repository, url, event) + } + } + + def deleteWebHook(owner: String, repository: String, url :String)(implicit s: Session): Unit = + WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete + + def callWebHookOf(owner: String, repository: String, event: WebHook.Event)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: JsonFormat.Context): Unit = { + val webHooks = getWebHooksByEvent(owner, repository, event) + if(webHooks.nonEmpty){ + makePayload.map(callWebHook(event, webHooks, _)) + } + } + + def callWebHook(event: WebHook.Event, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = { import org.apache.http.impl.client.HttpClientBuilder - import scala.concurrent._ import ExecutionContext.Implicits.global + import org.apache.http.protocol.HttpContext + import org.apache.http.client.methods.HttpPost if(webHookURLs.nonEmpty){ val json = JsonFormat(payload) - val httpClient = HttpClientBuilder.create.build - webHookURLs.foreach { webHookUrl => + webHookURLs.map { webHookUrl => + val reqPromise = Promise[HttpRequest] val f = Future { - logger.debug(s"start web hook invocation for ${webHookUrl}") - val httpPost = new HttpPost(webHookUrl.url) - httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded") - httpPost.addHeader("X-Github-Event", eventName) + val itcp = new org.apache.http.HttpRequestInterceptor{ + def process(res: HttpRequest, ctx: HttpContext): Unit = { + reqPromise.success(res) + } + } + try{ + val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build + logger.debug(s"start web hook invocation for ${webHookUrl.url}") + val httpPost = new HttpPost(webHookUrl.url) + httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded") + httpPost.addHeader("X-Github-Event", event.name) + httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString) - val params: java.util.List[NameValuePair] = new java.util.ArrayList() - params.add(new BasicNameValuePair("payload", json)) - httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")) + val params: java.util.List[NameValuePair] = new java.util.ArrayList() + params.add(new BasicNameValuePair("payload", json)) + httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")) - httpClient.execute(httpPost) - httpPost.releaseConnection() - logger.debug(s"end web hook invocation for ${webHookUrl}") + val res = httpClient.execute(httpPost) + httpPost.releaseConnection() + logger.debug(s"end web hook invocation for ${webHookUrl}") + res + }catch{ + case e:Throwable => { + if(!reqPromise.isCompleted){ + reqPromise.failure(e) + } + throw e + } + } } f.onSuccess { case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}") @@ -68,9 +116,12 @@ f.onFailure { case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t) } + (webHookUrl, json, reqPromise.future, f) } + } else { + Nil } - logger.debug("end callWebHook") + // logger.debug("end callWebHook") } } @@ -81,7 +132,7 @@ import WebHookService._ // https://developer.github.com/v3/activity/events/types/#issuesevent def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = { - callWebHookOf(repository.owner, repository.name, "issues"){ + callWebHookOf(repository.owner, repository.name, WebHook.Issues){ val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender)) for{ repoOwner <- users.get(repository.owner) @@ -99,7 +150,7 @@ def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = { import WebHookService._ - callWebHookOf(repository.owner, repository.name, "pull_request"){ + callWebHookOf(repository.owner, repository.name, WebHook.PullRequest){ for{ (issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender)) @@ -135,6 +186,7 @@ ru <- Accounts if ru.userName === pr.requestUserName iu <- Accounts if iu.userName === is.openedUserName wh <- WebHooks if wh.byRepository(is.userName , is.repositoryName) + wht <- WebHookEvents if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byWebHook(wh) } yield { ((is, iu, pr, bu, ru), wh) }).list.groupBy(_._1).mapValues(_.map(_._2)) @@ -155,7 +207,7 @@ baseRepository = baseRepo, baseOwner = baseOwner, sender = sender) - callWebHook("pull_request", webHooks, payload) + callWebHook(WebHook.PullRequest, webHooks, payload) } } } @@ -165,7 +217,7 @@ import WebHookService._ def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = { - callWebHookOf(repository.owner, repository.name, "issue_comment"){ + callWebHookOf(repository.owner, repository.name, WebHook.IssueComment){ for{ issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString()) users = getAccountsByUserNames(Set(issue.openedUserName, repository.owner, issueComment.commentedUserName), Set(sender)) diff --git a/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala b/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala index d487261..77ee78e 100644 --- a/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala +++ b/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala @@ -21,6 +21,7 @@ * The history of versions. A head of this sequence is the current BitBucket version. */ val versions = Seq( + new Version(3, 9), new Version(3, 8), new Version(3, 7) with SystemSettingsService { override def update(conn: Connection, cl: ClassLoader): Unit = { diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 0c12872..a2866f1 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -3,7 +3,7 @@ import java.io.File import gitbucket.core.api -import gitbucket.core.model.Session +import gitbucket.core.model.{Session, WebHook} import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry} import gitbucket.core.service.IssuesService.IssueSearchCondition import gitbucket.core.service.WebHookService._ @@ -200,7 +200,7 @@ } // call web hook - callWebHookOf(owner, repository, "push"){ + callWebHookOf(owner, repository, WebHook.Push){ for(pusherAccount <- getAccountByUserName(pusher); ownerAccount <- getAccountByUserName(owner)) yield { WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount, diff --git a/src/main/twirl/gitbucket/core/settings/editHooks.scala.html b/src/main/twirl/gitbucket/core/settings/editHooks.scala.html new file mode 100644 index 0000000..85f453f --- /dev/null +++ b/src/main/twirl/gitbucket/core/settings/editHooks.scala.html @@ -0,0 +1,165 @@ +@(webHook: gitbucket.core.model.WebHook, + events: Set[gitbucket.core.model.WebHook.Event], + repository: gitbucket.core.service.RepositoryService.RepositoryInfo, + info: Option[Any], + create: Boolean)(implicit context: gitbucket.core.controller.Context) +@import context._ +@import gitbucket.core.view.helpers._ +@import gitbucket.core.model.WebHook._ +@check(name: String, event: Event)={ +name="@(name).@event.name" value="on" @if(events(event)){checked} +} +@html.main("Settings", Some(repository)){ + @html.menu("settings", repository){ + @menu("hooks", repository){ + @helper.html.information(info) + +
+ Webhooks allow external services to be notified when certain events happen within your repository. + When the specified events happen, we’ll send a POST request to each of the URLs you provide. + Learn more in GitBucket Wiki Webhook Page. +
+ ++ + @webHook.url + + (@events.map(_.name).mkString(", ")) + | + + |