Files
GitBucket/src/main/scala/app/PullRequestsController.scala

256 lines
10 KiB
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
import org.eclipse.jgit.transport.RefSpec
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.lib.PersonIdent
class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with ActivityService
with ReferrerAuthenticator with CollaboratorsAuthenticator
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with IssuesService with MilestonesService with ActivityService with PullRequestService
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
val pullRequestForm = 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))),
"requestBranch" -> trim(text(required, maxlength(100)))
)(PullRequestForm.apply)
val mergeForm = mapping(
"message" -> trim(label("Message", text(required)))
)(MergeForm.apply)
case class PullRequestForm(title: String, content: Option[String], branch: String,
requestUserName: String, requestBranch: String)
case class MergeForm(message: String)
get("/:owner/:repository/pulls")(referrersOnly { repository =>
pulls.html.list(repository)
})
get("/:owner/:repository/pulls/:id")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
val issueId = params("id").toInt
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
pulls.html.pullreq(
issue, pullreq,
getComments(owner, name, issueId.toInt),
(getCollaborators(owner, name) :+ owner).sorted,
getMilestones(owner, name),
hasWritePermission(owner, name, context.loginAccount),
repository)
} getOrElse NotFound
})
get("/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
val issueId = params("id").toInt
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
pulls.html.commits(
issue, pullreq,
getCompareInfo(owner, name, pullreq.branch, pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)._1,
hasWritePermission(owner, name, context.loginAccount),
repository)
} getOrElse NotFound
})
get("/:owner/:repository/pulls/:id/files")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
val issueId = params("id").toInt
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
JGitUtil.withGit(getRepositoryDir(owner, name)){ git =>
val newId = git.getRepository.resolve(pullreq.requestBranch)
pulls.html.files(
issue, pullreq,
getCompareInfo(owner, name, pullreq.branch, pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch)._2,
newId.getName,
hasWritePermission(owner, name, context.loginAccount),
repository)
}
} getOrElse NotFound
})
post("/:owner/:repository/pulls/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
val issueId = params("id").toInt
getPullRequest(repository.owner, repository.name, issueId).map { case (issue, pullreq) =>
val remote = getRepositoryDir(repository.owner, repository.name)
val tmpdir = new java.io.File(getTemporaryDir(repository.owner, repository.name), s"merge-${issueId}")
val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).call
try {
// TODO merge and close issue
val loginAccount = context.loginAccount.get
recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, form.message)
git.checkout.setName(pullreq.branch).call
git.fetch
.setRemote(getRepositoryDir(pullreq.requestUserName, pullreq.requestRepositoryName).toURI.toString)
.setRefSpecs(new RefSpec(s"refs/heads/${pullreq.branch}:refs/heads/${pullreq.requestBranch}")).call
git.merge.include(git.getRepository.resolve("FETCH_HEAD")).setCommit(false).call
git.commit
.setCommitter(new PersonIdent(loginAccount.userName, loginAccount.mailAddress))
.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n"
+ form.message).call
git.push.call
} finally {
git.getRepository.close
FileUtils.deleteDirectory(tmpdir)
}
} getOrElse NotFound
})
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
}
}
})
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")
val userName = params("owner")
val repositoryName = params("repository")
JGitUtil.withGit(getRepositoryDir(userName, repositoryName)){ git =>
val newId = git.getRepository.resolve(forkedId)
val pullreq = getCompareInfo(
origin, repository.repository.originRepositoryName.get, originId,
params("owner"), params("repository"), forkedId)
pulls.html.compare(pullreq._1, pullreq._2, origin, originId, forkedId, newId.getName, repository, originRepository)
}
} getOrElse NotFound
}
})
post("/:owner/:repository/pulls/new", pullRequestForm)(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.requestBranch)
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
redirect(s"/${repository.owner}/${repository.name}/pulls/${issueId}")
})
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
}
}
/**
* Returns the commits and diffs between specified repository and revision.
*/
private def getCompareInfo(userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestBranch: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = {
withGit(
getRepositoryDir(userName, repositoryName),
getRepositoryDir(requestUserName, requestRepositoryName)
){ (oldGit, newGit) =>
val oldReader = oldGit.getRepository.newObjectReader
val oldTreeIter = new CanonicalTreeParser
oldTreeIter.reset(oldReader, oldGit.getRepository.resolve(s"${branch}^{tree}"))
val newReader = newGit.getRepository.newObjectReader
val newTreeIter = new CanonicalTreeParser
newTreeIter.reset(newReader, newGit.getRepository.resolve(s"${requestBranch}^{tree}"))
import scala.collection.JavaConverters._
import util.Implicits._
val oldId = oldGit.getRepository.resolve(branch)
val newId = newGit.getRepository.resolve(requestBranch)
val i = newGit.log.addRange(oldId, newId).call.iterator.asScala
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
new CommitInfo(revCommit)
}.toSeq.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
}
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")))
}
}.toSeq
(commits, diffs)
}
}
}