(refs #2)Comparing between all forked repositories.

This commit is contained in:
takezoe
2013-07-27 04:11:33 +09:00
parent 59d85531ce
commit 66f3a1fe7d
4 changed files with 119 additions and 68 deletions

View File

@@ -10,6 +10,7 @@ import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import service.RepositoryService.RepositoryTreeNode
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
@@ -137,37 +138,38 @@ trait PullRequestsControllerBase extends ControllerBase {
private def checkConflict(userName: String, repositoryName: String, branch: String, private def checkConflict(userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = { requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
LockUtil.lock(s"${userName}/${repositoryName}/merge-check"){ // LockUtil.lock(s"${userName}/${repositoryName}/merge-check"){
val remote = getRepositoryDir(userName, repositoryName) // val remote = getRepositoryDir(userName, repositoryName)
val tmpdir = new java.io.File(getTemporaryDir(userName, repositoryName), "merge-check") // val tmpdir = new java.io.File(getTemporaryDir(userName, repositoryName), "merge-check")
if(tmpdir.exists()){ // if(tmpdir.exists()){
FileUtils.deleteDirectory(tmpdir) // FileUtils.deleteDirectory(tmpdir)
} // }
//
val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).call // val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).call
try { // try {
git.checkout.setName(branch).call // git.checkout.setName(branch).call
//
git.fetch // git.fetch
.setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString) // .setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString)
.setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/heads/${requestBranch}")).call // .setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/heads/${requestBranch}")).call
//
val result = git.merge // val result = git.merge
.include(git.getRepository.resolve("FETCH_HEAD")) // .include(git.getRepository.resolve("FETCH_HEAD"))
.setCommit(false).call // .setCommit(false).call
//
result.getConflicts != null // result.getConflicts != null
//
} finally { // } finally {
git.getRepository.close // git.getRepository.close
FileUtils.deleteDirectory(tmpdir) // FileUtils.deleteDirectory(tmpdir)
} // }
} // }
true
} }
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 Compare to self branch?
case (Some(originUserName), Some(originRepositoryName)) => { case (Some(originUserName), Some(originRepositoryName)) => {
getRepository(originUserName, originRepositoryName, baseUrl).map { oldRepo => getRepository(originUserName, originRepositoryName, baseUrl).map { oldRepo =>
withGit( withGit(
@@ -184,39 +186,63 @@ trait PullRequestsControllerBase extends ControllerBase {
} }
}) })
get("/:owner/:repository/pulls/compare/*:*...*")(collaboratorsOnly { repository => private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) =
if(repository.repository.originUserName.isEmpty || repository.repository.originRepositoryName.isEmpty){ if(value.contains(':')){
NotFound // TODO BadRequest? val array = value.split(":")
(array(0), array(1))
} else { } else {
val originUserName = repository.repository.originUserName.get (defaultOwner, value)
val originRepositoryName = repository.repository.originRepositoryName.get }
getRepository(originUserName, originRepositoryName, baseUrl).map{ originRepository => get("/:owner/:repository/pulls/compare/*...*")(collaboratorsOnly { repository =>
val Seq(compareUserName, compareFrom, compareTo) = multiParams("splat") val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, repository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, repository.owner)
(getRepository(originOwner, repository.name, baseUrl),
getRepository(forkedOwner, repository.name, baseUrl)) match {
case (Some(originRepository), Some(forkedRepository)) => {
withGit( withGit(
getRepositoryDir(originUserName, originRepositoryName), getRepositoryDir(originOwner, repository.name),
getRepositoryDir(repository.owner, repository.name) getRepositoryDir(forkedOwner, repository.name)
){ case (oldGit, newGit) => ){ case (oldGit, newGit) =>
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val forkedId = getForkedCommitId(oldGit, newGit, originUserName, originRepositoryName, compareFrom, val forkedId = getForkedCommitId(oldGit, newGit,
repository.owner, repository.name, compareTo) originOwner, repository.name, originBranch,
forkedOwner, repository.name, forkedBranch)
val oldId = oldGit.getRepository.resolve(forkedId) val oldId = oldGit.getRepository.resolve(forkedId)
val newId = newGit.getRepository.resolve(compareTo) val newId = newGit.getRepository.resolve(forkedBranch)
val (commits, diffs) = getRequestCompareInfo( val (commits, diffs) = getRequestCompareInfo(
compareUserName, repository.repository.originRepositoryName.get, forkedId, originOwner, repository.name, oldId.getName,
repository.owner, repository.name, compareTo) forkedOwner, repository.name, newId.getName)
pulls.html.compare(commits, diffs, compareUserName, compareFrom, compareTo, oldId.getName, newId.getName, pulls.html.compare(
checkConflict(originUserName, originRepositoryName, compareFrom, repository.owner, repository.name, compareTo), commits,
repository, originRepository) diffs,
repository.repository.originUserName.map { userName =>
getRepositoryNames(getForkedRepositoryTree(userName, repository.name))
} getOrElse Nil,
originBranch,
forkedBranch,
oldId.getName,
newId.getName,
checkConflict(originOwner, repository.name, originBranch, forkedOwner, repository.name, forkedBranch),
repository,
originRepository,
forkedRepository)
} }
} getOrElse NotFound }
case _ => NotFound
} }
}) })
private def getRepositoryNames(node: RepositoryTreeNode): List[String] =
node.owner :: node.children.map { child => getRepositoryNames(child) }.flatten
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) => post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
val loginUserName = context.loginAccount.get.userName val loginUserName = context.loginAccount.get.userName

View File

@@ -1,9 +1,12 @@
@(buttonValue: String = "")(body: Html) @(buttonValue: String = "", prefix: String = "")(body: Html)
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-mini dropdown-toggle" data-toggle="dropdown"> <button class="btn btn-mini dropdown-toggle" data-toggle="dropdown">
@if(buttonValue == ""){ @if(buttonValue.isEmpty){
<i class="icon-cog"></i> <i class="icon-cog"></i>
} else { } else {
@if(prefix.nonEmpty){
<span class="muted">@prefix:</span>
}
<strong>@buttonValue</strong> <strong>@buttonValue</strong>
} }
<span class="caret"></span> <span class="caret"></span>

View File

@@ -1,13 +1,14 @@
@(commits: Seq[Seq[util.JGitUtil.CommitInfo]], @(commits: Seq[Seq[util.JGitUtil.CommitInfo]],
diffs: Seq[util.JGitUtil.DiffInfo], diffs: Seq[util.JGitUtil.DiffInfo],
origin: String, members: List[String],
originId: String, originId: String,
forkedId: String, forkedId: String,
sourceId: String, sourceId: String,
commitId: String, commitId: String,
hasConflict: Boolean, hasConflict: Boolean,
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
originRepository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context) originRepository: service.RepositoryService.RepositoryInfo,
forkedRepository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@import org.eclipse.jgit.diff.DiffEntry.ChangeType @import org.eclipse.jgit.diff.DiffEntry.ChangeType
@@ -16,21 +17,29 @@
<div style="border: 1px solid #eee; background-color: #f8f8f8; margin-bottom: 10px; padding: 8px;"> <div style="border: 1px solid #eee; background-color: #f8f8f8; margin-bottom: 10px; padding: 8px;">
<div id="compare-info"> <div id="compare-info">
<a href="#" id="edit-compare-condition" class="btn btn-mini pull-right">Edit</a> <a href="#" id="edit-compare-condition" class="btn btn-mini pull-right">Edit</a>
<span class="label label-info monospace">@origin:@originId</span> ... <span class="label label-info monospace">@repository.owner:@forkedId</span> <span class="label label-info monospace">@originRepository.owner:@originId</span> ... <span class="label label-info monospace">@forkedRepository.owner:@forkedId</span>
</div> </div>
<div id="compare-edit" style="display: none;"> <div id="compare-edit" style="display: none;">
<a href="#" id="refresh-compare" class="pull-right"><i class="icon-remove-circle"></i></a> <a href="#" id="cancel-condition-editing" class="pull-right"><i class="icon-remove-circle"></i></a>
<span class="label label-info monospace">@origin/@repository.name:</span> @helper.html.dropdown(originRepository.owner + "/" + repository.name, "base fork") {
@helper.html.dropdown(originId) { @members.map { member =>
<li><a href="#" class="origin-owner" data-name="@member">@helper.html.checkicon(member == originRepository.owner) @member/@repository.name</a></li>
}
}
@helper.html.dropdown(originId, "base") {
@originRepository.branchList.map { branch => @originRepository.branchList.map { branch =>
<li><a href="#" class="origin-branch" data-branch="@branch">@helper.html.checkicon(branch == originId) @branch</a></li> <li><a href="#" class="origin-branch" data-name="@branch">@helper.html.checkicon(branch == originId) @branch</a></li>
} }
} }
... ...
<span class="label label-info monospace">@repository.owner/@repository.name:</span> @helper.html.dropdown(forkedRepository.owner + "/" + repository.name, "head fork") {
@helper.html.dropdown(forkedId) { @members.map { member =>
@repository.branchList.map { branch => <li><a href="#" class="forked-owner" data-name="@member">@helper.html.checkicon(member == forkedRepository.owner) @member/@repository.name</a></li>
<li><a href="#" class="forked-branch" data-branch="@branch">@helper.html.checkicon(branch == forkedId) @branch</a></li> }
}
@helper.html.dropdown(forkedId, "compare") {
@forkedRepository.branchList.map { branch =>
<li><a href="#" class="forked-branch" data-name="@branch">@helper.html.checkicon(branch == forkedId) @branch</a></li>
} }
} }
</div> </div>
@@ -41,7 +50,7 @@
</div> </div>
<div id="pull-request-form" class="box" style="display: none;"> <div id="pull-request-form" class="box" style="display: none;">
<div class="box-content"> <div class="box-content">
<form method="POST" action="@path/@origin/@repository.name/pulls/new" validate="true"> <form method="POST" action="@path/@originRepository.owner/@repository.name/pulls/new" validate="true">
<div style="width: 260px; position: absolute; margin-left: 635px;"> <div style="width: 260px; position: absolute; margin-left: 635px;">
@if(hasConflict){ @if(hasConflict){
<h4>We cant automatically merge these branches</h4> <h4>We cant automatically merge these branches</h4>
@@ -71,7 +80,7 @@
<tr> <tr>
<td style="padding: 20px; background-color: #eee; text-align: center;"> <td style="padding: 20px; background-color: #eee; text-align: center;">
<h4>There isn't anything to compare.</h4> <h4>There isn't anything to compare.</h4>
<strong>@origin:@originId</strong> and <strong>@repository.owner:@forkedId</strong> are identical. <strong>@originRepository.owner:@originId</strong> and <strong>@forkedRepository.owner:@forkedId</strong> are identical.
</td> </td>
</tr> </tr>
</table> </table>
@@ -133,17 +142,22 @@ $(function(){
$('#compare-edit').show(); $('#compare-edit').show();
}); });
$('a.origin-branch, a.forked-branch').click(function(){ $('#cancel-condition-editing').click(function(){
$('#compare-info').show();
$('#compare-edit').hide();
});
$('a.origin-owner, a.forked-owner, a.origin-branch, a.forked-branch').click(function(){
var e = $(this); var e = $(this);
e.parents('ul').find('i').attr('class', 'icon-white'); e.parents('ul').find('i').attr('class', 'icon-white');
e.find('i').attr('class', 'icon-ok'); e.find('i').attr('class', 'icon-ok');
e.parents('div.btn-group').find('button strong').text(e.data('branch')); e.parents('div.btn-group').find('button strong').text(e.text());
});
$('#refresh-compare').click(function(){ location.href = '@url(repository)/pulls/compare/' +
location.href = '@url(repository)/pulls/compare/@origin:' + $.trim($('i.icon-ok').parents('a.origin-owner' ).data('name')) + ':' +
$.trim($('i.icon-ok').parents('a.origin-branch').text()) + '...' + $.trim($('i.icon-ok').parents('a.origin-branch').data('name')) + '...' +
$.trim($('i.icon-ok').parents('a.forked-branch').text()); $.trim($('i.icon-ok').parents('a.forked-owner' ).data('name')) + ':' +
$.trim($('i.icon-ok').parents('a.forked-branch').data('name'));
}); });
$('#show-form').click(function(){ $('#show-form').click(function(){

View File

@@ -280,15 +280,23 @@ div.account-image {
margin-bottom: 8px; margin-bottom: 8px;
} }
ul.dropdown-menu {
padding: 2px 0;
}
ul.dropdown-menu li { ul.dropdown-menu li {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
font-size: 85%;
}
ul.dropdown-menu li a {
padding: 2px 10px;
} }
ul.dropdown-menu :last-child { ul.dropdown-menu :last-child {
border-bottom: none; border-bottom: none;
} }
/****************************************************************************/ /****************************************************************************/
/* Sign-in form */ /* Sign-in form */
/****************************************************************************/ /****************************************************************************/