Merge pull request #512 from mrkm4ntr/create-branch-ui

(refs #394) Create branch from Web UI
This commit is contained in:
Naoki Takezoe
2014-11-01 03:10:38 +09:00
10 changed files with 150 additions and 20 deletions

View File

@@ -114,7 +114,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository, repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) => logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}, page, hasNext) }, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
case Left(_) => NotFound case Left(_) => NotFound
} }
} }
@@ -241,6 +241,24 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
}) })
/**
* Creates a branch.
*/
post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
val newBranchName = params.getOrElse("new", halt(400))
val fromBranchName = params.getOrElse("from", halt(400))
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.createBranch(git, fromBranchName, newBranchName)
} match {
case Right(message) =>
flash += "info" -> message
redirect(s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}")
case Left(message) =>
flash += "error" -> message
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
}
})
/** /**
* Deletes branch. * Deletes branch.
*/ */
@@ -333,7 +351,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repo.html.files(revision, repository, repo.html.files(revision, repository,
if(path == ".") Nil else path.split("/").toList, // current path if(path == ".") Nil else path.split("/").toList, // current path
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount)) files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
flash.get("info"), flash.get("error"))
} }
} getOrElse NotFound } getOrElse NotFound
} }

View File

@@ -14,7 +14,7 @@ import org.eclipse.jgit.treewalk.filter._
import org.eclipse.jgit.diff.DiffEntry.ChangeType import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException} import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
import java.util.Date import java.util.Date
import org.eclipse.jgit.api.errors.NoHeadException import org.eclipse.jgit.api.errors.{JGitInternalException, InvalidRefNameException, RefAlreadyExistsException, NoHeadException}
import service.RepositoryService import service.RepositoryService
import org.eclipse.jgit.dircache.DirCacheEntry import org.eclipse.jgit.dircache.DirCacheEntry
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -507,6 +507,17 @@ object JGitUtil {
}.find(_._1 != null) }.find(_._1 != null)
} }
def createBranch(git: Git, fromBranch: String, newBranch: String) = {
try {
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
Right("Branch created.")
} catch {
case e: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
// JGitInternalException occurs when new branch name is 'a' and the branch whose name is 'a/*' exists.
case _: InvalidRefNameException | _: JGitInternalException => Left("Sorry, that name is invalid.")
}
}
def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = { def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = {
val entry = new DirCacheEntry(path) val entry = new DirCacheEntry(path)
entry.setFileMode(mode) entry.setFileMode(mode)

View File

@@ -0,0 +1,62 @@
@(branch: String = "",
repository: service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean)(body: Html)(implicit context: app.Context)
@import context._
@import view.helpers._
@helper.html.dropdown(
value = if(branch.length == 40) branch.substring(0, 10) else branch,
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
mini = true
) {
<li><div id="branch-control-title">Switch branches<button id="branch-control-close" class="pull-right">&times</button></div></li>
<li><input id="branch-control-input" type="text" placeholder="Find or create branch ..."/></li>
@body
@if(hasWritePermission) {
<li id="create-branch" style="display: none;">
<a><form action="@url(repository)/branches" method="post" style="margin: 0;">
<span class="new-branch-name">Create branch:&nbsp;<span class="new-branch"></span></span>
<br><span style="padding-left: 17px;">from&nbsp;'@branch'</span>
<input type="hidden" name="new">
<input type="hidden" name="from" value="@branch">
</form></a>
</li>
}
}
<script>
$(function(){
$('#branch-control-input').parent().click(function(e) {
e.stopPropagation();
});
$('#branch-control-close').click(function() {
$('[data-toggle="dropdown"]').parent().removeClass('open');
});
$('#branch-control-input').keyup(function() {
var inputVal = $('#branch-control-input').val();
$.each($('#branch-control-input').parent().parent().find('a'), function(index, elem) {
if (!inputVal || !elem.text.trim() || elem.text.trim().lastIndexOf(inputVal, 0) >= 0) {
$(elem).parent().show();
} else {
$(elem).parent().hide();
}
});
@if(hasWritePermission) {
if (inputVal) {
$('#create-branch').parent().find('li:last-child').show().find('.new-branch').text(inputVal);
} else {
$('#create-branch').parent().find('li:last-child').hide();
}
}
});
@if(hasWritePermission) {
$('#create-branch').click(function() {
$(this).find('input[name="new"]').val($('.dropdown-menu input').val())
$(this).find('form').submit()
});
}
$('.btn-group').click(function() {
$('#branch-control-input').val('');
$('.dropdown-menu li').show();
$('#create-branch').hide();
});
});
</script>

View File

@@ -0,0 +1,7 @@
@(error: Option[Any])
@if(error.isDefined){
<div class='alert alert-danger'>
<button type="button" class="close" data-dismiss="alert">&times;</button>
@error
</div>
}

View File

@@ -1,7 +1,7 @@
@(info: Option[Any]) @(info: Option[Any])
@if(info.isDefined){ @if(info.isDefined){
<div class="alert alert-info"> <div class="alert alert-info">
<button type="button" class="close" data-dismiss="alert">×</button> <button type="button" class="close" data-dismiss="alert">&times;</button>
@info @info
</div> </div>
} }

View File

@@ -1,7 +1,9 @@
@(active: String, @(active: String,
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
id: Option[String] = None, id: Option[String] = None,
expand: Boolean = false)(body: Html)(implicit context: app.Context) expand: Boolean = false,
info: Option[Any] = None,
error: Option[Any] = None)(body: Html)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@@ -31,6 +33,8 @@
} }
<div class="container"> <div class="container">
@helper.html.information(info)
@helper.html.error(error)
@if(repository.commitCount > 0){ @if(repository.commitCount > 0){
<div class="pull-right"> <div class="pull-right">
<div class="input-prepend"> <div class="input-prepend">

View File

@@ -9,10 +9,10 @@
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("code", repository){
<div class="head"> <div class="head">
@helper.html.dropdown( @helper.html.branchcontrol(
value = if(branch.length == 40) branch.substring(0, 10) else branch, branch,
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree", repository,
mini = true hasWritePermission
){ ){
@repository.branchList.map { x => @repository.branchList.map { x =>
<li><a href="@url(repository)/blob/@encodeRefName(x)/@pathList.mkString("/")">@helper.html.checkicon(x == branch) @x</a></li> <li><a href="@url(repository)/blob/@encodeRefName(x)/@pathList.mkString("/")">@helper.html.checkicon(x == branch) @x</a></li>

View File

@@ -3,16 +3,17 @@
repository: service.RepositoryService.RepositoryInfo, repository: service.RepositoryService.RepositoryInfo,
commits: Seq[Seq[util.JGitUtil.CommitInfo]], commits: Seq[Seq[util.JGitUtil.CommitInfo]],
page: Int, page: Int,
hasNext: Boolean)(implicit context: app.Context) hasNext: Boolean,
hasWritePermission: Boolean)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository){ @html.menu("code", repository){
<div class="head"> <div class="head">
@helper.html.dropdown( @helper.html.branchcontrol(
value = if(branch.length == 40) branch.substring(0, 10) else branch, branch,
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree", repository,
mini = true hasWritePermission
){ ){
@repository.branchList.map { x => @repository.branchList.map { x =>
<li><a href="@url(repository)/commits/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li> <li><a href="@url(repository)/commits/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>

View File

@@ -4,16 +4,18 @@
latestCommit: util.JGitUtil.CommitInfo, latestCommit: util.JGitUtil.CommitInfo,
files: List[util.JGitUtil.FileInfo], files: List[util.JGitUtil.FileInfo],
readme: Option[(List[String], String)], readme: Option[(List[String], String)],
hasWritePermission: Boolean)(implicit context: app.Context) hasWritePermission: Boolean,
info: Option[Any] = None,
error: Option[Any] = None)(implicit context: app.Context)
@import context._ @import context._
@import view.helpers._ @import view.helpers._
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@html.menu("code", repository, Some(branch), pathList.isEmpty){ @html.menu("code", repository, Some(branch), pathList.isEmpty, info, error){
<div class="head"> <div class="head">
@helper.html.dropdown( @helper.html.branchcontrol(
value = if(branch.length == 40) branch.substring(0, 10) else branch, branch,
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree", repository,
mini = true hasWritePermission
){ ){
@repository.branchList.map { x => @repository.branchList.map { x =>
<li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li> <li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>

View File

@@ -617,6 +617,30 @@ span.simplified-path {
color: #888; color: #888;
} }
#branch-control-title {
margin: 5px 10px;
font-weight: bold;
}
#branch-control-close {
background: none;
border: none;
color: #aaa;
font-weight: bold;
line-height: 15px;
}
#branch-control-input {
border: solid 1px #ccc;
margin: 10px;
}
.new-branch-name {
font-weight: bold;
font-size: 1.2em;
padding-left: 16px;
}
/****************************************************************************/ /****************************************************************************/
/* nav pulls group */ /* nav pulls group */
/****************************************************************************/ /****************************************************************************/