diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index eeedd1c..5db473f 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -57,7 +57,7 @@ // Redirect to dashboard httpResponse.sendRedirect(baseUrl + "/") } - } else if(path.startsWith("/git/")){ + } else if(path.startsWith("/git/") || path.startsWith("/git-lfs/")){ // Git repository chain.doFilter(request, response) } else { diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index e336a8e..8d07303 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -315,9 +315,9 @@ }.toMap response.setContentLength(attrs("size").toInt) - val hash = attrs("oid").split(":")(1) + val oid = attrs("oid").split(":")(1) - using(new FileInputStream(Directory.LfsHome + "/" + hash.substring(0, 2) + "/" + hash.substring(2, 4) + "/" + hash)){ in => + using(new FileInputStream(Directory.LfsHome + "/" + oid.substring(0, 2) + "/" + oid.substring(2, 4) + "/" + oid)){ in => IOUtils.copy(in, response.getOutputStream) } } else { diff --git a/src/main/scala/gitbucket/core/servlet/GitLfsTransferServlet.scala b/src/main/scala/gitbucket/core/servlet/GitLfsTransferServlet.scala new file mode 100644 index 0000000..afac430 --- /dev/null +++ b/src/main/scala/gitbucket/core/servlet/GitLfsTransferServlet.scala @@ -0,0 +1,78 @@ +package gitbucket.core.servlet + +import java.io.{File, FileInputStream, FileOutputStream} +import java.text.MessageFormat +import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse} + +import gitbucket.core.util.Directory +import org.apache.commons.io.{FileUtils, IOUtils} +import org.json4s.jackson.Serialization._ +import org.apache.http.HttpStatus +import gitbucket.core.util.ControlUtil._ + +/** + * Provides GitLFS Transfer API + * https://github.com/git-lfs/git-lfs/blob/master/docs/api/basic-transfers.md + */ +class GitLfsTransferServlet extends HttpServlet { + + private implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats + private val LongObjectIdLength = 32 + private val LongObjectIdStringLength = LongObjectIdLength * 2 + + override protected def doGet(req: HttpServletRequest, res: HttpServletResponse): Unit = { + for { + oid <- getObjectId(req, res) + } yield { + val file = new File(Directory.LfsHome + "/" + oid.substring(0, 2) + "/" + oid.substring(2, 4) + "/" + oid) + if(file.exists()){ + res.setStatus(HttpStatus.SC_OK) + res.setContentType("application/octet-stream") + res.setContentLength(file.length.toInt) + using(new FileInputStream(file), res.getOutputStream){ (in, out) => + IOUtils.copy(in, out) + out.flush() + } + } else { + sendError(res, HttpStatus.SC_NOT_FOUND, + MessageFormat.format("Object ''{0}'' not found", oid)) + } + } + } + + override protected def doPut(req: HttpServletRequest, res: HttpServletResponse): Unit = { + for { + oid <- getObjectId(req, res) + } yield { + val file = new File(Directory.LfsHome + "/" + oid.substring(0, 2) + "/" + oid.substring(2, 4) + "/" + oid) + FileUtils.forceMkdir(file.getParentFile) + using(req.getInputStream, new FileOutputStream(file)){ (in, out) => + IOUtils.copy(in, out) + } + res.setStatus(HttpStatus.SC_OK) + } + } + + private def getObjectId(req: HttpServletRequest, rsp: HttpServletResponse): Option[String] = { + val info: String = req.getPathInfo + val length: Int = 1 + LongObjectIdStringLength + if (info.length != length) { + sendError(rsp, HttpStatus.SC_UNPROCESSABLE_ENTITY, + MessageFormat.format("Invalid pathInfo ''{0}'' does not match ''/'{'SHA-256'}'''", info)) + None + } else { + Some(info.substring(1, length)) + } + } + + private def sendError(res: HttpServletResponse, status: Int, message: String): Unit = { + res.setStatus(status) + using(res.getWriter()){ out => + out.write(write(GitLfs.Error(message))) + out.flush() + } + } + +} + + diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 879ab5c..c0cf9db 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -61,6 +61,10 @@ } } + /** + * Provides GitLFS Batch API + * https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md + */ protected def serviceGitLfsBatchAPI(req: HttpServletRequest, res: HttpServletResponse): Unit = { val batchRequest = read[GitLfs.BatchRequest](req.getInputStream) val settings = loadSystemSettings() @@ -96,10 +100,10 @@ } res.setContentType("application/vnd.git-lfs+json") - - val out = res.getWriter - out.print(write(batchResponse)) - out.flush() + using(res.getWriter){ out => + out.print(write(batchResponse)) + out.flush() + } } } } @@ -317,4 +321,8 @@ expires_at: Date ) + case class Error( + message: String + ) + } \ No newline at end of file diff --git a/src/main/scala/gitbucket/core/util/ControlUtil.scala b/src/main/scala/gitbucket/core/util/ControlUtil.scala index a323c42..74569ac 100644 --- a/src/main/scala/gitbucket/core/util/ControlUtil.scala +++ b/src/main/scala/gitbucket/core/util/ControlUtil.scala @@ -20,6 +20,20 @@ } } + def using[A <% { def close(): Unit }, B <% { def close(): Unit }, C](resource1: A, resource2: B)(f: (A, B) => C): C = + try f(resource1, resource2) finally { + if(resource1 != null){ + ignoring(classOf[Throwable]) { + resource1.close() + } + } + if(resource2 != null){ + ignoring(classOf[Throwable]) { + resource2.close() + } + } + } + def using[T](git: Git)(f: Git => T): T = try f(git) finally git.getRepository.close() diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 9634066..36b904e 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -46,6 +46,17 @@ /git/* + + GitLfsTransferServlet + gitbucket.core.servlet.GitLfsTransferServlet + + + + GitLfsTransferServlet + /git-lfs/* + + +