diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index 49cc97d..b32deb0 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -1,8 +1,10 @@ package gitbucket.core.controller +import gitbucket.core.model.{WebHook, WebHookEvent} import gitbucket.core.service.RepositoryService.RepositoryInfo +import gitbucket.core.service.WebHookService.WebHookGollumPayload import gitbucket.core.wiki.html -import gitbucket.core.service.{AccountService, ActivityService, RepositoryService, WikiService} +import gitbucket.core.service._ import gitbucket.core.util._ import gitbucket.core.util.StringUtil._ import gitbucket.core.util.SyntaxSugars._ @@ -13,11 +15,12 @@ import org.scalatra.i18n.Messages class WikiController extends WikiControllerBase - with WikiService with RepositoryService with AccountService with ActivityService + with WikiService with RepositoryService with AccountService with ActivityService with WebHookService with ReadableUsersAuthenticator with ReferrerAuthenticator trait WikiControllerBase extends ControllerBase { - self: WikiService with RepositoryService with ActivityService with ReadableUsersAuthenticator with ReferrerAuthenticator => + self: WikiService with RepositoryService with AccountService with ActivityService with WebHookService + with ReadableUsersAuthenticator with ReferrerAuthenticator => case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String) @@ -136,6 +139,11 @@ ).map { commitId => updateLastActivityDate(repository.owner, repository.name) recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId) + callWebHookOf(repository.owner, repository.name, WebHook.Gollum){ + getAccountByUserName(repository.owner).map { repositoryUser => + WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount) + } + } } if(notReservedPageName(form.pageName)) { redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") @@ -155,11 +163,24 @@ post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) => if(isEditable(repository)){ defining(context.loginAccount.get){ loginAccount => - saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName, - form.content, loginAccount, form.message.getOrElse(""), None) - - updateLastActivityDate(repository.owner, repository.name) - recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName) + saveWikiPage( + repository.owner, + repository.name, + form.currentPageName, + form.pageName, + form.content, + loginAccount, + form.message.getOrElse(""), + None + ).map { commitId => + updateLastActivityDate(repository.owner, repository.name) + recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName) + callWebHookOf(repository.owner, repository.name, WebHook.Gollum){ + getAccountByUserName(repository.owner).map { repositoryUser => + WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount) + } + } + } if(notReservedPageName(form.pageName)) { redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala index 2f060de..35fe847 100644 --- a/src/main/scala/gitbucket/core/service/WebHookService.scala +++ b/src/main/scala/gitbucket/core/service/WebHookService.scala @@ -8,7 +8,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._ import org.apache.http.client.utils.URLEncodedUtils import gitbucket.core.util.JGitUtil.CommitInfo -import gitbucket.core.util.RepositoryName +import gitbucket.core.util.{RepositoryName, StringUtil} import gitbucket.core.service.RepositoryService.RepositoryInfo import org.apache.http.NameValuePair import org.apache.http.client.entity.UrlEncodedFormEntity @@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory import scala.concurrent._ -import scala.util.{Success, Failure} +import scala.util.{Failure, Success} import org.apache.http.HttpRequest import org.apache.http.HttpResponse import gitbucket.core.model.WebHookContentType @@ -42,7 +42,7 @@ def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] = WebHooks.filter(_.byRepository(owner, repository)) .join(WebHookEvents).on { (wh, whe) => whe.byWebHook(wh) } - .filter { case (wh, whe) => whe.event === event.bind} + .filter { case (wh, whe) => whe.event === event.bind } .map{ case (wh, whe) => wh } .list.distinct @@ -160,7 +160,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 = { + (implicit s: Session, context: JsonFormat.Context): Unit = { callWebHookOf(repository.owner, repository.name, WebHook.Issues){ val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender)) for{ @@ -178,7 +178,7 @@ } def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account) - (implicit s: Session, context:JsonFormat.Context): Unit = { + (implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ callWebHookOf(repository.owner, repository.name, WebHook.PullRequest){ for{ @@ -224,7 +224,7 @@ }).list.groupBy(_._1).mapValues(_.map(_._2)) def callPullRequestWebHookByRequestBranch(action: String, requestRepository: RepositoryService.RepositoryInfo, requestBranch: String, baseUrl: String, sender: Account) - (implicit s: Session, context:JsonFormat.Context): Unit = { + (implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ for{ ((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch) @@ -246,12 +246,13 @@ callWebHook(WebHook.PullRequest, webHooks, payload) } } + } trait WebHookPullRequestReviewCommentService extends WebHookService { self: AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService => def callPullRequestReviewCommentWebHook(action: String, comment: CommitComment, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account) - (implicit s: Session, context:JsonFormat.Context): Unit = { + (implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment){ for{ @@ -285,7 +286,7 @@ import WebHookService._ def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: Account) - (implicit s: Session, context:JsonFormat.Context): Unit = { + (implicit s: Session, c: JsonFormat.Context): Unit = { callWebHookOf(repository.owner, repository.name, WebHook.IssueComment){ for{ issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString()) @@ -470,4 +471,53 @@ sender = senderPayload) } } + + // https://developer.github.com/v3/activity/events/types/#gollumevent + case class WebHookGollumPayload( + pages: Seq[WebHookGollumPagePayload], + repository: ApiRepository, + sender: ApiUser + ) extends WebHookPayload + + case class WebHookGollumPagePayload( + page_name: String, + title: String, + summary: Option[String] = None, + action: String, // created or edited + sha: String, // SHA of the latest commit + html_url: ApiPath + ) + + object WebHookGollumPayload { + def apply( + action: String, + pageName: String, + sha: String, + repository: RepositoryInfo, + repositoryUser: Account, + sender: Account + ): WebHookGollumPayload = apply(Seq((action, pageName, sha)), repository, repositoryUser, sender) + + def apply( + pages: Seq[(String, String, String)], + repository: RepositoryInfo, + repositoryUser: Account, + sender: Account + ): WebHookGollumPayload = { + WebHookGollumPayload( + pages = pages.map { case (action, pageName, sha) => + WebHookGollumPagePayload( + action = action, + page_name = pageName, + title = pageName, + sha = sha, + html_url = ApiPath(s"/${RepositoryName(repository).fullName}/wiki/${StringUtil.urlDecode(pageName)}") + ) + }, + repository = ApiRepository(repository, repositoryUser), + sender = ApiUser(sender) + ) + } + } + } diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index c4ffe41..a6af98b 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -1,6 +1,7 @@ package gitbucket.core.servlet import java.io.File +import java.util import java.util.Date import gitbucket.core.api @@ -22,6 +23,7 @@ import javax.servlet.ServletConfig import javax.servlet.http.{HttpServletRequest, HttpServletResponse} +import org.eclipse.jgit.diff.DiffEntry.ChangeType import org.json4s.jackson.Serialization._ @@ -161,6 +163,12 @@ receivePack.setPostReceiveHook(hook) } } + + if(repository.endsWith(".wiki")){ + defining(request) { implicit r => + receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.replaceFirst("\\.wiki$", ""), pusher, baseUrl)) + } + } } } @@ -170,7 +178,7 @@ import scala.collection.JavaConverters._ -class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)/*(implicit session: Session)*/ +class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String) extends PostReceiveHook with PreReceiveHook with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService with WebHookPullRequestService with CommitsService { @@ -185,9 +193,10 @@ // call pre-commit hook PluginRegistry().getReceiveHooks .flatMap(_.preReceive(owner, repository, receivePack, command, pusher)) - .headOption.foreach { error => - command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error) - } + .headOption + .foreach { error => + command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error) + } } using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => existIds = JGitUtil.getAllCommitIds(git) @@ -285,8 +294,10 @@ // call web hook callWebHookOf(owner, repository, WebHook.Push) { - for (pusherAccount <- getAccountByUserName(pusher); - ownerAccount <- getAccountByUserName(owner)) yield { + for { + pusherAccount <- getAccountByUserName(pusher) + ownerAccount <- getAccountByUserName(owner) + } yield { WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount, newId = command.getNewId(), oldId = command.getOldId()) } @@ -309,6 +320,67 @@ } +class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: String) + extends PostReceiveHook with WebHookService with AccountService with RepositoryService { + + private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook]) + + override def onPostReceive(receivePack: ReceivePack, commands: util.Collection[ReceiveCommand]): Unit = { + Database() withTransaction { implicit session => + try { + commands.asScala.headOption.foreach { command => + implicit val apiContext = api.JsonFormat.Context(baseUrl) + val refName = command.getRefName.split("/") + val commitIds = if (refName(1) == "tags") { + None + } else { + command.getType match { + case ReceiveCommand.Type.DELETE => None + case _ => Some((command.getOldId.getName, command.getNewId.name)) + } + } + + commitIds.map { case (oldCommitId, newCommitId) => + val commits = using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + JGitUtil.getCommitLog(git, oldCommitId, newCommitId).flatMap { commit => + val diffs = JGitUtil.getDiffs(git, commit.id, false) + diffs._1.collect { case diff if diff.newPath.toLowerCase.endsWith(".md") => + val action = if(diff.changeType == ChangeType.ADD) "created" else "edited" + val fileName = diff.newPath + println(action + " - " + fileName + " - " + commit.id) + (action, fileName, commit.id) + } + } + } + + val pages = commits + .groupBy { case (action, fileName, commitId) => fileName } + .map { case (fileName, commits) => + (commits.head._1, fileName, commits.last._3) + } + + callWebHookOf(owner, repository, WebHook.Gollum) { + for { + pusherAccount <- getAccountByUserName(pusher) + repositoryUser <- getAccountByUserName(owner) + repositoryInfo <- getRepository(owner, repository) + } yield { + WebHookGollumPayload(pages.toSeq, repositoryInfo, repositoryUser, pusherAccount) + } + } + } + } + } catch { + case ex: Exception => { + logger.error(ex.toString, ex) + throw ex + } + } + } + } + +} + object GitLfs { case class BatchRequest( diff --git a/src/main/scala/gitbucket/core/util/RepositoryName.scala b/src/main/scala/gitbucket/core/util/RepositoryName.scala index e7d293d..9f0825b 100644 --- a/src/main/scala/gitbucket/core/util/RepositoryName.scala +++ b/src/main/scala/gitbucket/core/util/RepositoryName.scala @@ -1,7 +1,7 @@ package gitbucket.core.util // TODO Move to gitbucket.core.api package? -case class RepositoryName(owner:String, name:String){ +case class RepositoryName(owner: String, name: String){ val fullName = s"${owner}/${name}" } diff --git a/src/main/twirl/gitbucket/core/settings/edithooks.scala.html b/src/main/twirl/gitbucket/core/settings/edithooks.scala.html index 812b929..35c4172 100644 --- a/src/main/twirl/gitbucket/core/settings/edithooks.scala.html +++ b/src/main/twirl/gitbucket/core/settings/edithooks.scala.html @@ -55,7 +55,9 @@ + --> +