Newer
Older
gitbucket_jkp / src / main / scala / app / PullRequestsController.scala
package app

import util.{CollaboratorsAuthenticator, FileUtil, JGitUtil, ReferrerAuthenticator}
import util.Directory._
import service._
import org.eclipse.jgit.treewalk.CanonicalTreeParser
import util.JGitUtil.{DiffInfo, CommitInfo}
import scala.collection.mutable.ArrayBuffer
import org.eclipse.jgit.api.Git
import jp.sf.amateras.scalatra.forms._
import util.JGitUtil.DiffInfo
import scala.Some
import util.JGitUtil.CommitInfo

class PullRequestsController extends PullRequestsControllerBase
  with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService
  with ReferrerAuthenticator with CollaboratorsAuthenticator

trait PullRequestsControllerBase extends ControllerBase {
  self: ReferrerAuthenticator with RepositoryService with IssuesService with MilestonesService
    with PullRequestService with CollaboratorsAuthenticator =>

  val form = mapping(
    "title"           -> trim(label("Title"  , text(required, maxlength(100)))),
    "content"         -> trim(label("Content", optional(text()))),
    "branch"          -> trim(text(required, maxlength(100))),
    "requestUserName" -> trim(text(required, maxlength(100))),
    "requestCommitId" -> trim(text(required, maxlength(40)))
  )(PullRequestForm.apply)

  case class PullRequestForm(title: String, content: Option[String], branch: String,
                             requestUserName: String, requestCommitId: String)

  get("/:owner/:repository/pulls")(referrersOnly { repository =>
    pulls.html.list(repository)
  })

  // TODO Replace correct authenticator
  get("/:owner/:repository/pulls/compare")(collaboratorsOnly { newRepo =>
    (newRepo.repository.originUserName, newRepo.repository.originRepositoryName) match {
      case (None,_)|(_, None) => NotFound // TODO BadRequest?
      case (Some(originUserName), Some(originRepositoryName)) => {
        getRepository(originUserName, originRepositoryName, baseUrl).map { oldRepo =>
          withGit(
            getRepositoryDir(originUserName, originRepositoryName),
            getRepositoryDir(params("owner"), params("repository"))
          ){ (oldGit, newGit) =>
            val oldBranch = JGitUtil.getDefaultBranch(oldGit, oldRepo).get._2
            val newBranch = JGitUtil.getDefaultBranch(newGit, newRepo).get._2

            redirect(s"${context.path}/${newRepo.owner}/${newRepo.name}/pulls/compare/${originUserName}:${oldBranch}...${newBranch}")
          }
        } getOrElse NotFound
      }
    }
  })

  // TODO Replace correct authenticator
  get("/:owner/:repository/pulls/compare/*:*...*")(collaboratorsOnly { repository =>
    if(repository.repository.originUserName.isEmpty || repository.repository.originRepositoryName.isEmpty){
      NotFound // TODO BadRequest?
    } else {
      getRepository(
        repository.repository.originUserName.get,
        repository.repository.originRepositoryName.get, baseUrl
      ).map{ originRepository =>

        val Seq(origin, originId, forkedId) = multiParams("splat")

        withGit(
          getRepositoryDir(origin, repository.repository.originRepositoryName.get),
          getRepositoryDir(params("owner"), params("repository"))
        ){ (oldGit, newGit) =>
          val oldReader = oldGit.getRepository.newObjectReader
          val oldTreeIter = new CanonicalTreeParser
          oldTreeIter.reset(oldReader, oldGit.getRepository.resolve(s"${originId}^{tree}"))

          val newReader = newGit.getRepository.newObjectReader
          val newTreeIter = new CanonicalTreeParser
          newTreeIter.reset(newReader, newGit.getRepository.resolve(s"${forkedId}^{tree}"))

          import scala.collection.JavaConverters._
          import util.Implicits._

          val oldId = oldGit.getRepository.resolve(originId)
          val newId = newGit.getRepository.resolve(forkedId)
          val i = newGit.log.addRange(oldId, newId).call.iterator

          val commits = new ArrayBuffer[CommitInfo]
          while(i.hasNext){
            val revCommit = i.next
            commits += new CommitInfo(revCommit)
          }

          val diffs = newGit.diff.setOldTree(oldTreeIter).setNewTree(newTreeIter).call.asScala.map { diff =>
            if(FileUtil.isImage(diff.getOldPath) || FileUtil.isImage(diff.getNewPath)){
              DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, None, None)
            } else {
              DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath,
                JGitUtil.getContent(oldGit, diff.getOldId.toObjectId, false).filter(FileUtil.isText).map(new String(_, "UTF-8")),
                JGitUtil.getContent(newGit, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(new String(_, "UTF-8")))
            }
          }

          pulls.html.compare(commits.toList.splitWith{ (commit1, commit2) =>
            view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
          }, diffs.toList, origin, originId, forkedId, newId.getName, repository, originRepository)
        }
      } getOrElse NotFound
    }
  })

  post("/:owner/:repository/pulls/new", form)(referrersOnly { (form, repository) =>
    val loginUserName = context.loginAccount.get.userName

    val issueId = createIssue(
      repository.owner,
      repository.name,
      loginUserName,
      form.title,
      form.content,
      None, None)

    createPullRequest(
      repository.owner,
      repository.name,
      issueId,
      form.branch,
      form.requestUserName,
      repository.name,
      form.requestCommitId)

    redirect(s"/${repository.owner}/${repository.name}/pulls/${issueId}")
  })

  get("/:owner/:repository/pulls/:id")(referrersOnly { repository =>
    val owner   = repository.owner
    val name    = repository.name
    val issueId = params("id")

    getIssue(owner, name, issueId) map {
      issues.html.issue(
        _,
        getComments(owner, name, issueId.toInt),
        getIssueLabels(owner, name, issueId.toInt),
        (getCollaborators(owner, name) :+ owner).sorted,
        getMilestones(owner, name),
        Nil,
        hasWritePermission(owner, name, context.loginAccount),
        repository)
    } getOrElse NotFound
  })

  post("/:owner/:repository/pulls/:id/merge")(collaboratorsOnly { repository =>
    // TODO Not implemented yet.
  })

  private def withGit[T](oldDir: java.io.File, newDir: java.io.File)(action: (Git, Git) => T): T = {
    val oldGit = Git.open(oldDir)
    val newGit = Git.open(newDir)
    try {
      action(oldGit, newGit)
    } finally {
      oldGit.getRepository.close
      newGit.getRepository.close
    }
  }

}