mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 03:26:06 +01:00
(refs #2)Experimental implementation of merge pull request.
This commit is contained in:
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user