(refs #2)Experimental implementation of merge pull request.

This commit is contained in:
takezoe
2013-07-15 03:23:28 +09:00
parent b68977597b
commit fd7d387fb0
3 changed files with 106 additions and 61 deletions

View File

@@ -11,6 +11,9 @@ import jp.sf.amateras.scalatra.forms._
import util.JGitUtil.DiffInfo import util.JGitUtil.DiffInfo
import scala.Some import scala.Some
import util.JGitUtil.CommitInfo 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 class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with ActivityService with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with ActivityService
@@ -20,7 +23,7 @@ trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with IssuesService with MilestonesService with ActivityService with PullRequestService self: RepositoryService with IssuesService with MilestonesService with ActivityService with PullRequestService
with ReferrerAuthenticator with CollaboratorsAuthenticator => with ReferrerAuthenticator with CollaboratorsAuthenticator =>
val form = mapping( val pullRequestForm = mapping(
"title" -> trim(label("Title" , text(required, maxlength(100)))), "title" -> trim(label("Title" , text(required, maxlength(100)))),
"content" -> trim(label("Content", optional(text()))), "content" -> trim(label("Content", optional(text()))),
"branch" -> trim(text(required, maxlength(100))), "branch" -> trim(text(required, maxlength(100))),
@@ -28,9 +31,15 @@ trait PullRequestsControllerBase extends ControllerBase {
"requestBranch" -> trim(text(required, maxlength(100))) "requestBranch" -> trim(text(required, maxlength(100)))
)(PullRequestForm.apply) )(PullRequestForm.apply)
val mergeForm = mapping(
"message" -> trim(label("Message", text(required)))
)(MergeForm.apply)
case class PullRequestForm(title: String, content: Option[String], branch: String, case class PullRequestForm(title: String, content: Option[String], branch: String,
requestUserName: String, requestBranch: String) requestUserName: String, requestBranch: String)
case class MergeForm(message: String)
get("/:owner/:repository/pulls")(referrersOnly { repository => get("/:owner/:repository/pulls")(referrersOnly { repository =>
pulls.html.list(repository) pulls.html.list(repository)
}) })
@@ -85,11 +94,40 @@ trait PullRequestsControllerBase extends ControllerBase {
}) })
post("/:owner/:repository/pulls/:id/merge")(collaboratorsOnly { repository => post("/:owner/:repository/pulls/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
// TODO Not implemented yet. 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
}) })
// TODO Replace correct authenticator
get("/:owner/:repository/pulls/compare")(collaboratorsOnly { newRepo => get("/:owner/:repository/pulls/compare")(collaboratorsOnly { newRepo =>
(newRepo.repository.originUserName, newRepo.repository.originRepositoryName) match { (newRepo.repository.originUserName, newRepo.repository.originRepositoryName) match {
case (None,_)|(_, None) => NotFound // TODO BadRequest? case (None,_)|(_, None) => NotFound // TODO BadRequest?
@@ -109,7 +147,6 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
}) })
// TODO Replace correct authenticator
get("/:owner/:repository/pulls/compare/*:*...*")(collaboratorsOnly { repository => get("/:owner/:repository/pulls/compare/*:*...*")(collaboratorsOnly { repository =>
if(repository.repository.originUserName.isEmpty || repository.repository.originRepositoryName.isEmpty){ if(repository.repository.originUserName.isEmpty || repository.repository.originRepositoryName.isEmpty){
NotFound // TODO BadRequest? NotFound // TODO BadRequest?
@@ -135,7 +172,7 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
}) })
post("/:owner/:repository/pulls/new", form)(referrersOnly { (form, repository) => post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
val loginUserName = context.loginAccount.get.userName val loginUserName = context.loginAccount.get.userName
val issueId = createIssue( val issueId = createIssue(
@@ -193,12 +230,12 @@ trait PullRequestsControllerBase extends ControllerBase {
val oldId = oldGit.getRepository.resolve(branch) val oldId = oldGit.getRepository.resolve(branch)
val newId = newGit.getRepository.resolve(requestBranch) val newId = newGit.getRepository.resolve(requestBranch)
val i = newGit.log.addRange(oldId, newId).call.iterator val i = newGit.log.addRange(oldId, newId).call.iterator.asScala
val commits = new ArrayBuffer[CommitInfo] val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
while(i.hasNext){ new CommitInfo(revCommit)
val revCommit = i.next }.toSeq.splitWith{ (commit1, commit2) =>
commits += new CommitInfo(revCommit) view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
} }
val diffs = newGit.diff.setOldTree(oldTreeIter).setNewTree(newTreeIter).call.asScala.map { diff => val diffs = newGit.diff.setOldTree(oldTreeIter).setNewTree(newTreeIter).call.asScala.map { diff =>
@@ -209,11 +246,9 @@ trait PullRequestsControllerBase extends ControllerBase {
JGitUtil.getContent(oldGit, diff.getOldId.toObjectId, false).filter(FileUtil.isText).map(new String(_, "UTF-8")), 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"))) JGitUtil.getContent(newGit, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(new String(_, "UTF-8")))
} }
} }.toSeq
(commits.toList.splitWith{ (commit1, commit2) => (commits, diffs)
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
}, diffs.toSeq)
} }
} }

View File

@@ -117,6 +117,13 @@ trait ActivityService {
Some(title), Some(title),
currentDate) currentDate)
def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String): Unit =
Activities.autoInc insert(userName, repositoryName, activityUserName,
"merge_pullreq",
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
Some(message),
currentDate)
def insertCommitId(userName: String, repositoryName: String, commitId: String) = { def insertCommitId(userName: String, repositoryName: String, commitId: String) = {
CommitLog insert (userName, repositoryName, commitId) CommitLog insert (userName, repositoryName, commitId)
} }

View File

@@ -98,59 +98,62 @@
</div> </div>
} }
} }
@if(hasWritePermission){
<div class="box issue-comment-box" style="background-color: #d8f5cd;"> <div class="box issue-comment-box" style="background-color: #d8f5cd;">
<div class="box-content"class="issue-content" style="border: 1px solid #95c97e; padding: 10px;"> <div class="box-content"class="issue-content" style="border: 1px solid #95c97e; padding: 10px;">
<div id="merge-pull-request"> <div id="merge-pull-request">
<div class="pull-right"> <div class="pull-right">
<input type="button" class="btn btn-success" id="merge-pull-request-button" value="Merge pull request"/> <input type="button" class="btn btn-success" id="merge-pull-request-button" value="Merge pull request"/>
</div>
<div>
<strong>This pull request can be automatically merged.</strong>
</div>
<div class="small">
You can also merge branches on the <a href="#" id="show-command-line">command line</a>.
</div>
<div id="command-line" style="display: none;">
<hr>
<strong>Merging via command line</strong>
<p>
If you do not want to use the merge button or an automatic merge cannot be performed,
you can perform a manual merge on the command line.
</p>
<div class="input-prepend">
<span class="add-on">HTTP</span>
<input type="text" value="https://github.com/takezoen/test.git" id="repository-url" readonly>
</div> </div>
<p> <div>
<strong>Step 1:</strong> Check out a new branch to test the changes — run this from your project directory <strong>This pull request can be automatically merged.</strong>
</p> </div>
<pre>git checkout -b @{pullreq.requestUserName}-@{pullreq.requestBranch} @{pullreq.requestBranch}</pre> <div class="small">
<p> You can also merge branches on the <a href="#" id="show-command-line">command line</a>.
<strong>Step 2:</strong> Bring in @{pullreq.requestUserName}'s changes and test </div>
</p> <div id="command-line" style="display: none;">
<pre>git pull https://github.com/@{pullreq.requestUserName}/@{repository.name}.git @{pullreq.requestBranch}</pre> <hr>
<p> <strong>Merging via command line</strong>
<strong>Step 3:</strong> Merge the changes and update the server <p>
</p> If you do not want to use the merge button or an automatic merge cannot be performed,
<pre>git checkout master you can perform a manual merge on the command line.
</p>
<div class="input-prepend">
<span class="add-on">HTTP</span>
<input type="text" value="https://github.com/takezoen/test.git" id="repository-url" readonly>
</div>
<p>
<strong>Step 1:</strong> Check out a new branch to test the changes — run this from your project directory
</p>
<pre>git checkout -b @{pullreq.requestUserName}-@{pullreq.requestBranch} @{pullreq.requestBranch}</pre>
<p>
<strong>Step 2:</strong> Bring in @{pullreq.requestUserName}'s changes and test
</p>
<pre>git pull https://github.com/@{pullreq.requestUserName}/@{repository.name}.git @{pullreq.requestBranch}</pre>
<p>
<strong>Step 3:</strong> Merge the changes and update the server
</p>
<pre>git checkout master
git merge @{pullreq.requestUserName}-@{pullreq.branch} git merge @{pullreq.requestUserName}-@{pullreq.branch}
git push origin @{pullreq.branch}</pre> git push origin @{pullreq.branch}</pre>
</div>
</div> </div>
</div> <div id="confirm-merge-form" style="display: none;">
<div id="confirm-merge-form" style="display: none;"> <form method="POST" action="@url(repository)/pulls/@issue.issueId/merge">
<div> <div>
<strong>Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}</strong> <strong>Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}</strong>
</div> </div>
<textarea name="content" style="width: 680px; height: 80px;">@issue.title</textarea> <span id="error-message" class="error"></span>
<div> <textarea name="message" style="width: 680px; height: 80px;">@issue.title</textarea>
<input type="button" class="btn" value="Cancel" id="cancel-merge-pull-request"/> <div>
<input type="submit" class="btn btn-success" value="Confirm merge"/> <input type="button" class="btn" value="Cancel" id="cancel-merge-pull-request"/>
<input type="submit" class="btn btn-success" value="Confirm merge"/>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
</div> }
@if(loginAccount.isDefined){ @if(loginAccount.isDefined){
<form method="POST" validate="true"> <form method="POST" validate="true">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div> <div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>