(refs #508)Basic filter box implementation

This commit is contained in:
Naoki Takezoe
2014-11-11 12:07:22 +09:00
parent 65a1ca7146
commit bc0b11b60a
4 changed files with 555 additions and 493 deletions

View File

@@ -9,7 +9,6 @@ import util.Implicits._
import util.ControlUtil._ import util.ControlUtil._
import org.scalatra.Ok import org.scalatra.Ok
import model.Issue import model.Issue
import plugin.PluginSystem
class IssuesController extends IssuesControllerBase class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
@@ -50,7 +49,12 @@ trait IssuesControllerBase extends ControllerBase {
)(IssueStateForm.apply) )(IssueStateForm.apply)
get("/:owner/:repository/issues")(referrersOnly { repository => get("/:owner/:repository/issues")(referrersOnly { repository =>
searchIssues(repository) val q = request.getParameter("q")
if(Option(q).exists(_.contains("is:pr"))){
redirect(s"/${repository.owner}/${repository.name}/pulls?q=" + StringUtil.urlEncode(q))
} else {
searchIssues(repository)
}
}) })
get("/:owner/:repository/issues/:id")(referrersOnly { repository => get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
@@ -390,7 +394,14 @@ trait IssuesControllerBase extends ControllerBase {
// retrieve search condition // retrieve search condition
val condition = session.putAndGet(sessionKey, val condition = session.putAndGet(sessionKey,
if(request.hasQueryString) IssueSearchCondition(request) if(request.hasQueryString){
val q = request.getParameter("q")
if(q == null){
IssueSearchCondition(request)
} else {
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
}
}
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition()) else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
) )

View File

@@ -1,475 +1,483 @@
package app package app
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier, Keys} import util._
import util.Directory._ import util.Directory._
import util.Implicits._ import util.Implicits._
import util.ControlUtil._ import util.ControlUtil._
import service._ import service._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import jp.sf.amateras.scalatra.forms._ import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent} import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
import service.IssuesService._ import service.IssuesService._
import service.PullRequestService._ import service.PullRequestService._
import util.JGitUtil.DiffInfo import util.JGitUtil.DiffInfo
import util.JGitUtil.CommitInfo import util.JGitUtil.CommitInfo
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.eclipse.jgit.merge.MergeStrategy import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.errors.NoMergeBaseException import org.eclipse.jgit.errors.NoMergeBaseException
import service.WebHookService.WebHookPayload import service.WebHookService.WebHookPayload
import util.JGitUtil.DiffInfo
class PullRequestsController extends PullRequestsControllerBase import scala.Some
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService import util.JGitUtil.CommitInfo
with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
class PullRequestsController extends PullRequestsControllerBase
trait PullRequestsControllerBase extends ControllerBase { with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
trait PullRequestsControllerBase extends ControllerBase {
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase]) self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
val pullRequestForm = mapping(
"title" -> trim(label("Title" , text(required, maxlength(100)))), private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
"content" -> trim(label("Content", optional(text()))),
"targetUserName" -> trim(text(required, maxlength(100))), val pullRequestForm = mapping(
"targetBranch" -> trim(text(required, maxlength(100))), "title" -> trim(label("Title" , text(required, maxlength(100)))),
"requestUserName" -> trim(text(required, maxlength(100))), "content" -> trim(label("Content", optional(text()))),
"requestRepositoryName" -> trim(text(required, maxlength(100))), "targetUserName" -> trim(text(required, maxlength(100))),
"requestBranch" -> trim(text(required, maxlength(100))), "targetBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))), "requestUserName" -> trim(text(required, maxlength(100))),
"commitIdTo" -> trim(text(required, maxlength(40))) "requestRepositoryName" -> trim(text(required, maxlength(100))),
)(PullRequestForm.apply) "requestBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))),
val mergeForm = mapping( "commitIdTo" -> trim(text(required, maxlength(40)))
"message" -> trim(label("Message", text(required))) )(PullRequestForm.apply)
)(MergeForm.apply)
val mergeForm = mapping(
case class PullRequestForm( "message" -> trim(label("Message", text(required)))
title: String, )(MergeForm.apply)
content: Option[String],
targetUserName: String, case class PullRequestForm(
targetBranch: String, title: String,
requestUserName: String, content: Option[String],
requestRepositoryName: String, targetUserName: String,
requestBranch: String, targetBranch: String,
commitIdFrom: String, requestUserName: String,
commitIdTo: String) requestRepositoryName: String,
requestBranch: String,
case class MergeForm(message: String) commitIdFrom: String,
commitIdTo: String)
get("/:owner/:repository/pulls")(referrersOnly { repository =>
searchPullRequests(None, repository) case class MergeForm(message: String)
})
get("/:owner/:repository/pulls")(referrersOnly { repository =>
get("/:owner/:repository/pull/:id")(referrersOnly { repository => val q = request.getParameter("q")
params("id").toIntOpt.flatMap{ issueId => if(Option(q).exists(_.contains("is:issue"))){
val owner = repository.owner redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
val name = repository.name } else {
getPullRequest(owner, name, issueId) map { case(issue, pullreq) => searchPullRequests(None, repository)
using(Git.open(getRepositoryDir(owner, name))){ git => }
val (commits, diffs) = })
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
pulls.html.pullreq( params("id").toIntOpt.flatMap{ issueId =>
issue, pullreq, val owner = repository.owner
getComments(owner, name, issueId), val name = repository.name
getIssueLabels(owner, name, issueId), getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, using(Git.open(getRepositoryDir(owner, name))){ git =>
getMilestonesWithIssueCount(owner, name), val (commits, diffs) =
getLabels(owner, name), getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
commits,
diffs, pulls.html.pullreq(
hasWritePermission(owner, name, context.loginAccount), issue, pullreq,
repository) getComments(owner, name, issueId),
} getIssueLabels(owner, name, issueId),
} (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
} getOrElse NotFound getMilestonesWithIssueCount(owner, name),
}) getLabels(owner, name),
commits,
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository => diffs,
params("id").toIntOpt.flatMap{ issueId => hasWritePermission(owner, name, context.loginAccount),
val owner = repository.owner repository)
val name = repository.name }
getPullRequest(owner, name, issueId) map { case(issue, pullreq) => }
pulls.html.mergeguide( } getOrElse NotFound
checkConflictInPullRequest(owner, name, pullreq.branch, pullreq.requestUserName, name, pullreq.requestBranch, issueId), })
pullreq,
s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git") ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
} params("id").toIntOpt.flatMap{ issueId =>
} getOrElse NotFound val owner = repository.owner
}) val name = repository.name
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
get("/:owner/:repository/pull/:id/delete/*")(collaboratorsOnly { repository => pulls.html.mergeguide(
params("id").toIntOpt.map { issueId => checkConflictInPullRequest(owner, name, pullreq.branch, pullreq.requestUserName, name, pullreq.requestBranch, issueId),
val branchName = multiParams("splat").head pullreq,
val userName = context.loginAccount.get.userName s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
if(repository.repository.defaultBranch != branchName){ }
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => } getOrElse NotFound
git.branchDelete().setForce(true).setBranchNames(branchName).call() })
recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
} get("/:owner/:repository/pull/:id/delete/*")(collaboratorsOnly { repository =>
} params("id").toIntOpt.map { issueId =>
createComment(repository.owner, repository.name, userName, issueId, branchName, "delete_branch") val branchName = multiParams("splat").head
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") val userName = context.loginAccount.get.userName
} getOrElse NotFound if(repository.repository.defaultBranch != branchName){
}) using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
git.branchDelete().setForce(true).setBranchNames(branchName).call()
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) => recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
params("id").toIntOpt.flatMap { issueId => }
val owner = repository.owner }
val name = repository.name createComment(repository.owner, repository.name, userName, issueId, branchName, "delete_branch")
LockUtil.lock(s"${owner}/${name}"){ redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
getPullRequest(owner, name, issueId).map { case (issue, pullreq) => } getOrElse NotFound
using(Git.open(getRepositoryDir(owner, name))) { git => })
// mark issue as merged and close.
val loginAccount = context.loginAccount.get post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge") params("id").toIntOpt.flatMap { issueId =>
createComment(owner, name, loginAccount.userName, issueId, "Close", "close") val owner = repository.owner
updateClosed(owner, name, issueId, true) val name = repository.name
LockUtil.lock(s"${owner}/${name}"){
// record activity getPullRequest(owner, name, issueId).map { case (issue, pullreq) =>
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message) using(Git.open(getRepositoryDir(owner, name))) { git =>
// mark issue as merged and close.
// merge val loginAccount = context.loginAccount.get
val mergeBaseRefName = s"refs/heads/${pullreq.branch}" createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
val mergeBaseTip = git.getRepository.resolve(mergeBaseRefName) updateClosed(owner, name, issueId, true)
val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
val conflicted = try { // record activity
!merger.merge(mergeBaseTip, mergeTip) recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
} catch {
case e: NoMergeBaseException => true // merge
} val mergeBaseRefName = s"refs/heads/${pullreq.branch}"
if (conflicted) { val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
throw new RuntimeException("This pull request can't merge automatically.") val mergeBaseTip = git.getRepository.resolve(mergeBaseRefName)
} val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
val conflicted = try {
// creates merge commit !merger.merge(mergeBaseTip, mergeTip)
val mergeCommit = new CommitBuilder() } catch {
mergeCommit.setTreeId(merger.getResultTreeId) case e: NoMergeBaseException => true
mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*) }
val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) if (conflicted) {
mergeCommit.setAuthor(personIdent) throw new RuntimeException("This pull request can't merge automatically.")
mergeCommit.setCommitter(personIdent) }
mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" +
form.message) // creates merge commit
val mergeCommit = new CommitBuilder()
// insertObject and got mergeCommit Object Id mergeCommit.setTreeId(merger.getResultTreeId)
val inserter = git.getRepository.newObjectInserter mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*)
val mergeCommitId = inserter.insert(mergeCommit) val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
inserter.flush() mergeCommit.setAuthor(personIdent)
inserter.release() mergeCommit.setCommitter(personIdent)
mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" +
// update refs form.message)
val refUpdate = git.getRepository.updateRef(mergeBaseRefName)
refUpdate.setNewObjectId(mergeCommitId) // insertObject and got mergeCommit Object Id
refUpdate.setForceUpdate(false) val inserter = git.getRepository.newObjectInserter
refUpdate.setRefLogIdent(personIdent) val mergeCommitId = inserter.insert(mergeCommit)
refUpdate.setRefLogMessage("merged", true) inserter.flush()
refUpdate.update() inserter.release()
val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom, // update refs
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo) val refUpdate = git.getRepository.updateRef(mergeBaseRefName)
refUpdate.setNewObjectId(mergeCommitId)
// close issue by content of pull request refUpdate.setForceUpdate(false)
val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch refUpdate.setRefLogIdent(personIdent)
if(pullreq.branch == defaultBranch){ refUpdate.setRefLogMessage("merged", true)
commits.flatten.foreach { commit => refUpdate.update()
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
} val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom,
issue.content match { pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
case Some(content) => closeIssuesFromMessage(content, loginAccount.userName, owner, name)
case _ => // close issue by content of pull request
} val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name) if(pullreq.branch == defaultBranch){
} commits.flatten.foreach { commit =>
// call web hook closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
getWebHookURLs(owner, name) match { }
case webHookURLs if(webHookURLs.nonEmpty) => issue.content match {
for(ownerAccount <- getAccountByUserName(owner)){ case Some(content) => closeIssuesFromMessage(content, loginAccount.userName, owner, name)
callWebHook(owner, name, webHookURLs, case _ =>
WebHookPayload(git, loginAccount, mergeBaseRefName, repository, commits.flatten.toList, ownerAccount)) }
} closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
case _ => }
} // call web hook
getWebHookURLs(owner, name) match {
// notifications case webHookURLs if(webHookURLs.nonEmpty) =>
Notifier().toNotify(repository, issueId, "merge"){ for(ownerAccount <- getAccountByUserName(owner)){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") callWebHook(owner, name, webHookURLs,
} WebHookPayload(git, loginAccount, mergeBaseRefName, repository, commits.flatten.toList, ownerAccount))
}
redirect(s"/${owner}/${name}/pull/${issueId}") case _ =>
} }
}
} // notifications
} getOrElse NotFound Notifier().toNotify(repository, issueId, "merge"){
}) Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
}
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { redirect(s"/${owner}/${name}/pull/${issueId}")
case (Some(originUserName), Some(originRepositoryName)) => { }
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository => }
using( }
Git.open(getRepositoryDir(originUserName, originRepositoryName)), } getOrElse NotFound
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) })
){ (oldGit, newGit) =>
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2 get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2 (forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => {
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}") getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository =>
} using(
} getOrElse NotFound Git.open(getRepositoryDir(originUserName, originRepositoryName)),
} Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
case _ => { ){ (oldGit, newGit) =>
using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git => val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) => val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
} getOrElse { redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}") }
} } getOrElse NotFound
} }
} case _ => {
} using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git =>
}) JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository => } getOrElse {
val Seq(origin, forked) = multiParams("splat") redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner) }
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner) }
}
(for( }
originRepositoryName <- if(originOwner == forkedOwner){ })
Some(forkedRepository.name)
} else { get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
forkedRepository.repository.originRepositoryName.orElse { val Seq(origin, forked) = multiParams("splat")
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2) val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
} val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
};
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl) (for(
) yield { originRepositoryName <- if(originOwner == forkedOwner){
using( Some(forkedRepository.name)
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), } else {
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) forkedRepository.repository.originRepositoryName.orElse {
){ case (oldGit, newGit) => getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2 }
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2 };
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
val forkedId = JGitUtil.getForkedCommitId(oldGit, newGit, ) yield {
originRepository.owner, originRepository.name, originBranch, using(
forkedRepository.owner, forkedRepository.name, forkedBranch) Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
val oldId = oldGit.getRepository.resolve(forkedId) ){ case (oldGit, newGit) =>
val newId = newGit.getRepository.resolve(forkedBranch) val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner, originRepository.name, oldId.getName, val forkedId = JGitUtil.getForkedCommitId(oldGit, newGit,
forkedRepository.owner, forkedRepository.name, newId.getName) originRepository.owner, originRepository.name, originBranch,
forkedRepository.owner, forkedRepository.name, forkedBranch)
pulls.html.compare(
commits, val oldId = oldGit.getRepository.resolve(forkedId)
diffs, val newId = newGit.getRepository.resolve(forkedBranch)
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName) val (commits, diffs) = getRequestCompareInfo(
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name) originRepository.owner, originRepository.name, oldId.getName,
}, forkedRepository.owner, forkedRepository.name, newId.getName)
originBranch,
forkedBranch, pulls.html.compare(
oldId.getName, commits,
newId.getName, diffs,
forkedRepository, (forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
originRepository, case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
forkedRepository, case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
hasWritePermission(forkedRepository.owner, forkedRepository.name, context.loginAccount)) },
} originBranch,
}) getOrElse NotFound forkedBranch,
}) oldId.getName,
newId.getName,
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { forkedRepository => forkedRepository,
val Seq(origin, forked) = multiParams("splat") originRepository,
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner) forkedRepository,
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner) hasWritePermission(forkedRepository.owner, forkedRepository.name, context.loginAccount))
}
(for( }) getOrElse NotFound
originRepositoryName <- if(originOwner == forkedOwner){ })
Some(forkedRepository.name)
} else { ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { forkedRepository =>
forkedRepository.repository.originRepositoryName.orElse { val Seq(origin, forked) = multiParams("splat")
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2) val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
} val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
};
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl) (for(
) yield { originRepositoryName <- if(originOwner == forkedOwner){
using( Some(forkedRepository.name)
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), } else {
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) forkedRepository.repository.originRepositoryName.orElse {
){ case (oldGit, newGit) => getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2 }
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2 };
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
pulls.html.mergecheck( ) yield {
checkConflict(originRepository.owner, originRepository.name, originBranch, using(
forkedRepository.owner, forkedRepository.name, forkedBranch)) Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
} Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
}) getOrElse NotFound ){ case (oldGit, newGit) =>
}) val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
val loginUserName = context.loginAccount.get.userName pulls.html.mergecheck(
checkConflict(originRepository.owner, originRepository.name, originBranch,
val issueId = createIssue( forkedRepository.owner, forkedRepository.name, forkedBranch))
owner = repository.owner, }
repository = repository.name, }) getOrElse NotFound
loginUser = loginUserName, })
title = form.title,
content = form.content, post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
assignedUserName = None, val loginUserName = context.loginAccount.get.userName
milestoneId = None,
isPullRequest = true) val issueId = createIssue(
owner = repository.owner,
createPullRequest( repository = repository.name,
originUserName = repository.owner, loginUser = loginUserName,
originRepositoryName = repository.name, title = form.title,
issueId = issueId, content = form.content,
originBranch = form.targetBranch, assignedUserName = None,
requestUserName = form.requestUserName, milestoneId = None,
requestRepositoryName = form.requestRepositoryName, isPullRequest = true)
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom, createPullRequest(
commitIdTo = form.commitIdTo) originUserName = repository.owner,
originRepositoryName = repository.name,
// fetch requested branch issueId = issueId,
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => originBranch = form.targetBranch,
git.fetch requestUserName = form.requestUserName,
.setRemote(getRepositoryDir(form.requestUserName, form.requestRepositoryName).toURI.toString) requestRepositoryName = form.requestRepositoryName,
.setRefSpecs(new RefSpec(s"refs/heads/${form.requestBranch}:refs/pull/${issueId}/head")) requestBranch = form.requestBranch,
.call commitIdFrom = form.commitIdFrom,
} commitIdTo = form.commitIdTo)
// record activity // fetch requested branch
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title) using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
git.fetch
// notifications .setRemote(getRepositoryDir(form.requestUserName, form.requestRepositoryName).toURI.toString)
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){ .setRefSpecs(new RefSpec(s"refs/heads/${form.requestBranch}:refs/pull/${issueId}/head"))
Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}") .call
} }
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") // record activity
}) recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
/** // notifications
* Checks whether conflict will be caused in merging. Returns true if conflict will be caused. Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
*/ Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
private def checkConflict(userName: String, repositoryName: String, branch: String, }
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
LockUtil.lock(s"${userName}/${repositoryName}"){ redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git => })
val remoteRefName = s"refs/heads/${branch}"
val tmpRefName = s"refs/merge-check/${userName}/${branch}" /**
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true) * Checks whether conflict will be caused in merging. Returns true if conflict will be caused.
try { */
// fetch objects from origin repository branch private def checkConflict(userName: String, repositoryName: String, branch: String,
git.fetch requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
.setRemote(getRepositoryDir(userName, repositoryName).toURI.toString) LockUtil.lock(s"${userName}/${repositoryName}"){
.setRefSpecs(refSpec) using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git =>
.call val remoteRefName = s"refs/heads/${branch}"
val tmpRefName = s"refs/merge-check/${userName}/${branch}"
// merge conflict check val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) try {
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}") // fetch objects from origin repository branch
val mergeTip = git.getRepository.resolve(tmpRefName) git.fetch
try { .setRemote(getRepositoryDir(userName, repositoryName).toURI.toString)
!merger.merge(mergeBaseTip, mergeTip) .setRefSpecs(refSpec)
} catch { .call
case e: NoMergeBaseException => true
} // merge conflict check
} finally { val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
val refUpdate = git.getRepository.updateRef(refSpec.getDestination) val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}")
refUpdate.setForceUpdate(true) val mergeTip = git.getRepository.resolve(tmpRefName)
refUpdate.delete() try {
} !merger.merge(mergeBaseTip, mergeTip)
} } catch {
} case e: NoMergeBaseException => true
} }
} finally {
/** val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
* Checks whether conflict will be caused in merging within pull request. Returns true if conflict will be caused. refUpdate.setForceUpdate(true)
*/ refUpdate.delete()
private def checkConflictInPullRequest(userName: String, repositoryName: String, branch: String, }
requestUserName: String, requestRepositoryName: String, requestBranch: String, }
issueId: Int): Boolean = { }
LockUtil.lock(s"${userName}/${repositoryName}") { }
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
// merge /**
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) * Checks whether conflict will be caused in merging within pull request. Returns true if conflict will be caused.
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${branch}") */
val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head") private def checkConflictInPullRequest(userName: String, repositoryName: String, branch: String,
try { requestUserName: String, requestRepositoryName: String, requestBranch: String,
!merger.merge(mergeBaseTip, mergeTip) issueId: Int): Boolean = {
} catch { LockUtil.lock(s"${userName}/${repositoryName}") {
case e: NoMergeBaseException => true using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
} // merge
} val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
} val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${branch}")
} val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
try {
/** !merger.merge(mergeBaseTip, mergeTip)
* Parses branch identifier and extracts owner and branch name as tuple. } catch {
* case e: NoMergeBaseException => true
* - "owner:branch" to ("owner", "branch") }
* - "branch" to ("defaultOwner", "branch") }
*/ }
private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) = }
if(value.contains(':')){
val array = value.split(":") /**
(array(0), array(1)) * Parses branch identifier and extracts owner and branch name as tuple.
} else { *
(defaultOwner, value) * - "owner:branch" to ("owner", "branch")
} * - "branch" to ("defaultOwner", "branch")
*/
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String, private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) =
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = if(value.contains(':')){
using( val array = value.split(":")
Git.open(getRepositoryDir(userName, repositoryName)), (array(0), array(1))
Git.open(getRepositoryDir(requestUserName, requestRepositoryName)) } else {
){ (oldGit, newGit) => (defaultOwner, value)
val oldId = oldGit.getRepository.resolve(branch) }
val newId = newGit.getRepository.resolve(requestCommitId)
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit => requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) =
new CommitInfo(revCommit) using(
}.toList.splitWith { (commit1, commit2) => Git.open(getRepositoryDir(userName, repositoryName)),
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
} ){ (oldGit, newGit) =>
val oldId = oldGit.getRepository.resolve(branch)
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true) val newId = newGit.getRepository.resolve(requestCommitId)
(commits, diffs) val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
} new CommitInfo(revCommit)
}.toList.splitWith { (commit1, commit2) =>
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
defining(repository.owner, repository.name){ case (owner, repoName) => }
val page = IssueSearchCondition.page(request)
val sessionKey = Keys.Session.Pulls(owner, repoName) val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
// retrieve search condition (commits, diffs)
val condition = session.putAndGet(sessionKey, }
if(request.hasQueryString) IssueSearchCondition(request)
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition()) private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
) defining(repository.owner, repository.name){ case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
issues.html.list( val sessionKey = Keys.Session.Pulls(owner, repoName)
"pulls",
searchIssue(condition, Map.empty, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), // retrieve search condition
page, val condition = session.putAndGet(sessionKey,
(getCollaborators(owner, repoName) :+ owner).sorted, if(request.hasQueryString) IssueSearchCondition(request)
getMilestones(owner, repoName), else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
getLabels(owner, repoName), )
countIssue(condition.copy(state = "open" ), Map.empty, true, owner -> repoName),
countIssue(condition.copy(state = "closed"), Map.empty, true, owner -> repoName), issues.html.list(
condition, "pulls",
repository, searchIssue(condition, Map.empty, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
hasWritePermission(owner, repoName, context.loginAccount)) page,
} (getCollaborators(owner, repoName) :+ owner).sorted,
getMilestones(owner, repoName),
} getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), Map.empty, true, owner -> repoName),
countIssue(condition.copy(state = "closed"), Map.empty, true, owner -> repoName),
condition,
repository,
hasWritePermission(owner, repoName, context.loginAccount))
}
}

View File

@@ -415,6 +415,47 @@ object IssuesService {
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value) if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
} }
/**
* Restores IssueSearchCondition instance from filter query.
*/
def apply(filter: String, milestones: Map[String, Int]): IssueSearchCondition = {
val conditions = filter.split("[  \t]+").map { x =>
val dim = x.split(":")
dim(0) -> dim(1)
}.groupBy(_._1).map { case (key, values) =>
key -> values.map(_._2).toSeq
}
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
case "created-asc" => ("created" , "asc" )
case "comments-desc" => ("comments", "desc")
case "comments-asc" => ("comments", "asc" )
case "updated-desc" => ("comments", "desc")
case "updated-asc" => ("comments", "asc" )
case _ => ("created" , "desc")
}
IssueSearchCondition(
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
conditions.get("milestone").flatMap(_.headOption) match {
case None => None
case Some("none") => Some(None)
case Some(x) => Some(milestones.get(x))
},
conditions.get("author").flatMap(_.headOption),
conditions.get("assignee").flatMap(_.headOption),
None, // TODO??
conditions.get("is").getOrElse(Seq.empty).filter(x => x == "open" || x == "closed").headOption.getOrElse("open"),
sort,
direction,
None, // TODO??
Set.empty // TODO??
)
}
/**
* Restores IssueSearchCondition instance from request parameters.
*/
def apply(request: HttpServletRequest): IssueSearchCondition = def apply(request: HttpServletRequest): IssueSearchCondition =
IssueSearchCondition( IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty), param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),

View File

@@ -11,22 +11,24 @@
<li class="@if(active == "milestones"){active} last"><a href="@url(repository)/issues/milestones">Milestones</a></li> <li class="@if(active == "milestones"){active} last"><a href="@url(repository)/issues/milestones">Milestones</a></li>
<li class="pull-right"> <li class="pull-right">
@condition.map { condition => @condition.map { condition =>
<div class="input-prepend" style="margin-bottom: 0px;"> <form method="GET" id="search-filter-form">
<div class="btn-group"> <div class="input-prepend" style="margin-bottom: 0px;">
<button class="btn dropdown-toggle" data-toggle="dropdown" style="height: 34px;"> <div class="btn-group">
Filter <button type="button" class="btn dropdown-toggle" data-toggle="dropdown" style="height: 34px;">
<span class="caret"></span> Filter
</button> <span class="caret"></span>
<ul class="dropdown-menu"> </button>
<li><a href="#">Open issues and pull requests</a></li> <ul class="dropdown-menu">
<li><a href="#">Your issues</a></li> <li><a href="#">Open issues and pull requests</a></li>
<li><a href="#">Your pull requests</a></li> <li><a href="#">Your issues</a></li>
<li><a href="#">Everything assigned to you</a></li> <li><a href="#">Your pull requests</a></li>
<li><a href="#">Everything mentioning you</a></li> <li><a href="#">Everything assigned to you</a></li>
</ul> <li><a href="#">Everything mentioning you</a></li>
</ul>
</div>
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px;" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
</div> </div>
<input type="text" class="input-xlarge" style="height: 24px;" value="@condition.toFilterString"/> </form>
</div>
} }
@if(loginAccount.isDefined){ @if(loginAccount.isDefined){
<div class="btn-group"> <div class="btn-group">