diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 6fc90d7..a45c5bb 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -21,7 +21,7 @@ import org.apache.commons.io.IOUtils import org.eclipse.jgit.api.{ArchiveCommand, Git} import org.eclipse.jgit.archive.{TgzFormat, ZipFormat} -import org.eclipse.jgit.dircache.DirCache +import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder} import org.eclipse.jgit.errors.MissingObjectException import org.eclipse.jgit.lib._ import org.eclipse.jgit.revwalk.RevCommit @@ -547,6 +547,149 @@ } }) + /** + * 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]) + + private def commitFiles(repository: RepositoryService.RepositoryInfo, + files: List[CommitFile], + branch: String, path: String, message: String) = { + + _commitFile(repository, branch, path, message) { case (git, headTip, builder, inserter) => + JGitUtil.processTree(git, headTip) { (path, tree) => + 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, + FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))) + builder.finish() + } + } + } + + private def commitFile(repository: RepositoryService.RepositoryInfo, + branch: String, path: String, newFileName: Option[String], oldFileName: Option[String], + content: String, charset: String, message: String) = { + + 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) => + val permission = JGitUtil.processTree(git, headTip){ (path, tree) => + // Add all entries except the editing file + if(!newPath.contains(path) && !oldPath.contains(path)){ + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } + // Retrieve permission if file exists to keep it + oldPath.collect { case x if x == path => tree.getEntryFileMode.getBits } + }.flatten.headOption + + newPath.foreach { newPath => + builder.add(JGitUtil.createDirCacheEntry(newPath, + permission.map { bits => FileMode.fromBits(bits) } getOrElse FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset)))) + } + builder.finish() + } + } + + private def _commitFile(repository: RepositoryService.RepositoryInfo, + branch: String, path: 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 => + val loginAccount = context.loginAccount.get + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headName = s"refs/heads/${branch}" + val headTip = git.getRepository.resolve(headName) + + f(git, headTip, builder, inserter) + + val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), + headName, loginAccount.userName, loginAccount.mailAddress, message) + + inserter.flush() + inserter.close() + + // update refs + val refUpdate = git.getRepository.updateRef(headName) + refUpdate.setNewObjectId(commitId) + refUpdate.setForceUpdate(false) + refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) + refUpdate.update() + + // update pull request + updatePullRequests(repository.owner, repository.name, branch) + + // record activity + val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) + recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo)) + + // create issue comment by commit message + createIssueComment(repository.owner, repository.name, commitInfo) + + // close issue by commit message + closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name) + + //call web hook + callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount) + val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) + callWebHookOf(repository.owner, repository.name, WebHook.Push) { + getAccountByUserName(repository.owner).map{ ownerAccount => + WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount, + oldId = headTip, newId = commitId) + } + } + } + } + } + private val readmeFiles = PluginRegistry().renderableExtensions.map { extension => s"readme.${extension}" } ++ Seq("readme.txt", "readme") @@ -597,84 +740,13 @@ } } - private def commitFile(repository: RepositoryService.RepositoryInfo, - branch: String, path: String, newFileName: Option[String], oldFileName: Option[String], - content: String, charset: String, message: String) = { - - 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}" } - - LockUtil.lock(s"${repository.owner}/${repository.name}"){ - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val loginAccount = context.loginAccount.get - val builder = DirCache.newInCore.builder() - val inserter = git.getRepository.newObjectInserter() - val headName = s"refs/heads/${branch}" - val headTip = git.getRepository.resolve(headName) - - val permission = JGitUtil.processTree(git, headTip){ (path, tree) => - // Add all entries except the editing file - if(!newPath.contains(path) && !oldPath.contains(path)){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } - // Retrieve permission if file exists to keep it - oldPath.collect { case x if x == path => tree.getEntryFileMode.getBits } - }.flatten.headOption - - newPath.foreach { newPath => - builder.add(JGitUtil.createDirCacheEntry(newPath, - permission.map { bits => FileMode.fromBits(bits) } getOrElse FileMode.REGULAR_FILE, - inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset)))) - } - builder.finish() - - val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), - headName, loginAccount.fullName, loginAccount.mailAddress, message) - - inserter.flush() - inserter.close() - - // update refs - val refUpdate = git.getRepository.updateRef(headName) - refUpdate.setNewObjectId(commitId) - refUpdate.setForceUpdate(false) - refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) - //refUpdate.setRefLogMessage("merged", true) - refUpdate.update() - - // update pull request - updatePullRequests(repository.owner, repository.name, branch) - - // record activity - val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) - recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo)) - - // create issue comment by commit message - createIssueComment(repository.owner, repository.name, commitInfo) - - // close issue by commit message - closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name) - - // call web hook - callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount) - val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) - callWebHookOf(repository.owner, repository.name, WebHook.Push) { - getAccountByUserName(repository.owner).map{ ownerAccount => - WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount, - oldId = headTip, newId = commitId) - } - } - } - } - } - private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): Unit = { val revision = name.stripSuffix(suffix) - + using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => val oid = git.getRepository.resolve(revision) val revCommit = JGitUtil.getRevCommitFromId(git, oid) - val sha1 = oid.getName() + val sha1 = oid.getName() val repositorySuffix = (if(sha1.startsWith(revision)) sha1 else revision).replace('/','-') val filename = repository.name + "-" + repositorySuffix + suffix