Merge remote-tracking branch 'origin/master' into api-support

Conflicts:
	src/main/scala/ScalatraBootstrap.scala
	src/main/scala/gitbucket/core/controller/AccountController.scala
	src/main/scala/gitbucket/core/controller/ControllerBase.scala
	src/main/scala/gitbucket/core/controller/IssuesController.scala
	src/main/scala/gitbucket/core/controller/PullRequestsController.scala
	src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
	src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
	src/main/scala/gitbucket/core/model/Profile.scala
	src/main/scala/gitbucket/core/service/PullRequestService.scala
	src/main/scala/gitbucket/core/service/WebHookService.scala
	src/main/scala/gitbucket/core/servlet/InitializeListener.scala
	src/main/scala/gitbucket/core/view/helpers.scala
	src/main/twirl/gitbucket/core/pulls/conversation.scala.html
	src/main/twirl/gitbucket/core/pulls/mergeguide.scala.html
	src/main/twirl/issues/listparts.scala.html
This commit is contained in:
Tomofumi Tanaka
2015-03-16 22:49:47 +09:00
215 changed files with 1127 additions and 916 deletions

View File

@@ -0,0 +1,35 @@
@(commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
comments: Option[List[gitbucket.core.model.Comment]] = None,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.model._
<div class="box">
<table class="table table-file-list" style="border: 1px solid silver;">
@commits.map { day =>
<tr>
<th colspan="4" class="box-header" style="font-weight: normal;">@date(day.head.commitTime)</th>
</tr>
@day.map { commit =>
<tr>
<td style="width: 20%;">
@avatar(commit, 20)
@user(commit.authorName, commit.authorEmailAddress, "username")
</td>
<td>@commit.shortMessage</td>
<td style="width: 10%; text-align: right">
<span class="badge" style="display: inline">@if(comments.isDefined){
@comments.get.flatMap @{
case comment: CommitComment => Some(comment)
case other => None
}.count(t => t.commitId == commit.id && !t.pullRequest)
}</span>
</td>
<td style="width: 10%; text-align: right;">
<a href="@url(repository)/commit/@commit.id" class="monospace">@commit.id.substring(0, 7)</a>
</td>
</tr>
}
}
</table>
</div>

View File

@@ -0,0 +1,155 @@
@(commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
members: List[(String, String)],
comments: List[gitbucket.core.model.Comment],
originId: String,
forkedId: String,
sourceId: String,
commitId: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
originRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
forkedRepository: gitbucket.core.service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main(s"Pull Requests - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("pulls", repository){
<div class="pullreq-info">
<div id="compare-info">
<a href="#" id="edit-compare-condition" class="btn btn-mini pull-right">Edit</a>
<span class="label label-info monospace">@originRepository.owner:@originId</span> ... <span class="label label-info monospace">@forkedRepository.owner:@forkedId</span>
</div>
<div id="compare-edit" style="display: none;">
<a href="#" id="cancel-condition-editing" class="pull-right"><i class="icon-remove-circle"></i></a>
@helper.html.dropdown(originRepository.owner + "/" + originRepository.name, "base fork") {
@members.map { case (owner, name) =>
<li><a href="#" class="origin-owner" data-owner="@owner" data-name="@name">@helper.html.checkicon(owner == originRepository.owner) @owner/@name</a></li>
}
}
@helper.html.dropdown(originId, "base") {
@originRepository.branchList.map { branch =>
<li><a href="#" class="origin-branch" data-branch="@encodeRefName(branch)">@helper.html.checkicon(branch == originId) @branch</a></li>
}
}
...
@helper.html.dropdown(forkedRepository.owner + "/" + forkedRepository.name, "head fork") {
@members.map { case (owner, name) =>
<li><a href="#" class="forked-owner" data-owner="@owner" data-name="@name">@helper.html.checkicon(owner == forkedRepository.owner) @owner/@name</a></li>
}
}
@helper.html.dropdown(forkedId, "compare") {
@forkedRepository.branchList.map { branch =>
<li><a href="#" class="forked-branch" data-branch="@encodeRefName(branch)">@helper.html.checkicon(branch == forkedId) @branch</a></li>
}
}
</div>
</div>
@if(commits.nonEmpty && hasWritePermission){
<div style="margin-bottom: 10px;" id="create-pull-request">
<a href="#" class="btn btn-success" id="show-form">Create pull request</a>
</div>
<div id="pull-request-form" class="box" style="display: none;">
<div class="box-content">
<form method="POST" action="@path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
<div style="width: 240px; position: absolute; margin-left: 610px;">
<div class="check-conflict" style="display: none;">
<img src="@assets/common/images/indicator.gif"/> Checking...
</div>
</div>
<div style="width: 600px; border-right: 1px solid #d4d4d4;">
<span class="error" id="error-title"></span>
<input type="text" name="title" style="width: 580px" placeholder="Title"/>
@helper.html.preview(repository, "", false, true, true, hasWritePermission, "width: 580px; height: 200px;")
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
<input type="hidden" name="targetBranch" value="@originId"/>
<input type="hidden" name="requestUserName" value="@forkedRepository.owner"/>
<input type="hidden" name="requestRepositoryName" value="@forkedRepository.name"/>
<input type="hidden" name="requestBranch" value="@forkedId"/>
<input type="hidden" name="commitIdFrom" value="@sourceId"/>
<input type="hidden" name="commitIdTo" value="@commitId"/>
</div>
</form>
</div>
</div>
}
@if(commits.isEmpty){
<table class="table table-bordered table-hover table-issues">
<tr>
<td style="padding: 20px; background-color: #eee; text-align: center;">
<h4>There isn't anything to compare.</h4>
<span class="strong">@originRepository.owner:@originId</span> and <span class="strong">@forkedRepository.owner:@forkedId</span> are identical.
</td>
</tr>
</table>
} else {
@pulls.html.commits(commits, Some(comments), repository)
@helper.html.diff(diffs, repository, Some(commitId), Some(sourceId), true, None, hasWritePermission, false)
<p>Showing you all comments on commits in this comparison.</p>
@issues.html.commentlist(None, comments, hasWritePermission, repository, None)
}
}
}
<script>
$(function(){
$('#edit-compare-condition').click(function(){
$('#compare-info').hide();
$('#compare-edit').show();
});
$('#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);
e.parents('ul').find('i').attr('class', 'icon-white');
e.find('i').attr('class', 'icon-ok');
e.parents('div.btn-group').find('button span.strong').text(e.text());
@if(members.isEmpty){
location.href = '@url(repository)/compare/' +
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'));
} else {
location.href = '@path/' +
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('owner')) + '/' +
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('name')) +'/compare/' +
$.trim($('i.icon-ok').parents('a.origin-owner' ).data('owner')) + ':' +
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')) + '...' +
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('owner')) + ':' +
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'));
}
});
$('#show-form').click(function(){
$(this).hide();
$('#pull-request-form').show();
});
if(location.search.substr(1).split("&").indexOf("expand=1")!=-1){
$('#show-form').click();
}
@if(hasWritePermission){
function checkConflict(from, to, noConflictHandler, hasConflictHandler){
$('.check-conflict').show();
$.get('@url(repository)/compare/' + from + '...' + to + '/mergecheck',
function(data){ $('.check-conflict').html(data); });
}
@if(members.isEmpty){
checkConflict(
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')),
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'))
);
} else {
checkConflict(
$.trim($('i.icon-ok').parents('a.origin-owner' ).data('owner')) + ":" +
$.trim($('i.icon-ok').parents('a.origin-branch').data('branch')),
$.trim($('i.icon-ok').parents('a.forked-owner' ).data('owner')) + ":" +
$.trim($('i.icon-ok').parents('a.forked-branch').data('branch'))
);
}
}
});
</script>

View File

@@ -0,0 +1,68 @@
@(issue: gitbucket.core.model.Issue,
pullreq: gitbucket.core.model.PullRequest,
comments: List[gitbucket.core.model.Comment],
issueLabels: List[gitbucket.core.model.Label],
collaborators: List[String],
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
labels: List[gitbucket.core.model.Label],
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.model._
<div class="row-fluid">
<div class="span10">
<div id="comment-list">
@issues.html.commentlist(Some(issue), comments, hasWritePermission, repository, Some(pullreq))
</div>
@defining(comments.flatMap {
case comment: gitbucket.core.model.IssueComment => Some(comment)
case other => None
}.exists(_.action == "merge")){ merged =>
@if(hasWritePermission && !issue.closed){
<div class="check-conflict" style="display: none;">
<div class="box issue-comment-box" style="background-color: #fbeed5">
<div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;">
<img src="@assets/common/images/indicator.gif"/> Checking...
</div>
</div>
</div>
}
@if(hasWritePermission && issue.closed && pullreq.userName == pullreq.requestUserName && merged &&
pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){
<div class="box issue-comment-box" style="background-color: #d0eeff;">
<div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;">
<a href="@url(repository)/pull/@issue.issueId/delete/@encodeRefName(pullreq.requestBranch)" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a>
<div>
<span class="strong">Pull request successfully merged and closed</span>
</div>
<span class="small muted">You're all set-the <span class="label label-info monospace">@pullreq.requestBranch</span> branch can be safely deleted.</span>
</div>
</div>
}
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
}
</div>
<div class="span2">
@issues.html.issueinfo(issue, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div>
</div>
<script>
$(function(){
$('#cancel-merge-pull-request').click(function(){
$('#confirm-merge-form').hide();
$('#merge-pull-request').show();
});
@if(hasWritePermission){
$('.check-conflict').show();
$.get('@url(repository)/pull/@issue.issueId/mergeguide', function(data){ $('.check-conflict').html(data); });
$('.delete-branch').click(function(e){
var branchName = $(e.target).data('name');
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
});
}
});
</script>

View File

@@ -0,0 +1,9 @@
@(hasConflict: Boolean)
@if(hasConflict){
<h4>We cant automatically merge these branches</h4>
<p>Don't worry, you can still submit the pull request.</p>
} else {
<h4 style="color: #468847;">Able to merge</h4>
<p>These branches can be automatically merged.</p>
}
<input type="submit" class="btn btn-success btn-block" value="Create pull request"/>

View File

@@ -0,0 +1,153 @@
@(hasConflict: Boolean,
hasProblem: Boolean,
issue: gitbucket.core.model.Issue,
pullreq: gitbucket.core.model.PullRequest,
statuses: List[model.CommitStatus],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
requestRepositoryUrl: String)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import model.CommitState
<div class="box issue-comment-box" style="background-color: @if(hasProblem){ #fbeed5 }else{ #d8f5cd };">
<div class="box-content"class="issue-content" style="border: 1px solid @if(hasProblem){ #c09853 }else{ #95c97e }; padding: 10px;">
<div id="merge-pull-request">
@if(!statuses.isEmpty){
<div class="build-statuses">
@if(statuses.size==1){
@defining(statuses.head){ status =>
<div class="build-status-item">
@status.targetUrl.map{ url => <a class="pull-right" href="@url">Details</a> }
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
<strong class="text-@{status.state.name}">@commitStateText(status.state, pullreq.commitIdTo)</strong>
@status.description.map{ desc => <span class="muted">— @desc</span> }
</div>
}
}else{
@defining(statuses.groupBy(_.state)){ stateMap => @defining(CommitState.combine(stateMap.keySet)){ state =>
<div class="build-status-item">
<a class="pull-right" id="toggle-all-checks"></a>
<span class="build-status-icon text-@{state.name}">@commitStateIcon(state)</span>
<strong class="text-@{state.name}">@commitStateText(state, pullreq.commitIdTo)</strong>
<span class="text-@{state.name}">— @{stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")} checks</span>
</div>
<div class="build-statuses-list" style="@if(state==CommitState.SUCCESS){ display:none; }else{ }">
@statuses.map{ status =>
<div class="build-status-item">
@status.targetUrl.map{ url => <a class="pull-right" href="@url">Details</a> }
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
<span class="text-@{status.state.name}">@status.context</span>
@status.description.map{ desc => <span class="muted">— @desc</span> }
</div>
}
</div>
} }
}
</div>
}
<div class="pull-right">
<input type="button" class="btn @if(!hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(hasConflict){ disabled="true"}/>
</div>
<div>
@if(hasConflict){
<span class="strong">We cant automatically merge this pull request.</span>
} else{ @if(hasProblem){
<span class="strong">Merge with caution!</span>
} else {
<span class="strong">This pull request can be automatically merged.</span>
} }
</div>
<div class="small">
@if(hasConflict){
<a href="#" id="show-command-line">Use the command line</a> to resolve conflicts before continuing.
} else {
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>
@if(hasConflict){
<span class="strong">Checkout via command line</span>
<p>
If you cannot merge a pull request automatically here, you have the option of checking
it out via command line to resolve conflicts and perform a manual merge.
</p>
} else {
<span class="strong">Merging via command line</span>
<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>
}
@helper.html.copy("repository-url-copy", requestRepositoryUrl){
<input type="text" style="width: 500px;" value="@requestRepositoryUrl" id="repository-url" readonly>
}
<div>
<p>
<span class="strong">Step 1:</span> Check out a new branch to test the changes — run this from your project directory
</p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-1", command){
<pre style="width: 500px; float: left;">@command</pre>
}
}
</div>
<div>
<p>
<span class="strong">Step 2:</span> Bring in @{pullreq.requestUserName}'s changes and test
</p>
@defining(s"git pull ${requestRepositoryUrl} ${pullreq.requestBranch}"){ command =>
@helper.html.copy("merge-command-copy-2", command){
<pre style="width: 500px; float: left;">@command</pre>
}
}
</div>
<div>
<p>
<span class="strong">Step 3:</span> Merge the changes and update the server
</p>
@defining(s"git checkout ${pullreq.branch}\ngit merge ${pullreq.requestUserName}-${pullreq.requestBranch}\ngit push origin ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-3", command){
<pre style="width: 500px; float: left;">@command</pre>
}
}
</div>
</div>
</div>
<div id="confirm-merge-form" style="display: none;">
<form method="POST" action="@url(repository)/pull/@pullreq.issueId/merge">
<div class="strong">
Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}
</div>
<span id="error-message" class="error"></span>
<textarea name="message" style="width: 635px; height: 80px;">@issue.title</textarea>
<div>
<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>
<script>
$(function(){
$('#show-command-line').click(function(){
$('#command-line').show();
return false;
});
function setToggleAllChecksLabel(){
$("#toggle-all-checks").text($('.build-statuses-list').is(":visible") ? "Hide all checks" : "Show all checks");
}
setToggleAllChecksLabel();
$('#toggle-all-checks').click(function(){
$('.build-statuses-list').toggle();
setToggleAllChecksLabel();
})
$('#merge-pull-request-button').click(function(){
$('#merge-pull-request').hide();
$('#confirm-merge-form').show();
});
});
</script>

View File

@@ -0,0 +1,125 @@
@(issue: gitbucket.core.model.Issue,
pullreq: gitbucket.core.model.PullRequest,
comments: List[gitbucket.core.model.Comment],
issueLabels: List[gitbucket.core.model.Label],
collaborators: List[String],
milestones: List[(gitbucket.core.model.Milestone, Int, Int)],
labels: List[gitbucket.core.model.Label],
dayByDayCommits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.model._
@html.main(s"${issue.title} - Pull Request #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("pulls", repository){
@defining(dayByDayCommits.flatten){ commits =>
<div>
<div class="show-title pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
<a class="btn btn-small" href="#" id="edit">Edit</a>
}
<a class="btn btn-small btn-success" href="@url(repository)/issues/new">New issue</a>
</div>
<div class="edit-title pull-right" style="display: none;">
<a class="btn" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a>
</div>
<h1>
<span class="show-title">
<span id="show-title">@issue.title</span>
<span class="muted">#@issue.issueId</span>
</span>
<span class="edit-title" style="display: none;">
<span id="error-edit-title" class="error"></span>
<input type="text" style="width: 700px;" id="edit-title" value="@issue.title"/>
</span>
</h1>
</div>
@if(issue.closed) {
@comments.flatMap @{
case comment: IssueComment => Some(comment)
case _ => None
}.find(_.action == "merge").map{ comment =>
<span class="label label-info issue-status">Merged</span>
<span class="muted">
@user(comment.commentedUserName, styleClass="username strong") merged @commits.size @plural(commits.size, "commit")
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
@helper.html.datetimeago(comment.registeredDate)
</span>
}.getOrElse {
<span class="label label-important issue-status">Closed</span>
<span class="muted">
@user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
</span>
}
} else {
<span class="label label-success issue-status">Open</span>
<span class="muted">
@user(issue.openedUserName, styleClass="username strong") wants to merge @commits.size @plural(commits.size, "commit")
into <code>@pullreq.userName:@pullreq.branch</code> from <code>@pullreq.requestUserName:@pullreq.requestBranch</code>
</span>
}
<br/><br/>
<ul class="nav nav-tabs fill-width pull-left" id="pullreq-tab">
<li class="active"><a href="#conversation">Conversation <span class="badge">@comments.flatMap @{
case comment: IssueComment => Some(comment)
case _: CommitComment => None
}.size</span></a></li>
<li><a href="#commits">Commits <span class="badge">@commits.size</span></a></li>
<li><a href="#files">Files Changed <span class="badge">@diffs.size</span></a></li>
</ul>
<div class="tab-content fill-width pull-left">
<div class="tab-pane active" id="conversation">
@pulls.html.conversation(issue, pullreq, comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
</div>
<div class="tab-pane" id="commits">
@pulls.html.commits(dayByDayCommits, Some(comments), repository)
</div>
<div class="tab-pane" id="files">
@helper.html.diff(diffs, repository, Some(commits.head.id), Some(commits.last.id), true, Some(pullreq.issueId), hasWritePermission, true)
</div>
</div>
}
}
}
<script>
$(function(){
$('#pullreq-tab a').click(function (e) {
e.preventDefault();
$(this).tab('show');
});
$('#edit').click(function(){
$('.edit-title').show();
$('.show-title').hide();
return false;
});
$('#update').click(function(){
$(this).attr('disabled', 'disabled');
$.ajax({
url: '@url(repository)/issues/edit_title/@issue.issueId',
type: 'POST',
data: {
title : $('#edit-title').val()
}
}).done(function(data){
$('#show-title').empty().text(data.title);
$('#cancel').click();
$(this).removeAttr('disabled');
}).fail(function(req){
$(this).removeAttr('disabled');
$('#error-edit-title').text($.parseJSON(req.responseText).title);
});
return false;
});
$('#cancel').click(function(){
$('.edit-title').hide();
$('.show-title').show();
return false;
});
});
</script>