mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 22:45:51 +01:00
Implementing file upload on the repository viewer
This commit is contained in:
@@ -31,6 +31,13 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
|
||||
}, FileUtil.isImage)
|
||||
}
|
||||
|
||||
post("/tmp"){
|
||||
execute({ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
}, _ => true)
|
||||
}
|
||||
|
||||
post("/file/:owner/:repository"){
|
||||
execute({ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.FileInputStream
|
||||
import java.io.File
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
@@ -18,14 +18,12 @@ import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import io.github.gitbucket.scalatra.forms._
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.errors.MissingObjectException
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import org.eclipse.jgit.treewalk._
|
||||
import org.scalatra._
|
||||
|
||||
|
||||
@@ -45,6 +43,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||
|
||||
case class UploadForm(
|
||||
branch: String,
|
||||
path: String,
|
||||
uploadFiles: String,
|
||||
message: Option[String]
|
||||
)
|
||||
|
||||
case class EditorForm(
|
||||
branch: String,
|
||||
path: String,
|
||||
@@ -71,6 +76,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
issueId: Option[Int]
|
||||
)
|
||||
|
||||
val uploadForm = mapping(
|
||||
"branch" -> trim(label("Branch", text(required))),
|
||||
"path" -> trim(label("Path", text())),
|
||||
"uploadFiles" -> trim(label("Upload files", text(required))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
)(UploadForm.apply)
|
||||
|
||||
val editorForm = mapping(
|
||||
"branch" -> trim(label("Branch", text(required))),
|
||||
"path" -> trim(label("Path", text())),
|
||||
@@ -173,10 +185,37 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
|
||||
None, JGitUtil.ContentInfo("text", None, None, Some("UTF-8")),
|
||||
protectedBranch)
|
||||
None, JGitUtil.ContentInfo("text", None, None, Some("UTF-8")), protectedBranch)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/upload/*")(writableUsersOnly { repository =>
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||
html.upload(branch, repository, if(path.length == 0) Nil else path.split("/").toList, protectedBranch)
|
||||
})
|
||||
|
||||
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
|
||||
val files = form.uploadFiles.split("\n").map { line =>
|
||||
val i = line.indexOf(":")
|
||||
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
|
||||
}
|
||||
|
||||
commitFiles(
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
path = form.path,
|
||||
files = files,
|
||||
message = form.message.getOrElse(s"Add files via upload")
|
||||
)
|
||||
|
||||
if(form.path.length == 0){
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}/${form.path}")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
get("/:owner/:repository/edit/*")(writableUsersOnly { repository =>
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
|
||||
@@ -547,65 +586,30 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Upload file to a branch.
|
||||
*/
|
||||
post("/:owner/:repository/files/upload")(writableUsersOnly { repository =>
|
||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||
(for {
|
||||
data <- extractFromJsonBody[UploadFiles] if data.isValid
|
||||
} yield {
|
||||
Directory.getAttachedDir(owner, name) match {
|
||||
case dir if (dir.exists && dir.isDirectory) =>
|
||||
val _commitFiles = data.fileIds.map { case (fileName, id) =>
|
||||
dir.listFiles.find(_.getName.startsWith(id + ".")).map { file =>
|
||||
CommitFile(id, fileName, using(new FileInputStream(file))(IOUtils.toByteArray))
|
||||
}
|
||||
}.toList
|
||||
|
||||
val finalCommitFiles = _commitFiles.flatten
|
||||
if(finalCommitFiles.size == data.fileIds.size) {
|
||||
commitFiles(
|
||||
repository,
|
||||
files = finalCommitFiles,
|
||||
branch = data.branch,
|
||||
path = data.path,
|
||||
message = data.message)
|
||||
}
|
||||
else {
|
||||
org.scalatra.NotAcceptable(
|
||||
s"""{"message":
|
||||
|"$repository doesn't contain all the files you specified in the body"}""".stripMargin)
|
||||
}
|
||||
|
||||
case _ => org.scalatra.NotFound(s"""{"message": "$repository doesn't contain any attached files"}""")
|
||||
}
|
||||
|
||||
}) getOrElse
|
||||
org.scalatra.NotAcceptable("""{"message": "FileIds can't be an empty list"}""")
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
case class UploadFiles(branch: String, path: String, fileIds : Map[String,String], message: String) {
|
||||
lazy val isValid: Boolean = fileIds.size > 0
|
||||
}
|
||||
|
||||
case class CommitFile(fileId: String, name: String, fileBytes: Array[Byte])
|
||||
case class CommitFile(id: String, name: String)
|
||||
|
||||
private def commitFiles(repository: RepositoryService.RepositoryInfo,
|
||||
files: List[CommitFile],
|
||||
files: Seq[CommitFile],
|
||||
branch: String, path: String, message: String) = {
|
||||
// prepend path to the filename
|
||||
val newFiles = files.map { file =>
|
||||
file.copy(name = if(path.length == 0) file.name else s"${path}/${file.name}")
|
||||
}
|
||||
|
||||
_commitFile(repository, branch, path, message) { case (git, headTip, builder, inserter) =>
|
||||
_commitFile(repository, branch, message) { case (git, headTip, builder, inserter) =>
|
||||
JGitUtil.processTree(git, headTip) { (path, tree) =>
|
||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||
if(!newFiles.exists(_.name.contains(path))) {
|
||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||
}
|
||||
}
|
||||
|
||||
files.foreach { item =>
|
||||
val fileName = item.name
|
||||
val bytes = item.fileBytes
|
||||
builder.add(JGitUtil.createDirCacheEntry(fileName,
|
||||
newFiles.foreach { file =>
|
||||
val bytes = FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), file.id))
|
||||
builder.add(JGitUtil.createDirCacheEntry(file.name,
|
||||
FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes)))
|
||||
builder.finish()
|
||||
}
|
||||
@@ -619,7 +623,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val newPath = newFileName.map { newFileName => if(path.length == 0) newFileName else s"${path}/${newFileName}" }
|
||||
val oldPath = oldFileName.map { oldFileName => if(path.length == 0) oldFileName else s"${path}/${oldFileName}" }
|
||||
|
||||
_commitFile(repository, branch, path, message){ case (git, headTip, builder, inserter) =>
|
||||
_commitFile(repository, branch, message){ case (git, headTip, builder, inserter) =>
|
||||
val permission = JGitUtil.processTree(git, headTip){ (path, tree) =>
|
||||
// Add all entries except the editing file
|
||||
if(!newPath.contains(path) && !oldPath.contains(path)){
|
||||
@@ -639,7 +643,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
private def _commitFile(repository: RepositoryService.RepositoryInfo,
|
||||
branch: String, path: String, message: String)(f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit) = {
|
||||
branch: String, message: String)(f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit) = {
|
||||
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
s"${(repository.name :: pathList).mkString("/")} at ${helpers.encodeRefName(branch)} - ${repository.owner}/${repository.name}"
|
||||
}, Some(repository)) {
|
||||
@gitbucket.core.html.menu("files", repository, Some(branch), info, error){
|
||||
<div class="head">
|
||||
<div class="head" style="height: 24px;">
|
||||
<div class="pull-right">
|
||||
<div class="btn-group">
|
||||
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t"><i class="octicon octicon-search"></i></a>
|
||||
@@ -67,27 +67,24 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@gitbucket.core.helper.html.branchcontrol(branch, repository, hasWritePermission){
|
||||
@repository.branchList.map { x =>
|
||||
<li><a href="@helpers.url(repository)/tree/@helpers.encodeRefName(x)">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
||||
<div class="pull-left">
|
||||
@gitbucket.core.helper.html.branchcontrol(branch, repository, hasWritePermission){
|
||||
@repository.branchList.map { x =>
|
||||
<li><a href="@helpers.url(repository)/tree/@helpers.encodeRefName(x)">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if(pathList.isEmpty){
|
||||
@*
|
||||
@branchPullRequest.map{ case (pullRequest, issue) =>
|
||||
<a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-sm btn-pullrequest-branch" title="@issue.title" data-toggle="tooltip">View #@pullRequest.issueId</a>
|
||||
}.getOrElse {
|
||||
<a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-sm btn-success" @if(loginAccount.isEmpty){disabled}>New pull request</a>
|
||||
@if(pathList.nonEmpty){
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)">@repository.name</a> /
|
||||
@pathList.zipWithIndex.map { case (section, i) =>
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
||||
}
|
||||
}
|
||||
*@
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)">@repository.name</a> /
|
||||
@pathList.zipWithIndex.map { case (section, i) =>
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<a href="@helpers.url(repository)/new/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here"><i class="octicon octicon-plus"></i></a>
|
||||
<div class="btn-group pull-left" style="margin-left: 4px;">
|
||||
<a href="@helpers.url(repository)/new/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file"><i class="octicon octicon-plus"></i></a>
|
||||
<a href="@helpers.url(repository)/upload/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Upload files"><i class="octicon octicon-cloud-upload"></i></a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
|
||||
103
src/main/twirl/gitbucket/core/repo/upload.scala.html
Normal file
103
src/main/twirl/gitbucket/core/repo/upload.scala.html
Normal file
@@ -0,0 +1,103 @@
|
||||
@(branch: String,
|
||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
pathList: List[String],
|
||||
protectedBranch: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@import gitbucket.core.util.FileUtil
|
||||
@gitbucket.core.html.main(s"Upload Files at ${branch} - ${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@gitbucket.core.html.menu("files", repository){
|
||||
@if(protectedBranch){
|
||||
<div class="alert alert-danger">branch @branch is protected.</div>
|
||||
}
|
||||
<form method="POST" action="@helpers.url(repository)/upload" id="upload-form">
|
||||
@*
|
||||
<span class="error" id="error-newFileName"></span>
|
||||
*@
|
||||
<div class="head">
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)">@repository.name</a> /
|
||||
@pathList.zipWithIndex.map { case (section, i) =>
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
|
||||
}
|
||||
<input type="hidden" name="branch" id="branch" value="@branch"/>
|
||||
<input type="hidden" name="path" id="path" value="@pathList.mkString("/")"/>
|
||||
</div>
|
||||
<table class="table table-bordered">
|
||||
<tr>
|
||||
<td style="padding: 0px; background-color: #f4f4f4; border: 1px #ddd solid;">
|
||||
<div id="upload-area" style="text-align: center; padding-top: 20px; padding-bottom: 20px; font-size: 120%;">
|
||||
Drag files here to add them to your repository
|
||||
</div>
|
||||
<ul id="upload-files">
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="panel panel-default issue-comment-box">
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<strong>Commit changes</strong>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="text" name="message" class="form-control"/>
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)/@{pathList.mkString("/")}" class="btn btn-danger">Cancel</a>
|
||||
<input type="submit" id="commit" class="btn btn-success" value="Commit changes" disabled="true"/>
|
||||
<input type="hidden" id="upload-files-data" name="uploadFiles" value=""/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
<style type="text/css">
|
||||
ul#upload-files {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
li.upload-file {
|
||||
border-top: 1px #f4f4f4 solid;
|
||||
background-color: white;
|
||||
padding: 4px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#upload-area').dropzone({
|
||||
url: '@context.path/upload/tmp',
|
||||
maxFilesize: 10,
|
||||
clickable: true,
|
||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||
success: function(file, id) {
|
||||
file.previewElement.remove();
|
||||
$('#upload-files').append($('<li class="upload-file">')
|
||||
.append($('<span>').data('id', id).text(file.name))
|
||||
.append($('<a class="delete" href="javascript:void(0);" style="margin-left: 4px;">(delete)</a>')));
|
||||
updateCommitButtonStatus();
|
||||
}
|
||||
});
|
||||
|
||||
$('#upload-form').submit(function(){
|
||||
try {
|
||||
var data = '';
|
||||
$.each($('li.upload-file span'), function(i, e){
|
||||
data = data + $(e).data('id') + ':' + $(e).text() + '\n';
|
||||
});
|
||||
$('#upload-files-data').val(data);
|
||||
} catch(e){
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', 'a.delete', function(e){
|
||||
$(e.target).parent().remove();
|
||||
updateCommitButtonStatus();
|
||||
});
|
||||
|
||||
function updateCommitButtonStatus(){
|
||||
$('#commit').attr('disabled', $('.upload-file').length == 0);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -38,9 +38,7 @@ h6 {
|
||||
|
||||
.octicon,.mega-octicon{
|
||||
color : #999;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
/*font-size: 14px;*/
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user