diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index a6a460a..e336a8e 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -1,6 +1,7 @@ package gitbucket.core.controller -import javax.servlet.http.{HttpServletResponse, HttpServletRequest} +import java.io.FileInputStream +import javax.servlet.http.{HttpServletRequest, HttpServletResponse} import gitbucket.core.plugin.PluginRegistry import gitbucket.core.repo.html @@ -16,9 +17,8 @@ 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.FileUtils +import org.apache.commons.io.{FileUtils, IOUtils} import org.eclipse.jgit.api.{ArchiveCommand, Git} import org.eclipse.jgit.archive.{TgzFormat, ZipFormat} import org.eclipse.jgit.dircache.DirCache @@ -255,13 +255,9 @@ val (id, path) = repository.splitPath(multiParams("splat").head) using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) - getPathObjectId(git, path, revCommit).flatMap { objectId => - JGitUtil.getObjectLoaderFromId(git, objectId){ loader => - contentType = FileUtil.getMimeType(path) - response.setContentLength(loader.getSize.toInt) - loader.copyTo(response.outputStream) - () - } + + getPathObjectId(git, path, revCommit).map { objectId => + responseRawFile(git, objectId, path) } getOrElse NotFound() } }) @@ -277,23 +273,61 @@ getPathObjectId(git, path, revCommit).map { objectId => if(raw){ // Download (This route is left for backword compatibility) - JGitUtil.getObjectLoaderFromId(git, objectId){ loader => - contentType = FileUtil.getMimeType(path) - response.setContentLength(loader.getSize.toInt) - loader.copyTo(response.outputStream) - () - } getOrElse NotFound() + responseRawFile(git, objectId, path) } else { html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId), new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)), hasDeveloperRole(repository.owner, repository.name, context.loginAccount), - request.paths(2) == "blame") + request.paths(2) == "blame", + isLfsFile(git, objectId)) } } getOrElse NotFound() } }) + private def isLfsFile(git: Git, objectId: ObjectId): Boolean = { + JGitUtil.getObjectLoaderFromId(git, objectId){ loader => + if(loader.isLarge){ + false + } else { + new String(loader.getCachedBytes, "UTF-8").startsWith("version https://git-lfs.github.com/spec/v1") + } + }.getOrElse(false) + } + + private def responseRawFile(git: Git, objectId: ObjectId, path: String): Unit = { + JGitUtil.getObjectLoaderFromId(git, objectId){ loader => + contentType = FileUtil.getMimeType(path) + + if(loader.isLarge || context.settings.lfs.serverUrl.isEmpty){ + response.setContentLength(loader.getSize.toInt) + loader.copyTo(response.outputStream) + } else { + val bytes = loader.getCachedBytes + val text = new String(bytes, "UTF-8") + + if(text.startsWith("version https://git-lfs.github.com/spec/v1")){ + // LFS objects + val attrs = text.split("\n").map { line => + val dim = line.split(" ") + dim(0) -> dim(1) + }.toMap + + response.setContentLength(attrs("size").toInt) + val hash = attrs("oid").split(":")(1) + + using(new FileInputStream(Directory.LfsHome + "/" + hash.substring(0, 2) + "/" + hash.substring(2, 4) + "/" + hash)){ in => + IOUtils.copy(in, response.getOutputStream) + } + } else { + response.setContentLength(loader.getSize.toInt) + response.getOutputStream.write(bytes) + } + } + } + } + get("/:owner/:repository/blame/*"){ blobRoute.action() } diff --git a/src/main/scala/gitbucket/core/util/Directory.scala b/src/main/scala/gitbucket/core/util/Directory.scala index a1d603e..6713443 100644 --- a/src/main/scala/gitbucket/core/util/Directory.scala +++ b/src/main/scala/gitbucket/core/util/Directory.scala @@ -36,6 +36,8 @@ val TemporaryHome = s"${GitBucketHome}/tmp" + val LfsHome = s"${GitBucketHome}/lfs" + /** * Substance directory of the repository. */ diff --git a/src/main/twirl/gitbucket/core/repo/blob.scala.html b/src/main/twirl/gitbucket/core/repo/blob.scala.html index 9ae999d..e5a8e63 100644 --- a/src/main/twirl/gitbucket/core/repo/blob.scala.html +++ b/src/main/twirl/gitbucket/core/repo/blob.scala.html @@ -4,7 +4,8 @@ content: gitbucket.core.util.JGitUtil.ContentInfo, latestCommit: gitbucket.core.util.JGitUtil.CommitInfo, hasWritePermission: Boolean, - isBlame: Boolean)(implicit context: gitbucket.core.controller.Context) + isBlame: Boolean, + isLfsFile: Boolean)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @gitbucket.core.html.main(s"${(repository.name :: pathList).mkString("/")} at ${helpers.encodeRefName(branch)} - ${repository.owner}/${repository.name}", Some(repository)) { @gitbucket.core.html.menu("files", repository){ @@ -45,6 +46,9 @@ @section / } } + @if(isLfsFile){ + LFS + }