diff --git a/src/main/scala/gitbucket/core/api/CreateAFile.scala b/src/main/scala/gitbucket/core/api/CreateAFile.scala new file mode 100644 index 0000000..5517354 --- /dev/null +++ b/src/main/scala/gitbucket/core/api/CreateAFile.scala @@ -0,0 +1,13 @@ +package gitbucket.core.api + +/** + * https://developer.github.com/v3/repos/contents/#create-a-file + */ +case class CreateAFile( + message: String, + content: String, + sha: Option[String], + branch: Option[String], + committer: Option[ApiPusher], + author: Option[ApiPusher] +) diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 291140a..39c4884 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -32,6 +32,7 @@ with CommitsService with CommitStatusService with RepositoryCreationService + with RepositoryCommitFileService with IssueCreationService with HandleCommentService with MergeService diff --git a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala index d0dae95..07cd144 100644 --- a/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/api/ApiRepositoryContentsControllerBase.scala @@ -1,7 +1,7 @@ package gitbucket.core.controller.api -import gitbucket.core.api.{ApiContents, JsonFormat} +import gitbucket.core.api.{ApiContents, ApiError, CreateAFile, JsonFormat} import gitbucket.core.controller.ControllerBase -import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService} +import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService} import gitbucket.core.util.Directory.getRepositoryDir import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList} import gitbucket.core.util._ @@ -11,7 +11,7 @@ import org.eclipse.jgit.api.Git trait ApiRepositoryContentsControllerBase extends ControllerBase { - self: ReferrerAuthenticator => + self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService => /* * i. Get the README @@ -101,16 +101,48 @@ } } /* - * iii. Create a file + * iii. Create a file or iv. Update a file * https://developer.github.com/v3/repos/contents/#create-a-file + * https://developer.github.com/v3/repos/contents/#update-a-file + * if sha is presented, update a file else create a file. * requested #2112 */ - /* - * iv. Update a file - * https://developer.github.com/v3/repos/contents/#update-a-file - * requested #2112 - */ + put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository => + JsonFormat(for { + data <- extractFromJsonBody[CreateAFile] + } yield { + val branch = data.branch.getOrElse(repository.repository.defaultBranch) + val commit = using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => + val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) + revCommit.name + } + val paths = multiParams("splat").head.split("/") + val path = paths.take(paths.size - 1).toList.mkString("/") + if (data.sha.isDefined && data.sha.get != commit) { + ApiError("The blob SHA is not matched.", Some("https://developer.github.com/v3/repos/contents/#update-a-file")) + } else { + val objectId = commitFile( + repository, + branch, + path, + Some(paths.last), + if (data.sha.isDefined) { + Some(paths.last) + } else { + None + }, + StringUtil.base64Decode(data.content), + data.message, + commit, + context.loginAccount.get, + data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName), + data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress) + ) + ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository)) + } + }) + }) /* * v. Delete a file diff --git a/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala b/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala index 55bf9fb..a1bc173 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCommitFileService.scala @@ -29,7 +29,7 @@ f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit )(implicit s: Session, c: JsonFormat.Context) = { // prepend path to the filename - _commitFile(repository, branch, message, loginAccount)(f) + _commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress)(f) } def commitFile( @@ -43,7 +43,35 @@ message: String, commit: String, loginAccount: Account - )(implicit s: Session, c: JsonFormat.Context) = { + )(implicit s: Session, c: JsonFormat.Context): ObjectId = { + commitFile( + repository, + branch, + path, + newFileName, + oldFileName, + if (content.nonEmpty) { content.getBytes(charset) } else { Array.emptyByteArray }, + message, + commit, + loginAccount, + loginAccount.fullName, + loginAccount.mailAddress + ) + } + + def commitFile( + repository: RepositoryService.RepositoryInfo, + branch: String, + path: String, + newFileName: Option[String], + oldFileName: Option[String], + content: Array[Byte], + message: String, + commit: String, + loginAccount: Account, + fullName: String, + mailAddress: String + )(implicit s: Session, c: JsonFormat.Context): ObjectId = { val newPath = newFileName.map { newFileName => if (path.length == 0) newFileName else s"${path}/${newFileName}" @@ -52,7 +80,7 @@ if (path.length == 0) oldFileName else s"${path}/${oldFileName}" } - _commitFile(repository, branch, message, loginAccount) { + _commitFile(repository, branch, message, loginAccount, fullName, mailAddress) { case (git, headTip, builder, inserter) => if (headTip.getName == commit) { val permission = JGitUtil @@ -70,7 +98,7 @@ 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)))) + } getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content))) } builder.finish() } @@ -81,10 +109,12 @@ repository: RepositoryService.RepositoryInfo, branch: String, message: String, - loginAccount: Account + loginAccount: Account, + committerName: String, + committerMailAddress: String )( f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit - )(implicit s: Session, c: JsonFormat.Context) = { + )(implicit s: Session, c: JsonFormat.Context): ObjectId = { LockUtil.lock(s"${repository.owner}/${repository.name}") { using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => @@ -101,8 +131,8 @@ headTip, builder.getDirCache.writeTree(inserter), headName, - loginAccount.fullName, - loginAccount.mailAddress, + committerName, + committerMailAddress, message ) @@ -114,7 +144,7 @@ // call post commit hook val error = PluginRegistry().getReceiveHooks.flatMap { hook => - hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName) + hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName) }.headOption error match { @@ -131,7 +161,7 @@ val refUpdate = git.getRepository.updateRef(headName) refUpdate.setNewObjectId(commitId) refUpdate.setForceUpdate(false) - refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) + refUpdate.setRefLogIdent(new PersonIdent(committerName, committerMailAddress)) refUpdate.update() // update pull request @@ -139,26 +169,25 @@ // record activity val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) - recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo)) + recordPushActivity(repository.owner, repository.name, committerName, branch, List(commitInfo)) // create issue comment by commit message createIssueComment(repository.owner, repository.name, commitInfo) // close issue by commit message if (branch == repository.repository.defaultBranch) { - closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach { - issueId => - getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => - callIssuesWebHook("closed", repository, issue, loginAccount) - PluginRegistry().getIssueHooks - .foreach(_.closedByCommitComment(issue, repository, message, loginAccount)) - } + closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId => + getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => + callIssuesWebHook("closed", repository, issue, loginAccount) + PluginRegistry().getIssueHooks + .foreach(_.closedByCommitComment(issue, repository, message, loginAccount)) + } } } // call post commit hook PluginRegistry().getReceiveHooks.foreach { hook => - hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName) + hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName) } val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) @@ -177,6 +206,7 @@ } } } + commitId } } }