diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index 08458b5..5888f4e 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,4 +1,3 @@ - import java.util.EnumSet import javax.servlet._ @@ -9,28 +8,35 @@ import gitbucket.core.util.Directory import org.scalatra._ - class ScalatraBootstrap extends LifeCycle with SystemSettingsService { override def init(context: ServletContext) { val settings = loadSystemSettings() - if(settings.baseUrl.exists(_.startsWith("https://"))) { + if (settings.baseUrl.exists(_.startsWith("https://"))) { context.getSessionCookieConfig.setSecure(true) } // Register TransactionFilter and BasicAuthenticationFilter at first context.addFilter("transactionFilter", new TransactionFilter) - context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") + context + .getFilterRegistration("transactionFilter") + .addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") context.addFilter("gitAuthenticationFilter", new GitAuthenticationFilter) - context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") + context + .getFilterRegistration("gitAuthenticationFilter") + .addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter) - context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*") + context + .getFilterRegistration("apiAuthenticationFilter") + .addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*") // Register controllers context.mount(new PreProcessController, "/*") context.addFilter("pluginControllerFilter", new PluginControllerFilter) - context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") + context + .getFilterRegistration("pluginControllerFilter") + .addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") context.mount(new FileUploadController, "/upload") @@ -51,11 +57,13 @@ filter.mount(new RepositorySettingsController, "/*") context.addFilter("compositeScalatraFilter", filter) - context.getFilterRegistration("compositeScalatraFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") + context + .getFilterRegistration("compositeScalatraFilter") + .addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") // Create GITBUCKET_HOME directory if it does not exist val dir = new java.io.File(Directory.GitBucketHome) - if(!dir.exists){ + if (!dir.exists) { dir.mkdirs() } } diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index b81132a..4c2a3c4 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -3,61 +3,52 @@ import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration} import io.github.gitbucket.solidbase.model.{Version, Module} -object GitBucketCoreModule extends Module("gitbucket-core", - new Version("4.0.0", - new LiquibaseMigration("update/gitbucket-core_4.0.xml"), - new SqlMigration("update/gitbucket-core_4.0.sql") - ), - new Version("4.1.0"), - new Version("4.2.0", - new LiquibaseMigration("update/gitbucket-core_4.2.xml") - ), - new Version("4.2.1"), - new Version("4.3.0"), - new Version("4.4.0"), - new Version("4.5.0"), - new Version("4.6.0", - new LiquibaseMigration("update/gitbucket-core_4.6.xml") - ), - new Version("4.7.0", - new LiquibaseMigration("update/gitbucket-core_4.7.xml"), - new SqlMigration("update/gitbucket-core_4.7.sql") - ), - new Version("4.7.1"), - new Version("4.8"), - new Version("4.9.0", - new LiquibaseMigration("update/gitbucket-core_4.9.xml") - ), - new Version("4.10.0"), - new Version("4.11.0", - new LiquibaseMigration("update/gitbucket-core_4.11.xml") - ), - new Version("4.12.0"), - new Version("4.12.1"), - new Version("4.13.0"), - new Version("4.14.0", - new LiquibaseMigration("update/gitbucket-core_4.14.xml"), - new SqlMigration("update/gitbucket-core_4.14.sql") - ), - new Version("4.14.1"), - new Version("4.15.0"), - new Version("4.16.0"), - new Version("4.17.0"), - new Version("4.18.0"), - new Version("4.19.0"), - new Version("4.19.1"), - new Version("4.19.2"), - new Version("4.19.3"), - new Version("4.20.0"), - new Version("4.21.0", - new LiquibaseMigration("update/gitbucket-core_4.21.xml") - ), - new Version("4.21.1"), - new Version("4.21.2"), - new Version("4.22.0", - new LiquibaseMigration("update/gitbucket-core_4.22.xml") - ), - new Version("4.23.0", - new LiquibaseMigration("update/gitbucket-core_4.23.xml") - ) -) +object GitBucketCoreModule + extends Module( + "gitbucket-core", + new Version( + "4.0.0", + new LiquibaseMigration("update/gitbucket-core_4.0.xml"), + new SqlMigration("update/gitbucket-core_4.0.sql") + ), + new Version("4.1.0"), + new Version("4.2.0", new LiquibaseMigration("update/gitbucket-core_4.2.xml")), + new Version("4.2.1"), + new Version("4.3.0"), + new Version("4.4.0"), + new Version("4.5.0"), + new Version("4.6.0", new LiquibaseMigration("update/gitbucket-core_4.6.xml")), + new Version( + "4.7.0", + new LiquibaseMigration("update/gitbucket-core_4.7.xml"), + new SqlMigration("update/gitbucket-core_4.7.sql") + ), + new Version("4.7.1"), + new Version("4.8"), + new Version("4.9.0", new LiquibaseMigration("update/gitbucket-core_4.9.xml")), + new Version("4.10.0"), + new Version("4.11.0", new LiquibaseMigration("update/gitbucket-core_4.11.xml")), + new Version("4.12.0"), + new Version("4.12.1"), + new Version("4.13.0"), + new Version( + "4.14.0", + new LiquibaseMigration("update/gitbucket-core_4.14.xml"), + new SqlMigration("update/gitbucket-core_4.14.sql") + ), + new Version("4.14.1"), + new Version("4.15.0"), + new Version("4.16.0"), + new Version("4.17.0"), + new Version("4.18.0"), + new Version("4.19.0"), + new Version("4.19.1"), + new Version("4.19.2"), + new Version("4.19.3"), + new Version("4.20.0"), + new Version("4.21.0", new LiquibaseMigration("update/gitbucket-core_4.21.xml")), + new Version("4.21.1"), + new Version("4.21.2"), + new Version("4.22.0", new LiquibaseMigration("update/gitbucket-core_4.22.xml")), + new Version("4.23.0", new LiquibaseMigration("update/gitbucket-core_4.23.xml")) + ) diff --git a/src/main/scala/gitbucket/core/api/ApiBranch.scala b/src/main/scala/gitbucket/core/api/ApiBranch.scala index faee455..a33f7a9 100644 --- a/src/main/scala/gitbucket/core/api/ApiBranch.scala +++ b/src/main/scala/gitbucket/core/api/ApiBranch.scala @@ -6,13 +6,14 @@ * https://developer.github.com/v3/repos/#get-branch * https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */ -case class ApiBranch( - name: String, - commit: ApiBranchCommit, - protection: ApiBranchProtection)(repositoryName:RepositoryName) extends FieldSerializable { - def _links = Map( - "self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"), - "html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}")) +case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)( + repositoryName: RepositoryName +) extends FieldSerializable { + def _links = + Map( + "self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"), + "html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}") + ) } case class ApiBranchCommit(sha: String) diff --git a/src/main/scala/gitbucket/core/api/ApiBranchProtection.scala b/src/main/scala/gitbucket/core/api/ApiBranchProtection.scala index dc452eb..0b7fae4 100644 --- a/src/main/scala/gitbucket/core/api/ApiBranchProtection.scala +++ b/src/main/scala/gitbucket/core/api/ApiBranchProtection.scala @@ -4,17 +4,22 @@ import org.json4s._ /** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */ -case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]){ +case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]) { def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone) } -object ApiBranchProtection{ +object ApiBranchProtection { + /** form for enabling-and-disabling-branch-protection */ case class EnablingAndDisabling(protection: ApiBranchProtection) - def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection( - enabled = info.enabled, - required_status_checks = Some(Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts))) + def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = + ApiBranchProtection( + enabled = info.enabled, + required_status_checks = Some( + Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts) + ) + ) val statusNone = Status(Off, Seq.empty) case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String]) sealed class EnforcementLevel(val name: String) @@ -22,25 +27,28 @@ case object NonAdmins extends EnforcementLevel("non_admins") case object Everyone extends EnforcementLevel("everyone") object EnforcementLevel { - def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel = if(enabled){ - if(includeAdministrators){ - Everyone - }else{ - NonAdmins + def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel = + if (enabled) { + if (includeAdministrators) { + Everyone + } else { + NonAdmins + } + } else { + Off } - }else{ - Off - } } - implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](format => ( - { - case JString("off") => Off - case JString("non_admins") => NonAdmins - case JString("everyone") => Everyone - }, - { - case x: EnforcementLevel => JString(x.name) - } - )) + implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel]( + format => + ( + { + case JString("off") => Off + case JString("non_admins") => NonAdmins + case JString("everyone") => Everyone + }, { + case x: EnforcementLevel => JString(x.name) + } + ) + ) } diff --git a/src/main/scala/gitbucket/core/api/ApiCombinedCommitStatus.scala b/src/main/scala/gitbucket/core/api/ApiCombinedCommitStatus.scala index 6a2ccf8..d10d55b 100644 --- a/src/main/scala/gitbucket/core/api/ApiCombinedCommitStatus.scala +++ b/src/main/scala/gitbucket/core/api/ApiCombinedCommitStatus.scala @@ -2,7 +2,6 @@ import gitbucket.core.model.{Account, CommitState, CommitStatus} - /** * https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref */ @@ -11,15 +10,22 @@ sha: String, total_count: Int, statuses: Iterable[ApiCommitStatus], - repository: ApiRepository){ + repository: ApiRepository +) { // val commit_url = ApiPath(s"/api/v3/repos/${repository.full_name}/${sha}") val url = ApiPath(s"/api/v3/repos/${repository.full_name}/commits/${sha}/status") } object ApiCombinedCommitStatus { - def apply(sha:String, statuses: Iterable[(CommitStatus, Account)], repository:ApiRepository): ApiCombinedCommitStatus = ApiCombinedCommitStatus( - state = CommitState.combine(statuses.map(_._1.state).toSet).name, - sha = sha, - total_count= statuses.size, - statuses = statuses.map{ case (s, a)=> ApiCommitStatus(s, ApiUser(a)) }, - repository = repository) + def apply( + sha: String, + statuses: Iterable[(CommitStatus, Account)], + repository: ApiRepository + ): ApiCombinedCommitStatus = + ApiCombinedCommitStatus( + state = CommitState.combine(statuses.map(_._1.state).toSet).name, + sha = sha, + total_count = statuses.size, + statuses = statuses.map { case (s, a) => ApiCommitStatus(s, ApiUser(a)) }, + repository = repository + ) } diff --git a/src/main/scala/gitbucket/core/api/ApiComment.scala b/src/main/scala/gitbucket/core/api/ApiComment.scala index 62bcd3c..89027af 100644 --- a/src/main/scala/gitbucket/core/api/ApiComment.scala +++ b/src/main/scala/gitbucket/core/api/ApiComment.scala @@ -5,25 +5,32 @@ import java.util.Date - /** * https://developer.github.com/v3/issues/comments/ */ -case class ApiComment( - id: Int, - user: ApiUser, - body: String, - created_at: Date, - updated_at: Date)(repositoryName: RepositoryName, issueId: Int, isPullRequest: Boolean){ - val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${issueId}#comment-${id}") +case class ApiComment(id: Int, user: ApiUser, body: String, created_at: Date, updated_at: Date)( + repositoryName: RepositoryName, + issueId: Int, + isPullRequest: Boolean +) { + val html_url = ApiPath( + s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${issueId}#comment-${id}" + ) } -object ApiComment{ - def apply(comment: IssueComment, repositoryName: RepositoryName, issueId: Int, user: ApiUser, isPullRequest: Boolean): ApiComment = +object ApiComment { + def apply( + comment: IssueComment, + repositoryName: RepositoryName, + issueId: Int, + user: ApiUser, + isPullRequest: Boolean + ): ApiComment = ApiComment( id = comment.commentId, user = user, body = comment.content, created_at = comment.registeredDate, - updated_at = comment.updatedDate)(repositoryName, issueId, isPullRequest) + updated_at = comment.updatedDate + )(repositoryName, issueId, isPullRequest) } diff --git a/src/main/scala/gitbucket/core/api/ApiCommit.scala b/src/main/scala/gitbucket/core/api/ApiCommit.scala index 04c05a5..f564f13 100644 --- a/src/main/scala/gitbucket/core/api/ApiCommit.scala +++ b/src/main/scala/gitbucket/core/api/ApiCommit.scala @@ -20,38 +20,41 @@ removed: List[String], modified: List[String], author: ApiPersonIdent, - committer: ApiPersonIdent)(repositoryName:RepositoryName, urlIsHtmlUrl: Boolean) extends FieldSerializable{ - val url = if(urlIsHtmlUrl){ + committer: ApiPersonIdent +)(repositoryName: RepositoryName, urlIsHtmlUrl: Boolean) + extends FieldSerializable { + val url = if (urlIsHtmlUrl) { ApiPath(s"/${repositoryName.fullName}/commit/${id}") - }else{ + } else { ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}") } - val html_url = if(urlIsHtmlUrl){ + val html_url = if (urlIsHtmlUrl) { None - }else{ + } else { Some(ApiPath(s"/${repositoryName.fullName}/commit/${id}")) } } -object ApiCommit{ +object ApiCommit { def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo, urlIsHtmlUrl: Boolean = false): ApiCommit = { val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false) ApiCommit( - id = commit.id, - message = commit.fullMessage, + id = commit.id, + message = commit.fullMessage, timestamp = commit.commitTime, - added = diffs.collect { - case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath + added = diffs.collect { + case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath }, - removed = diffs.collect { + removed = diffs.collect { case x if x.changeType == DiffEntry.ChangeType.DELETE => x.oldPath }, - modified = diffs.collect { + modified = diffs.collect { case x if x.changeType != DiffEntry.ChangeType.ADD && x.changeType != DiffEntry.ChangeType.DELETE => x.newPath }, - author = ApiPersonIdent.author(commit), + author = ApiPersonIdent.author(commit), committer = ApiPersonIdent.committer(commit) )(repositoryName, urlIsHtmlUrl) } - def forWebhookPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = apply(git, repositoryName, commit, true) + def forWebhookPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = + apply(git, repositoryName, commit, true) } diff --git a/src/main/scala/gitbucket/core/api/ApiCommitListItem.scala b/src/main/scala/gitbucket/core/api/ApiCommitListItem.scala index a57431f..7a79474 100644 --- a/src/main/scala/gitbucket/core/api/ApiCommitListItem.scala +++ b/src/main/scala/gitbucket/core/api/ApiCommitListItem.scala @@ -4,7 +4,6 @@ import gitbucket.core.util.JGitUtil.CommitInfo import gitbucket.core.util.RepositoryName - /** * https://developer.github.com/v3/repos/commits/ */ @@ -13,30 +12,33 @@ commit: Commit, author: Option[ApiUser], committer: Option[ApiUser], - parents: Seq[Parent])(repositoryName: RepositoryName) { + parents: Seq[Parent] +)(repositoryName: RepositoryName) { val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}") } object ApiCommitListItem { - def apply(commit: CommitInfo, repositoryName: RepositoryName): ApiCommitListItem = ApiCommitListItem( - sha = commit.id, - commit = Commit( - message = commit.fullMessage, - author = ApiPersonIdent.author(commit), - committer = ApiPersonIdent.committer(commit) + def apply(commit: CommitInfo, repositoryName: RepositoryName): ApiCommitListItem = + ApiCommitListItem( + sha = commit.id, + commit = Commit( + message = commit.fullMessage, + author = ApiPersonIdent.author(commit), + committer = ApiPersonIdent.committer(commit) )(commit.id, repositoryName), - author = None, - committer = None, - parents = commit.parents.map(Parent(_)(repositoryName)))(repositoryName) + author = None, + committer = None, + parents = commit.parents.map(Parent(_)(repositoryName)) + )(repositoryName) - case class Parent(sha: String)(repositoryName: RepositoryName){ + case class Parent(sha: String)(repositoryName: RepositoryName) { val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}") } - case class Commit( - message: String, - author: ApiPersonIdent, - committer: ApiPersonIdent)(sha:String, repositoryName: RepositoryName) { + case class Commit(message: String, author: ApiPersonIdent, committer: ApiPersonIdent)( + sha: String, + repositoryName: RepositoryName + ) { val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${sha}") } } diff --git a/src/main/scala/gitbucket/core/api/ApiCommitStatus.scala b/src/main/scala/gitbucket/core/api/ApiCommitStatus.scala index 03d8ef6..6142ea1 100644 --- a/src/main/scala/gitbucket/core/api/ApiCommitStatus.scala +++ b/src/main/scala/gitbucket/core/api/ApiCommitStatus.scala @@ -5,7 +5,6 @@ import java.util.Date - /** * https://developer.github.com/v3/repos/statuses/#create-a-status * https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref @@ -23,16 +22,16 @@ val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}/statuses") } - object ApiCommitStatus { - def apply(status: CommitStatus, creator:ApiUser): ApiCommitStatus = ApiCommitStatus( - created_at = status.registeredDate, - updated_at = status.updatedDate, - state = status.state.name, - target_url = status.targetUrl, - description= status.description, - id = status.commitStatusId, - context = status.context, - creator = creator - )(status.commitId, RepositoryName(status)) + def apply(status: CommitStatus, creator: ApiUser): ApiCommitStatus = + ApiCommitStatus( + created_at = status.registeredDate, + updated_at = status.updatedDate, + state = status.state.name, + target_url = status.targetUrl, + description = status.description, + id = status.commitStatusId, + context = status.context, + creator = creator + )(status.commitId, RepositoryName(status)) } diff --git a/src/main/scala/gitbucket/core/api/ApiCommits.scala b/src/main/scala/gitbucket/core/api/ApiCommits.scala index dc3e2ad..3b1f014 100644 --- a/src/main/scala/gitbucket/core/api/ApiCommits.scala +++ b/src/main/scala/gitbucket/core/api/ApiCommits.scala @@ -51,20 +51,25 @@ patch: String ) - - def apply(repositoryName: RepositoryName, commitInfo: CommitInfo, diffs: Seq[DiffInfo], author: Account, committer: Account, - commentCount: Int): ApiCommits = { + def apply( + repositoryName: RepositoryName, + commitInfo: CommitInfo, + diffs: Seq[DiffInfo], + author: Account, + committer: Account, + commentCount: Int + ): ApiCommits = { val files = diffs.map { diff => var additions = 0 var deletions = 0 diff.patch.getOrElse("").split("\n").foreach { line => - if(line.startsWith("+")) additions = additions + 1 - if(line.startsWith("-")) deletions = deletions + 1 + if (line.startsWith("+")) additions = additions + 1 + if (line.startsWith("-")) deletions = deletions + 1 } File( - filename = if(diff.changeType == ChangeType.DELETE){ diff.oldPath } else { diff.newPath }, + filename = if (diff.changeType == ChangeType.DELETE) { diff.oldPath } else { diff.newPath }, additions = additions, deletions = deletions, changes = additions + deletions, @@ -75,12 +80,12 @@ case ChangeType.RENAME => "renamed" case ChangeType.COPY => "copied" }, - raw_url = if(diff.changeType == ChangeType.DELETE){ + raw_url = if (diff.changeType == ChangeType.DELETE) { ApiPath(s"/${repositoryName.fullName}/raw/${commitInfo.parents.head}/${diff.oldPath}") } else { ApiPath(s"/${repositoryName.fullName}/raw/${commitInfo.id}/${diff.newPath}") }, - blob_url = if(diff.changeType == ChangeType.DELETE){ + blob_url = if (diff.changeType == ChangeType.DELETE) { ApiPath(s"/${repositoryName.fullName}/blob/${commitInfo.parents.head}/${diff.oldPath}") } else { ApiPath(s"/${repositoryName.fullName}/blob/${commitInfo.id}/${diff.newPath}") diff --git a/src/main/scala/gitbucket/core/api/ApiContents.scala b/src/main/scala/gitbucket/core/api/ApiContents.scala index 8b3735a..f340595 100644 --- a/src/main/scala/gitbucket/core/api/ApiContents.scala +++ b/src/main/scala/gitbucket/core/api/ApiContents.scala @@ -11,18 +11,29 @@ path: String, sha: String, content: Option[String], - encoding: Option[String])(repositoryName: RepositoryName){ + encoding: Option[String] +)(repositoryName: RepositoryName) { val download_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/raw/${sha}/${path}") } -object ApiContents{ +object ApiContents { def apply(fileInfo: FileInfo, repositoryName: RepositoryName, content: Option[Array[Byte]]): ApiContents = { - if(fileInfo.isDirectory) { + if (fileInfo.isDirectory) { ApiContents("dir", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName) } else { - content.map(arr => - ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, Some(Base64.getEncoder.encodeToString(arr)), Some("base64"))(repositoryName) - ).getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)) + content + .map( + arr => + ApiContents( + "file", + fileInfo.name, + fileInfo.path, + fileInfo.commitId, + Some(Base64.getEncoder.encodeToString(arr)), + Some("base64") + )(repositoryName) + ) + .getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)) } } } diff --git a/src/main/scala/gitbucket/core/api/ApiEndPoint.scala b/src/main/scala/gitbucket/core/api/ApiEndPoint.scala index 4c66d84..015c456 100644 --- a/src/main/scala/gitbucket/core/api/ApiEndPoint.scala +++ b/src/main/scala/gitbucket/core/api/ApiEndPoint.scala @@ -1,3 +1,3 @@ package gitbucket.core.api -case class ApiEndPoint(rate_limit_url: ApiPath = ApiPath("/api/v3/rate_limit")) \ No newline at end of file +case class ApiEndPoint(rate_limit_url: ApiPath = ApiPath("/api/v3/rate_limit")) diff --git a/src/main/scala/gitbucket/core/api/ApiError.scala b/src/main/scala/gitbucket/core/api/ApiError.scala index 95114d1..f3e755b 100644 --- a/src/main/scala/gitbucket/core/api/ApiError.scala +++ b/src/main/scala/gitbucket/core/api/ApiError.scala @@ -1,5 +1,3 @@ package gitbucket.core.api -case class ApiError( - message: String, - documentation_url: Option[String] = None) +case class ApiError(message: String, documentation_url: Option[String] = None) diff --git a/src/main/scala/gitbucket/core/api/ApiIssue.scala b/src/main/scala/gitbucket/core/api/ApiIssue.scala index 9f27f55..b03cf48 100644 --- a/src/main/scala/gitbucket/core/api/ApiIssue.scala +++ b/src/main/scala/gitbucket/core/api/ApiIssue.scala @@ -5,7 +5,6 @@ import java.util.Date - /** * https://developer.github.com/v3/issues/ */ @@ -17,30 +16,34 @@ state: String, created_at: Date, updated_at: Date, - body: String)(repositoryName: RepositoryName, isPullRequest: Boolean){ + body: String +)(repositoryName: RepositoryName, isPullRequest: Boolean) { val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments") - val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}") + val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}") val pull_request = if (isPullRequest) { - Some(Map( - "url" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${number}"), - "html_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}") - // "diff_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.diff"), - // "patch_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.patch") - )) + Some( + Map( + "url" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${number}"), + "html_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}") + // "diff_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.diff"), + // "patch_url" -> ApiPath(s"/${repositoryName.fullName}/pull/${number}.patch") + ) + ) } else { None } } -object ApiIssue{ +object ApiIssue { def apply(issue: Issue, repositoryName: RepositoryName, user: ApiUser, labels: List[ApiLabel]): ApiIssue = ApiIssue( number = issue.issueId, - title = issue.title, - user = user, + title = issue.title, + user = user, labels = labels, - state = if(issue.closed){ "closed" }else{ "open" }, - body = issue.content.getOrElse(""), + state = if (issue.closed) { "closed" } else { "open" }, + body = issue.content.getOrElse(""), created_at = issue.registeredDate, - updated_at = issue.updatedDate)(repositoryName, issue.isPullRequest) + updated_at = issue.updatedDate + )(repositoryName, issue.isPullRequest) } diff --git a/src/main/scala/gitbucket/core/api/ApiLabel.scala b/src/main/scala/gitbucket/core/api/ApiLabel.scala index 2d1842b..4a080a1 100644 --- a/src/main/scala/gitbucket/core/api/ApiLabel.scala +++ b/src/main/scala/gitbucket/core/api/ApiLabel.scala @@ -4,18 +4,16 @@ import gitbucket.core.util.RepositoryName /** - * https://developer.github.com/v3/issues/labels/ - */ -case class ApiLabel( - name: String, - color: String)(repositoryName: RepositoryName){ + * https://developer.github.com/v3/issues/labels/ + */ +case class ApiLabel(name: String, color: String)(repositoryName: RepositoryName) { var url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/labels/${name}") } -object ApiLabel{ - def apply(label:Label, repositoryName: RepositoryName): ApiLabel = +object ApiLabel { + def apply(label: Label, repositoryName: RepositoryName): ApiLabel = ApiLabel( name = label.labelName, color = label.color )(repositoryName) -} \ No newline at end of file +} diff --git a/src/main/scala/gitbucket/core/api/ApiPersonIdent.scala b/src/main/scala/gitbucket/core/api/ApiPersonIdent.scala index 3c31e15..f242e3b 100644 --- a/src/main/scala/gitbucket/core/api/ApiPersonIdent.scala +++ b/src/main/scala/gitbucket/core/api/ApiPersonIdent.scala @@ -4,22 +4,11 @@ import java.util.Date - -case class ApiPersonIdent( - name: String, - email: String, - date: Date) - +case class ApiPersonIdent(name: String, email: String, date: Date) object ApiPersonIdent { def author(commit: CommitInfo): ApiPersonIdent = - ApiPersonIdent( - name = commit.authorName, - email = commit.authorEmailAddress, - date = commit.authorTime) + ApiPersonIdent(name = commit.authorName, email = commit.authorEmailAddress, date = commit.authorTime) def committer(commit: CommitInfo): ApiPersonIdent = - ApiPersonIdent( - name = commit.committerName, - email = commit.committerEmailAddress, - date = commit.commitTime) + ApiPersonIdent(name = commit.committerName, email = commit.committerEmailAddress, date = commit.commitTime) } diff --git a/src/main/scala/gitbucket/core/api/ApiPlugin.scala b/src/main/scala/gitbucket/core/api/ApiPlugin.scala index e7dbcef..d386556 100644 --- a/src/main/scala/gitbucket/core/api/ApiPlugin.scala +++ b/src/main/scala/gitbucket/core/api/ApiPlugin.scala @@ -10,7 +10,7 @@ jarFileName: String ) -object ApiPlugin{ +object ApiPlugin { def apply(plugin: PluginInfo): ApiPlugin = { ApiPlugin(plugin.pluginId, plugin.pluginName, plugin.pluginVersion, plugin.description, plugin.pluginJar.getName) } diff --git a/src/main/scala/gitbucket/core/api/ApiPullRequest.scala b/src/main/scala/gitbucket/core/api/ApiPullRequest.scala index bf7f886..50d20b3 100644 --- a/src/main/scala/gitbucket/core/api/ApiPullRequest.scala +++ b/src/main/scala/gitbucket/core/api/ApiPullRequest.scala @@ -21,20 +21,21 @@ body: String, user: ApiUser, labels: List[ApiLabel], - assignee: Option[ApiUser]){ - val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}") + assignee: Option[ApiUser] +) { + val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}") //val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff") //val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch") - val url = ApiPath(s"${base.repo.url.path}/pulls/${number}") + val url = ApiPath(s"${base.repo.url.path}/pulls/${number}") //val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}") - val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits") + val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits") val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments") - val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}") - val comments_url = ApiPath(s"${base.repo.url.path}/issues/${number}/comments") - val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}") + val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}") + val comments_url = ApiPath(s"${base.repo.url.path}/issues/${number}/comments") + val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}") } -object ApiPullRequest{ +object ApiPullRequest { def apply( issue: Issue, pullRequest: PullRequest, @@ -46,34 +47,25 @@ mergedComment: Option[(IssueComment, Account)] ): ApiPullRequest = ApiPullRequest( - number = issue.issueId, - state = if (issue.closed) "closed" else "open", + number = issue.issueId, + state = if (issue.closed) "closed" else "open", updated_at = issue.updatedDate, created_at = issue.registeredDate, - head = Commit( - sha = pullRequest.commitIdTo, - ref = pullRequest.requestBranch, - repo = headRepo)(issue.userName), - base = Commit( - sha = pullRequest.commitIdFrom, - ref = pullRequest.branch, - repo = baseRepo)(issue.userName), - mergeable = None, // TODO: need check mergeable. - merged = mergedComment.isDefined, - merged_at = mergedComment.map { case (comment, _) => comment.registeredDate }, - merged_by = mergedComment.map { case (_, account) => ApiUser(account) }, - title = issue.title, - body = issue.content.getOrElse(""), - user = user, - labels = labels, - assignee = assignee + head = Commit(sha = pullRequest.commitIdTo, ref = pullRequest.requestBranch, repo = headRepo)(issue.userName), + base = Commit(sha = pullRequest.commitIdFrom, ref = pullRequest.branch, repo = baseRepo)(issue.userName), + mergeable = None, // TODO: need check mergeable. + merged = mergedComment.isDefined, + merged_at = mergedComment.map { case (comment, _) => comment.registeredDate }, + merged_by = mergedComment.map { case (_, account) => ApiUser(account) }, + title = issue.title, + body = issue.content.getOrElse(""), + user = user, + labels = labels, + assignee = assignee ) - case class Commit( - sha: String, - ref: String, - repo: ApiRepository)(baseOwner:String){ - val label = if( baseOwner == repo.owner.login ){ ref } else { s"${repo.owner.login}:${ref}" } + case class Commit(sha: String, ref: String, repo: ApiRepository)(baseOwner: String) { + val label = if (baseOwner == repo.owner.login) { ref } else { s"${repo.owner.login}:${ref}" } val user = repo.owner } diff --git a/src/main/scala/gitbucket/core/api/ApiPullRequestReviewComment.scala b/src/main/scala/gitbucket/core/api/ApiPullRequestReviewComment.scala index 55d1fe9..7516b2e 100644 --- a/src/main/scala/gitbucket/core/api/ApiPullRequestReviewComment.scala +++ b/src/main/scala/gitbucket/core/api/ApiPullRequestReviewComment.scala @@ -18,9 +18,10 @@ // "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", user: ApiUser, body: String, // "Maybe you should use more emojji on this line.", - created_at: Date, // "2015-05-05T23:40:27Z", + created_at: Date, // "2015-05-05T23:40:27Z", updated_at: Date // "2015-05-05T23:40:27Z", -)(repositoryName:RepositoryName, issueId: Int) extends FieldSerializable { +)(repositoryName: RepositoryName, issueId: Int) + extends FieldSerializable { // "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692", val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/comments/${id}") // "html_url": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692", @@ -40,22 +41,28 @@ "href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1" } } - */ + */ val _links = Map( "self" -> Map("href" -> url), "html" -> Map("href" -> html_url), - "pull_request" -> Map("href" -> pull_request_url)) + "pull_request" -> Map("href" -> pull_request_url) + ) } -object ApiPullRequestReviewComment{ - def apply(comment: CommitComment, commentedUser: ApiUser, repositoryName: RepositoryName, issueId: Int): ApiPullRequestReviewComment = +object ApiPullRequestReviewComment { + def apply( + comment: CommitComment, + commentedUser: ApiUser, + repositoryName: RepositoryName, + issueId: Int + ): ApiPullRequestReviewComment = new ApiPullRequestReviewComment( - id = comment.commentId, - path = comment.fileName.getOrElse(""), - commit_id = comment.commitId, - user = commentedUser, - body = comment.content, - created_at = comment.registeredDate, - updated_at = comment.updatedDate - )(repositoryName, issueId) + id = comment.commentId, + path = comment.fileName.getOrElse(""), + commit_id = comment.commitId, + user = commentedUser, + body = comment.content, + created_at = comment.registeredDate, + updated_at = comment.updatedDate + )(repositoryName, issueId) } diff --git a/src/main/scala/gitbucket/core/api/ApiPusher.scala b/src/main/scala/gitbucket/core/api/ApiPusher.scala index a0b714e..038e699 100644 --- a/src/main/scala/gitbucket/core/api/ApiPusher.scala +++ b/src/main/scala/gitbucket/core/api/ApiPusher.scala @@ -5,7 +5,5 @@ case class ApiPusher(name: String, email: String) object ApiPusher { - def apply(user: Account): ApiPusher = ApiPusher( - name = user.userName, - email = user.mailAddress) -} \ No newline at end of file + def apply(user: Account): ApiPusher = ApiPusher(name = user.userName, email = user.mailAddress) +} diff --git a/src/main/scala/gitbucket/core/api/ApiRepository.scala b/src/main/scala/gitbucket/core/api/ApiRepository.scala index facaf3f..549e8e7 100644 --- a/src/main/scala/gitbucket/core/api/ApiRepository.scala +++ b/src/main/scala/gitbucket/core/api/ApiRepository.scala @@ -3,7 +3,6 @@ import gitbucket.core.model.{Account, Repository} import gitbucket.core.service.RepositoryService.RepositoryInfo - // https://developer.github.com/v3/repos/ case class ApiRepository( name: String, @@ -13,56 +12,58 @@ forks: Int, `private`: Boolean, default_branch: String, - owner: ApiUser)(urlIsHtmlUrl: Boolean) { + owner: ApiUser +)(urlIsHtmlUrl: Boolean) { val forks_count = forks val watchers_count = watchers - val url = if(urlIsHtmlUrl){ + val url = if (urlIsHtmlUrl) { ApiPath(s"/${full_name}") } else { ApiPath(s"/api/v3/repos/${full_name}") } - val http_url = ApiPath(s"/git/${full_name}.git") + val http_url = ApiPath(s"/git/${full_name}.git") val clone_url = ApiPath(s"/git/${full_name}.git") - val html_url = ApiPath(s"/${full_name}") - val ssh_url = Some(SshPath(s":${full_name}.git")) + val html_url = ApiPath(s"/${full_name}") + val ssh_url = Some(SshPath(s":${full_name}.git")) } -object ApiRepository{ +object ApiRepository { def apply( - repository: Repository, - owner: ApiUser, - forkedCount: Int =0, - watchers: Int = 0, - urlIsHtmlUrl: Boolean = false): ApiRepository = + repository: Repository, + owner: ApiUser, + forkedCount: Int = 0, + watchers: Int = 0, + urlIsHtmlUrl: Boolean = false + ): ApiRepository = ApiRepository( - name = repository.repositoryName, - full_name = s"${repository.userName}/${repository.repositoryName}", - description = repository.description.getOrElse(""), - watchers = watchers, - forks = forkedCount, - `private` = repository.isPrivate, + name = repository.repositoryName, + full_name = s"${repository.userName}/${repository.repositoryName}", + description = repository.description.getOrElse(""), + watchers = watchers, + forks = forkedCount, + `private` = repository.isPrivate, default_branch = repository.defaultBranch, - owner = owner + owner = owner )(urlIsHtmlUrl) def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository = - ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount) + ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount) def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository = this(repositoryInfo.repository, ApiUser(owner)) def forWebhookPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository = - ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount, urlIsHtmlUrl=true) + ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount, urlIsHtmlUrl = true) def forDummyPayload(owner: ApiUser): ApiRepository = ApiRepository( - name = "dummy", - full_name = s"${owner.login}/dummy", - description = "", - watchers = 0, - forks = 0, - `private` = false, + name = "dummy", + full_name = s"${owner.login}/dummy", + description = "", + watchers = 0, + forks = 0, + `private` = false, default_branch = "master", - owner = owner + owner = owner )(true) } diff --git a/src/main/scala/gitbucket/core/api/ApiUser.scala b/src/main/scala/gitbucket/core/api/ApiUser.scala index 9b3dc9d..5d74e40 100644 --- a/src/main/scala/gitbucket/core/api/ApiUser.scala +++ b/src/main/scala/gitbucket/core/api/ApiUser.scala @@ -4,16 +4,10 @@ import java.util.Date - -case class ApiUser( - login: String, - email: String, - `type`: String, - site_admin: Boolean, - created_at: Date) { - val url = ApiPath(s"/api/v3/users/${login}") - val html_url = ApiPath(s"/${login}") - val avatar_url = ApiPath(s"/${login}/_avatar") +case class ApiUser(login: String, email: String, `type`: String, site_admin: Boolean, created_at: Date) { + val url = ApiPath(s"/api/v3/users/${login}") + val html_url = ApiPath(s"/${login}") + val avatar_url = ApiPath(s"/${login}/_avatar") // val followers_url = ApiPath(s"/api/v3/users/${login}/followers") // val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}") // val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}") @@ -25,12 +19,11 @@ // val received_events_url = ApiPath(s"/api/v3/users/${login}/received_events") } - -object ApiUser{ +object ApiUser { def apply(user: Account): ApiUser = ApiUser( - login = user.userName, - email = user.mailAddress, - `type` = if(user.isGroupAccount){ "Organization" } else { "User" }, + login = user.userName, + email = user.mailAddress, + `type` = if (user.isGroupAccount) { "Organization" } else { "User" }, site_admin = user.isAdmin, created_at = user.registeredDate ) diff --git a/src/main/scala/gitbucket/core/api/CreateALabel.scala b/src/main/scala/gitbucket/core/api/CreateALabel.scala index cfd71d1..fce7c2c 100644 --- a/src/main/scala/gitbucket/core/api/CreateALabel.scala +++ b/src/main/scala/gitbucket/core/api/CreateALabel.scala @@ -1,18 +1,18 @@ package gitbucket.core.api /** - * https://developer.github.com/v3/issues/labels/#create-a-label - * api form - */ + * https://developer.github.com/v3/issues/labels/#create-a-label + * api form + */ case class CreateALabel( - name: String, - color: String + name: String, + color: String ) { def isValid: Boolean = { - name.length<=100 && - !name.startsWith("_") && - !name.startsWith("-") && - color.length==6 && - color.matches("[a-fA-F0-9+_.]+") + name.length <= 100 && + !name.startsWith("_") && + !name.startsWith("-") && + color.length == 6 && + color.matches("[a-fA-F0-9+_.]+") } -} \ No newline at end of file +} diff --git a/src/main/scala/gitbucket/core/api/CreateARepository.scala b/src/main/scala/gitbucket/core/api/CreateARepository.scala index 7247c9f..93dd00a 100644 --- a/src/main/scala/gitbucket/core/api/CreateARepository.scala +++ b/src/main/scala/gitbucket/core/api/CreateARepository.scala @@ -5,15 +5,15 @@ * api form */ case class CreateARepository( - name: String, - description: Option[String], - `private`: Boolean = false, - auto_init: Boolean = false + name: String, + description: Option[String], + `private`: Boolean = false, + auto_init: Boolean = false ) { def isValid: Boolean = { name.length <= 100 && - name.matches("[a-zA-Z0-9\\-\\+_.]+") && - !name.startsWith("_") && - !name.startsWith("-") + name.matches("[a-zA-Z0-9\\-\\+_.]+") && + !name.startsWith("_") && + !name.startsWith("-") } } diff --git a/src/main/scala/gitbucket/core/api/CreateAStatus.scala b/src/main/scala/gitbucket/core/api/CreateAStatus.scala index f8c087f..862533b 100644 --- a/src/main/scala/gitbucket/core/api/CreateAStatus.scala +++ b/src/main/scala/gitbucket/core/api/CreateAStatus.scala @@ -18,9 +18,9 @@ ) { def isValid: Boolean = { CommitState.valueOf(state).isDefined && - // only http - target_url.forall(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length < 255) && - context.forall(f => f.length < 255) && - description.forall(f => f.length < 1000) + // only http + target_url.forall(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length < 255) && + context.forall(f => f.length < 255) && + description.forall(f => f.length < 1000) } } diff --git a/src/main/scala/gitbucket/core/api/CreateAnIssue.scala b/src/main/scala/gitbucket/core/api/CreateAnIssue.scala index cb54652..16585b7 100644 --- a/src/main/scala/gitbucket/core/api/CreateAnIssue.scala +++ b/src/main/scala/gitbucket/core/api/CreateAnIssue.scala @@ -1,11 +1,12 @@ package gitbucket.core.api /** - * https://developer.github.com/v3/issues/#create-an-issue - */ + * https://developer.github.com/v3/issues/#create-an-issue + */ case class CreateAnIssue( - title: String, - body: Option[String], - assignees: List[String], - milestone: Option[Int], - labels: List[String]) + title: String, + body: Option[String], + assignees: List[String], + milestone: Option[Int], + labels: List[String] +) diff --git a/src/main/scala/gitbucket/core/api/JsonFormat.scala b/src/main/scala/gitbucket/core/api/JsonFormat.scala index a9fddcd..8579926 100644 --- a/src/main/scala/gitbucket/core/api/JsonFormat.scala +++ b/src/main/scala/gitbucket/core/api/JsonFormat.scala @@ -15,10 +15,13 @@ val parserISO = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") - val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format => - ( - { case JString(s) => Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date")) }, - { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) } + val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date]( + format => + ( + { + case JString(s) => + Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date")) + }, { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) } ) ) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + @@ -41,19 +44,31 @@ FieldSerializer[ApiCommits.File]() + ApiBranchProtection.enforcementLevelSerializer - def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](_ => ({ - case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length)) - case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") - }, { - case ApiPath(path) => JString(c.baseUrl + path) - })) + def apiPathSerializer(c: Context) = + new CustomSerializer[ApiPath]( + _ => + ({ + case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length)) + case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") + }, { + case ApiPath(path) => JString(c.baseUrl + path) + }) + ) - def sshPathSerializer(c: Context) = new CustomSerializer[SshPath](_ => ({ - case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) => SshPath(s.substring(c.sshUrl.get.length)) - case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") - }, { - case SshPath(path) => c.sshUrl.map { sshUrl => JString(sshUrl + path) } getOrElse JNothing - })) + def sshPathSerializer(c: Context) = + new CustomSerializer[SshPath]( + _ => + ({ + case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) => + SshPath(s.substring(c.sshUrl.get.length)) + case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") + }, { + case SshPath(path) => + c.sshUrl.map { sshUrl => + JString(sshUrl + path) + } getOrElse JNothing + }) + ) /** * convert object to json string diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 1b4508c..93d3a99 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -16,77 +16,133 @@ import org.scalatra.BadRequest import org.scalatra.forms._ -class AccountController extends AccountControllerBase - with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService - with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator - with AccessTokenService with WebHookService with PrioritiesService with RepositoryCreationService - +class AccountController + extends AccountControllerBase + with AccountService + with RepositoryService + with ActivityService + with WikiService + with LabelsService + with SshKeyService + with OneselfAuthenticator + with UsersAuthenticator + with GroupManagerAuthenticator + with ReadableUsersAuthenticator + with AccessTokenService + with WebHookService + with PrioritiesService + with RepositoryCreationService trait AccountControllerBase extends AccountManagementControllerBase { - self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService - with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator - with AccessTokenService with WebHookService with PrioritiesService with RepositoryCreationService => + self: AccountService + with RepositoryService + with ActivityService + with WikiService + with LabelsService + with SshKeyService + with OneselfAuthenticator + with UsersAuthenticator + with GroupManagerAuthenticator + with ReadableUsersAuthenticator + with AccessTokenService + with WebHookService + with PrioritiesService + with RepositoryCreationService => - case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String, - description: Option[String], url: Option[String], fileId: Option[String]) + case class AccountNewForm( + userName: String, + password: String, + fullName: String, + mailAddress: String, + description: Option[String], + url: Option[String], + fileId: Option[String] + ) - case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String, - description: Option[String], url: Option[String], fileId: Option[String], clearImage: Boolean) + case class AccountEditForm( + password: Option[String], + fullName: String, + mailAddress: String, + description: Option[String], + url: Option[String], + fileId: Option[String], + clearImage: Boolean + ) case class SshKeyForm(title: String, publicKey: String) case class PersonalTokenForm(note: String) val newForm = mapping( - "userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), - "password" -> trim(label("Password" , text(required, maxlength(20), password))), - "fullName" -> trim(label("Full Name" , text(required, maxlength(100)))), - "mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))), - "description" -> trim(label("bio" , optional(text()))), - "url" -> trim(label("URL" , optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" , optional(text()))) + "userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), + "password" -> trim(label("Password", text(required, maxlength(20), password))), + "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), + "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), + "description" -> trim(label("bio", optional(text()))), + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))) )(AccountNewForm.apply) val editForm = mapping( - "password" -> trim(label("Password" , optional(text(maxlength(20), password)))), - "fullName" -> trim(label("Full Name" , text(required, maxlength(100)))), - "mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))), - "description" -> trim(label("bio" , optional(text()))), - "url" -> trim(label("URL" , optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" , optional(text()))), - "clearImage" -> trim(label("Clear image" , boolean())) + "password" -> trim(label("Password", optional(text(maxlength(20), password)))), + "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), + "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), + "description" -> trim(label("bio", optional(text()))), + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))), + "clearImage" -> trim(label("Clear image", boolean())) )(AccountEditForm.apply) val sshKeyForm = mapping( - "title" -> trim(label("Title", text(required, maxlength(100)))), - "publicKey" -> trim2(label("Key" , text(required, validPublicKey))) + "title" -> trim(label("Title", text(required, maxlength(100)))), + "publicKey" -> trim2(label("Key", text(required, validPublicKey))) )(SshKeyForm.apply) val personalTokenForm = mapping( "note" -> trim(label("Token", text(required, maxlength(100)))) )(PersonalTokenForm.apply) - case class NewGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], members: String) - case class EditGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], members: String, clearImage: Boolean) + case class NewGroupForm( + groupName: String, + description: Option[String], + url: Option[String], + fileId: Option[String], + members: String + ) + case class EditGroupForm( + groupName: String, + description: Option[String], + url: Option[String], + fileId: Option[String], + members: String, + clearImage: Boolean + ) val newGroupForm = mapping( - "groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), + "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), "description" -> trim(label("Group description", optional(text()))), - "url" -> trim(label("URL" ,optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" ,optional(text()))), - "members" -> trim(label("Members" ,text(required, members))) + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))), + "members" -> trim(label("Members", text(required, members))) )(NewGroupForm.apply) val editGroupForm = mapping( - "groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))), + "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))), "description" -> trim(label("Group description", optional(text()))), - "url" -> trim(label("URL" ,optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" ,optional(text()))), - "members" -> trim(label("Members" ,text(required, members))), - "clearImage" -> trim(label("Clear image" ,boolean())) + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))), + "members" -> trim(label("Members", text(required, members))), + "clearImage" -> trim(label("Clear image", boolean())) )(EditGroupForm.apply) - case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, initOption: String, sourceUrl: Option[String]) + case class RepositoryCreationForm( + owner: String, + name: String, + description: Option[String], + isPrivate: Boolean, + initOption: String, + sourceUrl: Option[String] + ) case class ForkRepositoryForm(owner: String, name: String) val newRepositoryForm = mapping( @@ -100,7 +156,7 @@ val forkRepositoryForm = mapping( "owner" -> trim(label("Repository owner", text(required))), - "name" -> trim(label("Repository name", text(required))) + "name" -> trim(label("Repository name", text(required))) )(ForkRepositoryForm.apply) case class AccountForm(accountName: String) @@ -110,23 +166,30 @@ )(AccountForm.apply) // for account web hook url addition. - case class AccountWebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String]) - - def accountWebHookForm(update:Boolean) = mapping( - "url" -> trim(label("url", text(required, accountWebHook(update)))), - "events" -> accountWebhookEvents, - "ctype" -> label("ctype", text()), - "token" -> optional(trim(label("token", text(maxlength(100))))) - )( - (url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token) + case class AccountWebHookForm( + url: String, + events: Set[WebHook.Event], + ctype: WebHookContentType, + token: Option[String] ) + + def accountWebHookForm(update: Boolean) = + mapping( + "url" -> trim(label("url", text(required, accountWebHook(update)))), + "events" -> accountWebhookEvents, + "ctype" -> label("ctype", text()), + "token" -> optional(trim(label("token", text(maxlength(100))))) + )( + (url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token) + ) + /** - * Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala - */ - private def accountWebHook(needExists: Boolean): Constraint = new Constraint(){ + * Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala + */ + private def accountWebHook(needExists: Boolean): Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(getAccountWebHook(params("userName"), value).isDefined != needExists){ - Some(if(needExists){ + if (getAccountWebHook(params("userName"), value).isDefined != needExists) { + Some(if (needExists) { "URL had not been registered yet." } else { "URL had been registered already." @@ -136,21 +199,20 @@ } } - private def accountWebhookEvents = new ValueType[Set[WebHook.Event]]{ + private def accountWebhookEvents = new ValueType[Set[WebHook.Event]] { def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = { WebHook.Event.values.flatMap { t => params.optionValue(name + "." + t.name).map(_ => t) }.toSet } def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = - if(convert(name, params, messages).isEmpty){ + if (convert(name, params, messages).isEmpty) { Seq(name -> messages("error.required").format(name)) } else { Nil } } - /** * Displays user information. */ @@ -160,24 +222,41 @@ params.getOrElse("tab", "repositories") match { // Public Activity case "activity" => - gitbucket.core.account.html.activity(account, - if(account.isGroupAccount) Nil else getGroupsByUserName(userName), - getActivitiesByUser(userName, true)) + gitbucket.core.account.html.activity( + account, + if (account.isGroupAccount) Nil else getGroupsByUserName(userName), + getActivitiesByUser(userName, true) + ) // Members - case "members" if(account.isGroupAccount) => { + case "members" if (account.isGroupAccount) => { val members = getGroupMembers(account.userName) - gitbucket.core.account.html.members(account, members, - context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })) + gitbucket.core.account.html.members( + account, + members, + context.loginAccount.exists( + x => + members.exists { member => + member.userName == x.userName && member.isManager + } + ) + ) } // Repositories case _ => { val members = getGroupMembers(account.userName) - gitbucket.core.account.html.repositories(account, - if(account.isGroupAccount) Nil else getGroupsByUserName(userName), + gitbucket.core.account.html.repositories( + account, + if (account.isGroupAccount) Nil else getGroupsByUserName(userName), getVisibleRepositories(context.loginAccount, Some(userName)), - context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })) + context.loginAccount.exists( + x => + members.exists { member => + member.userName == x.userName && member.isManager + } + ) + ) } } } getOrElse NotFound() @@ -189,24 +268,28 @@ helper.xml.feed(getActivitiesByUser(userName, true)) } - get("/:userName/_avatar"){ + get("/:userName/_avatar") { val userName = params("userName") contentType = "image/png" - getAccountByUserName(userName).flatMap{ account => - response.setDateHeader("Last-Modified", account.updatedDate.getTime) - account.image.map{ image => - Some(RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))) - }.getOrElse{ - if (account.isGroupAccount) { - TextAvatarUtil.textGroupAvatar(account.fullName) - } else { - TextAvatarUtil.textAvatar(account.fullName) - } + getAccountByUserName(userName) + .flatMap { account => + response.setDateHeader("Last-Modified", account.updatedDate.getTime) + account.image + .map { image => + Some(RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))) + } + .getOrElse { + if (account.isGroupAccount) { + TextAvatarUtil.textGroupAvatar(account.fullName) + } else { + TextAvatarUtil.textAvatar(account.fullName) + } + } } - }.getOrElse{ - response.setHeader("Cache-Control", "max-age=3600") - Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png") - } + .getOrElse { + response.setHeader("Cache-Control", "max-age=3600") + Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png") + } } get("/:userName/_edit")(oneselfOnly { @@ -219,12 +302,15 @@ post("/:userName/_edit", editForm)(oneselfOnly { form => val userName = params("userName") getAccountByUserName(userName).map { account => - updateAccount(account.copy( - password = form.password.map(sha1).getOrElse(account.password), - fullName = form.fullName, - mailAddress = form.mailAddress, - description = form.description, - url = form.url)) + updateAccount( + account.copy( + password = form.password.map(sha1).getOrElse(account.password), + fullName = form.fullName, + mailAddress = form.mailAddress, + description = form.description, + url = form.url + ) + ) updateImage(userName, form.fileId, form.clearImage) flash += "info" -> "Account information has been updated." @@ -236,11 +322,12 @@ get("/:userName/_delete")(oneselfOnly { val userName = params("userName") - getAccountByUserName(userName, true).map { account => - if(isLastAdministrator(account)){ - flash += "error" -> "Account can't be removed because this is last one administrator." - redirect(s"/${userName}/_edit") - } else { + getAccountByUserName(userName, true).map { + account => + if (isLastAdministrator(account)) { + flash += "error" -> "Account can't be removed because this is last one administrator." + redirect(s"/${userName}/_edit") + } else { // // Remove repositories // getRepositoryNamesOfUser(userName).foreach { repositoryName => // deleteRepository(userName, repositoryName) @@ -248,16 +335,16 @@ // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) // } - // Remove from GROUP_MEMBER and COLLABORATOR - removeUserRelatedData(userName) - updateAccount(account.copy(isRemoved = true)) + // Remove from GROUP_MEMBER and COLLABORATOR + removeUserRelatedData(userName) + updateAccount(account.copy(isRemoved = true)) - // call hooks - PluginRegistry().getAccountHooks.foreach(_.deleted(userName)) + // call hooks + PluginRegistry().getAccountHooks.foreach(_.deleted(userName)) - session.invalidate - redirect("/") - } + session.invalidate + redirect("/") + } } getOrElse NotFound() }) @@ -286,9 +373,9 @@ getAccountByUserName(userName).map { x => var tokens = getAccessTokens(x.userName) val generatedToken = flash.get("generatedToken") match { - case Some((tokenId:Int, token:String)) => { + case Some((tokenId: Int, token: String)) => { val gt = tokens.find(_.accessTokenId == tokenId) - gt.map{ t => + gt.map { t => tokens = tokens.filterNot(_ == t) (t, token) } @@ -359,8 +446,9 @@ get("/:userName/_hooks/edit")(oneselfOnly { val userName = params("userName") getAccountByUserName(userName).flatMap { account => - getAccountWebHook(userName, params("url")).map { case (webhook, events) => - html.edithook(webhook, events, account, false) + getAccountWebHook(userName, params("url")).map { + case (webhook, events) => + html.edithook(webhook, events, account, false) } } getOrElse NotFound() }) @@ -386,7 +474,9 @@ import org.apache.http.util.EntityUtils import scala.concurrent.ExecutionContext.Implicits.global - def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => Array(h.getName, h.getValue) } + def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => + Array(h.getName, h.getValue) + } val userName = params("userName") val url = params("url") @@ -400,31 +490,49 @@ val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head - val toErrorMap: PartialFunction[Throwable, Map[String,String]] = { - case e: java.net.UnknownHostException => Map("error"-> ("Unknown host " + e.getMessage)) - case e: java.lang.IllegalArgumentException => Map("error"-> ("invalid url")) - case e: org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url")) - case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage)) + val toErrorMap: PartialFunction[Throwable, Map[String, String]] = { + case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage)) + case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url")) + case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url")) + case NonFatal(e) => Map("error" -> (e.getClass + " " + e.getMessage)) } contentType = formats("json") - org.json4s.jackson.Serialization.write(Map( - "url" -> url, - "request" -> Await.result(reqFuture.map(req => Map( - "headers" -> _headers(req.getAllHeaders), - "payload" -> json - )).recover(toErrorMap), 20 seconds), - "response" -> Await.result(resFuture.map(res => Map( - "status" -> res.getStatusLine(), - "body" -> EntityUtils.toString(res.getEntity()), - "headers" -> _headers(res.getAllHeaders()) - )).recover(toErrorMap), 20 seconds) - )) + org.json4s.jackson.Serialization.write( + Map( + "url" -> url, + "request" -> Await.result( + reqFuture + .map( + req => + Map( + "headers" -> _headers(req.getAllHeaders), + "payload" -> json + ) + ) + .recover(toErrorMap), + 20 seconds + ), + "response" -> Await.result( + resFuture + .map( + res => + Map( + "status" -> res.getStatusLine(), + "body" -> EntityUtils.toString(res.getEntity()), + "headers" -> _headers(res.getAllHeaders()) + ) + ) + .recover(toErrorMap), + 20 seconds + ) + ) + ) }) - get("/register"){ - if(context.settings.allowAccountRegistration){ - if(context.loginAccount.isDefined){ + get("/register") { + if (context.settings.allowAccountRegistration) { + if (context.loginAccount.isDefined) { redirect("/") } else { html.register() @@ -432,9 +540,17 @@ } else NotFound() } - post("/register", newForm){ form => - if(context.settings.allowAccountRegistration){ - createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.description, form.url) + post("/register", newForm) { form => + if (context.settings.allowAccountRegistration) { + createAccount( + form.userName, + sha1(form.password), + form.fullName, + form.mailAddress, + false, + form.description, + form.url + ) updateImage(form.userName, form.fileId, false) redirect("/signin") } else NotFound() @@ -446,17 +562,23 @@ post("/groups/new", newGroupForm)(usersOnly { form => createGroup(form.groupName, form.description, form.url) - updateGroupMembers(form.groupName, form.members.split(",").map { - _.split(":") match { - case Array(userName, isManager) => (userName, isManager.toBoolean) - } - }.toList) + updateGroupMembers( + form.groupName, + form.members + .split(",") + .map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + } + .toList + ) updateImage(form.groupName, form.fileId, false) redirect(s"/${form.groupName}") }) get("/:groupName/_editgroup")(managersOnly { - defining(params("groupName")){ groupName => + defining(params("groupName")) { groupName => getAccountByUserName(groupName, true).map { account => html.editgroup(account, getGroupMembers(groupName), flash.get("info")) } getOrElse NotFound() @@ -464,13 +586,14 @@ }) get("/:groupName/_deletegroup")(managersOnly { - defining(params("groupName")){ groupName => - // Remove from GROUP_MEMBER - updateGroupMembers(groupName, Nil) - // Disable group - getAccountByUserName(groupName, false).foreach { account => - updateGroup(groupName, account.description, account.url, true) - } + defining(params("groupName")) { + groupName => + // Remove from GROUP_MEMBER + updateGroupMembers(groupName, Nil) + // Disable group + getAccountByUserName(groupName, false).foreach { account => + updateGroup(groupName, account.description, account.url, true) + } // // Remove repositories // getRepositoryNamesOfUser(groupName).foreach { repositoryName => // deleteRepository(groupName, repositoryName) @@ -483,16 +606,23 @@ }) post("/:groupName/_editgroup", editGroupForm)(managersOnly { form => - defining(params("groupName"), form.members.split(",").map { - _.split(":") match { - case Array(userName, isManager) => (userName, isManager.toBoolean) - } - }.toList){ case (groupName, members) => - getAccountByUserName(groupName, true).map { account => - updateGroup(groupName, form.description, form.url, false) + defining( + params("groupName"), + form.members + .split(",") + .map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + } + .toList + ) { + case (groupName, members) => + getAccountByUserName(groupName, true).map { account => + updateGroup(groupName, form.description, form.url, false) - // Update GROUP_MEMBER - updateGroupMembers(form.groupName, members) + // Update GROUP_MEMBER + updateGroupMembers(form.groupName, members) // // Update COLLABORATOR for group repositories // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // removeCollaborators(form.groupName, repositoryName) @@ -501,12 +631,12 @@ // } // } - updateImage(form.groupName, form.fileId, form.clearImage) + updateImage(form.groupName, form.fileId, form.clearImage) - flash += "info" -> "Account information has been updated." - redirect(s"/${groupName}/_editgroup") + flash += "info" -> "Account information has been updated." + redirect(s"/${groupName}/_editgroup") - } getOrElse NotFound() + } getOrElse NotFound() } }) @@ -521,9 +651,17 @@ * Create new repository. */ post("/new", newRepositoryForm)(usersOnly { form => - LockUtil.lock(s"${form.owner}/${form.name}"){ - if(getRepository(form.owner, form.name).isEmpty){ - createRepository(context.loginAccount.get, form.owner, form.name, form.description, form.isPrivate, form.initOption, form.sourceUrl) + LockUtil.lock(s"${form.owner}/${form.name}") { + if (getRepository(form.owner, form.name).isEmpty) { + createRepository( + context.loginAccount.get, + form.owner, + form.name, + form.description, + form.isPrivate, + form.initOption, + form.sourceUrl + ) } } @@ -532,15 +670,20 @@ }) get("/:owner/:repository/fork")(readableUsersOnly { repository => - if(repository.repository.options.allowFork){ - val loginAccount = context.loginAccount.get - val loginUserName = loginAccount.userName - val groups = getGroupsByUserName(loginUserName) + if (repository.repository.options.allowFork) { + val loginAccount = context.loginAccount.get + val loginUserName = loginAccount.userName + val groups = getGroupsByUserName(loginUserName) groups match { case _: List[String] => val managerPermissions = groups.map { group => val members = getGroupMembers(group) - context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }) + context.loginAccount.exists( + x => + members.exists { member => + member.userName == x.userName && member.isManager + } + ) } helper.html.forkrepository( repository, @@ -552,13 +695,13 @@ }) post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) => - if(repository.repository.options.allowFork){ - val loginAccount = context.loginAccount.get + if (repository.repository.options.allowFork) { + val loginAccount = context.loginAccount.get val loginUserName = loginAccount.userName - val accountName = form.accountName + val accountName = form.accountName if (getRepository(accountName, repository.name).isDefined || - (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) { + (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) { // redirect to the repository if repository already exists redirect(s"/${accountName}/${repository.name}") } else { @@ -570,42 +713,49 @@ } else BadRequest() }) - private def existsAccount: Constraint = new Constraint(){ + private def existsAccount: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None + if (getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None } - private def uniqueRepository: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = { + private def uniqueRepository: Constraint = new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = { for { userName <- params.optionValue("owner") - _ <- getRepositoryNamesOfUser(userName).find(_ == value) + _ <- getRepositoryNamesOfUser(userName).find(_ == value) } yield { "Repository already exists." } } } - private def members: Constraint = new Constraint(){ + private def members: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = { - if(value.split(",").exists { - _.split(":") match { case Array(userName, isManager) => isManager.toBoolean } - }) None else Some("Must select one manager at least.") + if (value.split(",").exists { + _.split(":") match { case Array(userName, isManager) => isManager.toBoolean } + }) None + else Some("Must select one manager at least.") } } - private def validPublicKey: Constraint = new Constraint(){ - override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match { - case Some(_) if !getAllKeys().exists(_.publicKey == value) => None - case _ => Some("Key is invalid.") - } + private def validPublicKey: Constraint = new Constraint() { + override def validate(name: String, value: String, messages: Messages): Option[String] = + SshUtil.str2PublicKey(value) match { + case Some(_) if !getAllKeys().exists(_.publicKey == value) => None + case _ => Some("Key is invalid.") + } } - private def validAccountName: Constraint = new Constraint(){ + private def validAccountName: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = { getAccountByUserName(value) match { case Some(_) => None - case None => Some("Invalid Group/User Account.") + case None => Some("Invalid Group/User Account.") } } } diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 2044558..1cf042d 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -20,31 +20,32 @@ import scala.concurrent.Await import scala.concurrent.duration.Duration -class ApiController extends ApiControllerBase - with RepositoryService - with AccountService - with ProtectedBranchService - with IssuesService - with LabelsService - with MilestonesService - with PullRequestService - with CommitsService - with CommitStatusService - with RepositoryCreationService - with IssueCreationService - with HandleCommentService - with WebHookService - with WebHookPullRequestService - with WebHookIssueCommentService - with WikiService - with ActivityService - with PrioritiesService - with OwnerAuthenticator - with UsersAuthenticator - with GroupManagerAuthenticator - with ReferrerAuthenticator - with ReadableUsersAuthenticator - with WritableUsersAuthenticator +class ApiController + extends ApiControllerBase + with RepositoryService + with AccountService + with ProtectedBranchService + with IssuesService + with LabelsService + with MilestonesService + with PullRequestService + with CommitsService + with CommitStatusService + with RepositoryCreationService + with IssueCreationService + with HandleCommentService + with WebHookService + with WebHookPullRequestService + with WebHookIssueCommentService + with WikiService + with ActivityService + with PrioritiesService + with OwnerAuthenticator + with UsersAuthenticator + with GroupManagerAuthenticator + with ReferrerAuthenticator + with ReadableUsersAuthenticator + with WritableUsersAuthenticator trait ApiControllerBase extends ControllerBase { self: RepositoryService @@ -68,22 +69,22 @@ with WritableUsersAuthenticator => /** - * 404 for non-implemented api - */ + * 404 for non-implemented api + */ get("/api/v3/*") { NotFound() } /** - * https://developer.github.com/v3/#root-endpoint - */ + * https://developer.github.com/v3/#root-endpoint + */ get("/api/v3") { JsonFormat(ApiEndPoint()) } /** - * https://developer.github.com/v3/orgs/#get-an-organization - */ + * https://developer.github.com/v3/orgs/#get-an-organization + */ get("/api/v3/orgs/:groupName") { getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account => JsonFormat(ApiUser(account)) @@ -101,43 +102,59 @@ } /** - * https://developer.github.com/v3/repos/#list-organization-repositories - */ + * https://developer.github.com/v3/repos/#list-organization-repositories + */ get("/api/v3/orgs/:orgName/repos") { - JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)}) + JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r => + ApiRepository(r, getAccountByUserName(r.owner).get) + }) } + /** * https://developer.github.com/v3/repos/#list-user-repositories */ get("/api/v3/users/:userName/repos") { - JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)}) + JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r => + ApiRepository(r, getAccountByUserName(r.owner).get) + }) } /* * https://developer.github.com/v3/repos/branches/#list-branches */ - get ("/api/v3/repos/:owner/:repo/branches")(referrersOnly { repository => - JsonFormat(JGitUtil.getBranches( - owner = repository.owner, - name = repository.name, - defaultBranch = repository.repository.defaultBranch, - origin = repository.repository.originUserName.isEmpty - ).map { br => - ApiBranchForList(br.name, ApiBranchCommit(br.commitId)) - }) + get("/api/v3/repos/:owner/:repo/branches")(referrersOnly { repository => + JsonFormat( + JGitUtil + .getBranches( + owner = repository.owner, + name = repository.name, + defaultBranch = repository.repository.defaultBranch, + origin = repository.repository.originUserName.isEmpty + ) + .map { br => + ApiBranchForList(br.name, ApiBranchCommit(br.commitId)) + } + ) }) /** - * https://developer.github.com/v3/repos/branches/#get-branch - */ - get ("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository => + * https://developer.github.com/v3/repos/branches/#get-branch + */ + get("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository => //import gitbucket.core.api._ - (for{ + (for { branch <- params.get("splat") if repository.branchList.contains(branch) - br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch) + br <- getBranches( + repository.owner, + repository.name, + repository.repository.defaultBranch, + repository.repository.originUserName.isEmpty + ).find(_.name == branch) } yield { val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) - JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))) + JsonFormat( + ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository)) + ) }) getOrElse NotFound() }) @@ -157,65 +174,85 @@ val path = multiParams("splat").head match { case s if s.isEmpty => "." - case s => s + case s => s } val refStr = params.getOrElse("ref", repository.repository.defaultBranch) - using(Git.open(getRepositoryDir(params("owner"), params("repo")))){ git => - val fileList = getFileList(git, refStr, path) - if (fileList.isEmpty) { // file or NotFound - getFileInfo(git, refStr, path).flatMap(f => { - val largeFile = params.get("large_file").exists(s => s.equals("true")) - val content = getContentFromId(git, f.id, largeFile) - request.getHeader("Accept") match { - case "application/vnd.github.v3.raw" => { - contentType = "application/vnd.github.v3.raw" - content - } - case "application/vnd.github.v3.html" if isRenderable(f.name) => { - contentType = "application/vnd.github.v3.html" - content.map(c => - List( - "
", "
", - renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body, - "
", "
" - ).mkString - ) - } - case "application/vnd.github.v3.html" => { - contentType = "application/vnd.github.v3.html" - content.map(c => - List( - "
", "
", "
",
-                  play.twirl.api.HtmlFormat.escape(new String(c)).body,
-                  "
", "
", "
" - ).mkString - ) - } - case _ => - Some(JsonFormat(ApiContents(f, RepositoryName(repository), content))) - } - }).getOrElse(NotFound()) - } else { // directory - JsonFormat(fileList.map{f => ApiContents(f, RepositoryName(repository), None)}) - } + using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { + git => + val fileList = getFileList(git, refStr, path) + if (fileList.isEmpty) { // file or NotFound + getFileInfo(git, refStr, path) + .flatMap(f => { + val largeFile = params.get("large_file").exists(s => s.equals("true")) + val content = getContentFromId(git, f.id, largeFile) + request.getHeader("Accept") match { + case "application/vnd.github.v3.raw" => { + contentType = "application/vnd.github.v3.raw" + content + } + case "application/vnd.github.v3.html" if isRenderable(f.name) => { + contentType = "application/vnd.github.v3.html" + content.map( + c => + List( + "
", + "
", + renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body, + "
", + "
" + ).mkString + ) + } + case "application/vnd.github.v3.html" => { + contentType = "application/vnd.github.v3.html" + content.map( + c => + List( + "
", + "
", + "
",
+                        play.twirl.api.HtmlFormat.escape(new String(c)).body,
+                        "
", + "
", + "
" + ).mkString + ) + } + case _ => + Some(JsonFormat(ApiContents(f, RepositoryName(repository), content))) + } + }) + .getOrElse(NotFound()) + } else { // directory + JsonFormat(fileList.map { f => + ApiContents(f, RepositoryName(repository), None) + }) + } } }) /* * https://developer.github.com/v3/git/refs/#get-a-reference */ - get("/api/v3/repos/:owner/:repo/git/refs/*") (referrersOnly { repository => + get("/api/v3/repos/:owner/:repo/git/refs/*")(referrersOnly { repository => val revstr = multiParams("splat").head using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git => val ref = git.getRepository().findRef(revstr) - if(ref != null){ + if (ref != null) { val sha = ref.getObjectId().name() JsonFormat(ApiRef(revstr, ApiObject(sha))) } else { - val refs = git.getRepository().getAllRefs().asScala + val refs = git + .getRepository() + .getAllRefs() + .asScala .collect { case (str, ref) if str.startsWith("refs/" + revstr) => ref } JsonFormat(refs.map { ref => @@ -229,7 +266,7 @@ /** * https://developer.github.com/v3/repos/collaborators/#list-collaborators */ - get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository => + get("/api/v3/repos/:owner/:repo/collaborators")(referrersOnly { repository => // TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members. JsonFormat(getCollaboratorUserNames(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get))) }) @@ -247,9 +284,9 @@ * List user's own repository * https://developer.github.com/v3/repos/#list-your-repositories */ - get("/api/v3/user/repos")(usersOnly{ - JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map{ - r => ApiRepository(r, getAccountByUserName(r.owner).get) + get("/api/v3/user/repos")(usersOnly { + JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r => + ApiRepository(r, getAccountByUserName(r.owner).get) }) }) @@ -263,8 +300,15 @@ data <- extractFromJsonBody[CreateARepository] if data.isValid } yield { LockUtil.lock(s"${owner}/${data.name}") { - if(getRepository(owner, data.name).isEmpty){ - val f = createRepository(context.loginAccount.get, owner, data.name, data.description, data.`private`, data.auto_init) + if (getRepository(owner, data.name).isEmpty) { + val f = createRepository( + context.loginAccount.get, + owner, + data.name, + data.description, + data.`private`, + data.auto_init + ) Await.result(f, Duration.Inf) val repository = getRepository(owner, data.name).get JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get))) @@ -288,8 +332,15 @@ data <- extractFromJsonBody[CreateARepository] if data.isValid } yield { LockUtil.lock(s"${groupName}/${data.name}") { - if(getRepository(groupName, data.name).isEmpty){ - val f = createRepository(context.loginAccount.get, groupName, data.name, data.description, data.`private`, data.auto_init) + if (getRepository(groupName, data.name).isEmpty) { + val f = createRepository( + context.loginAccount.get, + groupName, + data.name, + data.description, + data.`private`, + data.auto_init + ) Await.result(f, Duration.Inf) val repository = getRepository(groupName, data.name).get JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get))) @@ -308,13 +359,24 @@ */ patch("/api/v3/repos/:owner/:repo/branches/*")(ownerOnly { repository => import gitbucket.core.api._ - (for{ - branch <- params.get("splat") if repository.branchList.contains(branch) + (for { + branch <- params.get("splat") if repository.branchList.contains(branch) protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection) - br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch) + br <- getBranches( + repository.owner, + repository.name, + repository.repository.defaultBranch, + repository.repository.originUserName.isEmpty + ).find(_.name == branch) } yield { - if(protection.enabled){ - enableBranchProtection(repository.owner, repository.name, branch, protection.status.enforcement_level == ApiBranchProtection.Everyone, protection.status.contexts) + if (protection.enabled) { + enableBranchProtection( + repository.owner, + repository.name, + branch, + protection.status.enforcement_level == ApiBranchProtection.Everyone, + protection.status.contexts + ) } else { disableBranchProtection(repository.owner, repository.name, branch) } @@ -326,15 +388,15 @@ * @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status * but not enabled. */ - get("/api/v3/rate_limit"){ + get("/api/v3/rate_limit") { contentType = formats("json") // this message is same as github enterprise... org.scalatra.NotFound(ApiError("Rate limiting is not enabled.")) } /** - * https://developer.github.com/v3/issues/#list-issues-for-a-repository - */ + * https://developer.github.com/v3/issues/#list-issues-for-a-repository + */ get("/api/v3/repos/:owner/:repository/issues")(referrersOnly { repository => val page = IssueSearchCondition.page(request) // TODO: more api spec condition @@ -344,18 +406,20 @@ val issues: List[(Issue, Account)] = searchIssueByApi( condition = condition, - offset = (page - 1) * PullRequestLimit, - limit = PullRequestLimit, - repos = repository.owner -> repository.name + offset = (page - 1) * PullRequestLimit, + limit = PullRequestLimit, + repos = repository.owner -> repository.name ) - JsonFormat(issues.map { case (issue, issueUser) => - ApiIssue( - issue = issue, - repositoryName = RepositoryName(repository), - user = ApiUser(issueUser), - labels = getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))) - ) + JsonFormat(issues.map { + case (issue, issueUser) => + ApiIssue( + issue = issue, + repositoryName = RepositoryName(repository), + user = ApiUser(issueUser), + labels = getIssueLabels(repository.owner, repository.name, issue.issueId) + .map(ApiLabel(_, RepositoryName(repository))) + ) }) }) @@ -363,22 +427,28 @@ * https://developer.github.com/v3/issues/#get-a-single-issue */ get("/api/v3/repos/:owner/:repository/issues/:id")(referrersOnly { repository => - (for{ - issueId <- params("id").toIntOpt + (for { + issueId <- params("id").toIntOpt issue <- getIssue(repository.owner, repository.name, issueId.toString) openedUser <- getAccountByUserName(issue.openedUserName) } yield { - JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(openedUser), - getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))))) + JsonFormat( + ApiIssue( + issue, + RepositoryName(repository), + ApiUser(openedUser), + getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))) + ) + ) }) getOrElse NotFound() }) /** - * https://developer.github.com/v3/issues/#create-an-issue - */ + * https://developer.github.com/v3/issues/#create-an-issue + */ post("/api/v3/repos/:owner/:repository/issues")(readableUsersOnly { repository => - if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator? - (for{ + if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator? + (for { data <- extractFromJsonBody[CreateAnIssue] loginAccount <- context.loginAccount } yield { @@ -391,9 +461,17 @@ milestone.map(_.milestoneId), None, data.labels, - loginAccount) - JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(loginAccount), - getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))))) + loginAccount + ) + JsonFormat( + ApiIssue( + issue, + RepositoryName(repository), + ApiUser(loginAccount), + getIssueLabels(repository.owner, repository.name, issue.issueId) + .map(ApiLabel(_, RepositoryName(repository))) + ) + ) }) getOrElse NotFound() } else Unauthorized() }) @@ -402,11 +480,14 @@ * https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue */ get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository => - (for{ - issueId <- params("id").toIntOpt - comments = getCommentsForApi(repository.owner, repository.name, issueId) + (for { + issueId <- params("id").toIntOpt + comments = getCommentsForApi(repository.owner, repository.name, issueId) } yield { - JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) }) + JsonFormat(comments.map { + case (issueComment, user, issue) => + ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) + }) }) getOrElse NotFound() }) @@ -414,15 +495,23 @@ * https://developer.github.com/v3/issues/comments/#create-a-comment */ post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository => - (for{ - issueId <- params("id").toIntOpt - issue <- getIssue(repository.owner, repository.name, issueId.toString) - body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty - action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName)) - (issue, id) <- handleComment(issue, Some(body), repository, action) + (for { + issueId <- params("id").toIntOpt + issue <- getIssue(repository.owner, repository.name, issueId.toString) + body <- extractFromJsonBody[CreateAComment].map(_.body) if !body.isEmpty + action = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName)) + (issue, id) <- handleComment(issue, Some(body), repository, action) issueComment <- getComment(repository.owner, repository.name, id.toString()) } yield { - JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest)) + JsonFormat( + ApiComment( + issueComment, + RepositoryName(repository), + issueId, + ApiUser(context.loginAccount.get), + issue.isPullRequest + ) + ) }) getOrElse NotFound() }) @@ -451,7 +540,7 @@ * https://developer.github.com/v3/issues/labels/#create-a-label */ post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { repository => - (for{ + (for { data <- extractFromJsonBody[CreateALabel] if data.isValid } yield { LockUtil.lock(RepositoryName(repository).fullName) { @@ -462,10 +551,12 @@ } getOrElse NotFound() } else { // TODO ApiError should support errors field to enhance compatibility of GitHub API - UnprocessableEntity(ApiError( - "Validation Failed", - Some("https://developer.github.com/v3/issues/labels/#create-a-label") - )) + UnprocessableEntity( + ApiError( + "Validation Failed", + Some("https://developer.github.com/v3/issues/labels/#create-a-label") + ) + ) } } }) getOrElse NotFound() @@ -476,24 +567,29 @@ * https://developer.github.com/v3/issues/labels/#update-a-label */ patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository => - (for{ + (for { data <- extractFromJsonBody[CreateALabel] if data.isValid } yield { LockUtil.lock(RepositoryName(repository).fullName) { - getLabel(repository.owner, repository.name, params("labelName")).map { label => - if (getLabel(repository.owner, repository.name, data.name).isEmpty) { - updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color) - JsonFormat(ApiLabel( - getLabel(repository.owner, repository.name, label.labelId).get, - RepositoryName(repository) - )) - } else { - // TODO ApiError should support errors field to enhance compatibility of GitHub API - UnprocessableEntity(ApiError( - "Validation Failed", - Some("https://developer.github.com/v3/issues/labels/#create-a-label") - )) - } + getLabel(repository.owner, repository.name, params("labelName")).map { + label => + if (getLabel(repository.owner, repository.name, data.name).isEmpty) { + updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color) + JsonFormat( + ApiLabel( + getLabel(repository.owner, repository.name, label.labelId).get, + RepositoryName(repository) + ) + ) + } else { + // TODO ApiError should support errors field to enhance compatibility of GitHub API + UnprocessableEntity( + ApiError( + "Validation Failed", + Some("https://developer.github.com/v3/issues/labels/#create-a-label") + ) + ) + } } getOrElse NotFound() } }) getOrElse NotFound() @@ -524,22 +620,24 @@ val issues: List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = searchPullRequestByApi( condition = condition, - offset = (page - 1) * PullRequestLimit, - limit = PullRequestLimit, - repos = repository.owner -> repository.name + offset = (page - 1) * PullRequestLimit, + limit = PullRequestLimit, + repos = repository.owner -> repository.name ) - JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) => - ApiPullRequest( - issue = issue, - pullRequest = pullRequest, - headRepo = ApiRepository(headRepo, ApiUser(headOwner)), - baseRepo = ApiRepository(repository, ApiUser(baseOwner)), - user = ApiUser(issueUser), - labels = getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))), - assignee = assignee.map(ApiUser.apply), - mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) - ) + JsonFormat(issues.map { + case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignee) => + ApiPullRequest( + issue = issue, + pullRequest = pullRequest, + headRepo = ApiRepository(headRepo, ApiUser(headOwner)), + baseRepo = ApiRepository(repository, ApiUser(baseOwner)), + user = ApiUser(issueUser), + labels = getIssueLabels(repository.owner, repository.name, issue.issueId) + .map(ApiLabel(_, RepositoryName(repository))), + assignee = assignee.map(ApiUser.apply), + mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) + ) }) }) @@ -547,26 +645,34 @@ * https://developer.github.com/v3/pulls/#get-a-single-pull-request */ get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository => - (for{ - issueId <- params("id").toIntOpt + (for { + issueId <- params("id").toIntOpt (issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) - users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set.empty) + users = getAccountsByUserNames( + Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), + Set.empty + ) baseOwner <- users.get(repository.owner) headOwner <- users.get(pullRequest.requestUserName) issueUser <- users.get(issue.openedUserName) - assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) } - headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) + assignee = issue.assignedUserName.flatMap { userName => + getAccountByUserName(userName, false) + } + headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) } yield { - JsonFormat(ApiPullRequest( - issue = issue, - pullRequest = pullRequest, - headRepo = ApiRepository(headRepo, ApiUser(headOwner)), - baseRepo = ApiRepository(repository, ApiUser(baseOwner)), - user = ApiUser(issueUser), - labels = getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))), - assignee = assignee.map(ApiUser.apply), - mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) - )) + JsonFormat( + ApiPullRequest( + issue = issue, + pullRequest = pullRequest, + headRepo = ApiRepository(headRepo, ApiUser(headOwner)), + baseRepo = ApiRepository(repository, ApiUser(baseOwner)), + user = ApiUser(issueUser), + labels = getIssueLabels(repository.owner, repository.name, issue.issueId) + .map(ApiLabel(_, RepositoryName(repository))), + assignee = assignee.map(ApiUser.apply), + mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) + ) + ) }) getOrElse NotFound() }) @@ -576,16 +682,26 @@ get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository => val owner = repository.owner val name = repository.name - params("id").toIntOpt.flatMap{ issueId => - getPullRequest(owner, name, issueId) map { case(issue, pullreq) => - using(Git.open(getRepositoryDir(owner, name))){ git => - val oldId = git.getRepository.resolve(pullreq.commitIdFrom) - val newId = git.getRepository.resolve(pullreq.commitIdTo) - val repoFullName = RepositoryName(repository) - val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map { c => ApiCommitListItem(new CommitInfo(c), repoFullName) }.toList - JsonFormat(commits) + params("id").toIntOpt.flatMap { + issueId => + getPullRequest(owner, name, issueId) map { + case (issue, pullreq) => + using(Git.open(getRepositoryDir(owner, name))) { git => + val oldId = git.getRepository.resolve(pullreq.commitIdFrom) + val newId = git.getRepository.resolve(pullreq.commitIdTo) + val repoFullName = RepositoryName(repository) + val commits = git.log + .addRange(oldId, newId) + .call + .iterator + .asScala + .map { c => + ApiCommitListItem(new CommitInfo(c), repoFullName) + } + .toList + JsonFormat(commits) + } } - } } getOrElse NotFound() }) @@ -600,15 +716,24 @@ * https://developer.github.com/v3/repos/statuses/#create-a-status */ post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository => - (for{ - ref <- params.get("sha") - sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) - data <- extractFromJsonBody[CreateAStatus] if data.isValid - creator <- context.loginAccount - state <- CommitState.valueOf(data.state) - statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"), - state, data.target_url, data.description, new java.util.Date(), creator) - status <- getCommitStatus(repository.owner, repository.name, statusId) + (for { + ref <- params.get("sha") + sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) + data <- extractFromJsonBody[CreateAStatus] if data.isValid + creator <- context.loginAccount + state <- CommitState.valueOf(data.state) + statusId = createCommitStatus( + repository.owner, + repository.name, + sha, + data.context.getOrElse("default"), + state, + data.target_url, + data.description, + new java.util.Date(), + creator + ) + status <- getCommitStatus(repository.owner, repository.name, statusId) } yield { JsonFormat(ApiCommitStatus(status, ApiUser(creator))) }) getOrElse NotFound() @@ -620,12 +745,13 @@ * ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name. */ val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository => - (for{ + (for { ref <- params.get("ref") sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) } yield { - JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) => - ApiCommitStatus(status, ApiUser(creator)) + JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map { + case (status, creator) => + ApiCommitStatus(status, ApiUser(creator)) }) }) getOrElse NotFound() }) @@ -635,7 +761,7 @@ * * legacy route */ - get("/api/v3/repos/:owner/:repo/statuses/:ref"){ + get("/api/v3/repos/:owner/:repo/statuses/:ref") { listStatusesRoute.action() } @@ -645,10 +771,10 @@ * ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name. */ get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository => - (for{ - ref <- params.get("ref") + (for { + ref <- params.get("ref") owner <- getAccountByUserName(repository.owner) - sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) + sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) } yield { val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha) JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner))) @@ -660,24 +786,27 @@ */ get("/api/v3/repos/:owner/:repo/commits/:sha")(referrersOnly { repository => val owner = repository.owner - val name = repository.name - val sha = params("sha") + val name = repository.name + val sha = params("sha") - using(Git.open(getRepositoryDir(owner, name))){ git => - val repo = git.getRepository - val objectId = repo.resolve(sha) - val commitInfo = using(new RevWalk(repo)){ revWalk => - new CommitInfo(revWalk.parseCommit(objectId)) - } + using(Git.open(getRepositoryDir(owner, name))) { + git => + val repo = git.getRepository + val objectId = repo.resolve(sha) + val commitInfo = using(new RevWalk(repo)) { revWalk => + new CommitInfo(revWalk.parseCommit(objectId)) + } - JsonFormat(ApiCommits( - repositoryName = RepositoryName(repository), - commitInfo = commitInfo, - diffs = JGitUtil.getDiffs(git, Some(commitInfo.parents.head), commitInfo.id, false, true), - author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), - committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress), - commentCount = getCommitComment(repository.owner, repository.name, sha).size - )) + JsonFormat( + ApiCommits( + repositoryName = RepositoryName(repository), + commitInfo = commitInfo, + diffs = JGitUtil.getDiffs(git, Some(commitInfo.parents.head), commitInfo.id, false, true), + author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), + committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress), + commentCount = getCommitComment(repository.owner, repository.name, sha).size + ) + ) } }) @@ -705,11 +834,11 @@ hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName /** - * non-GitHub compatible API for Jenkins-Plugin - */ + * non-GitHub compatible API for Jenkins-Plugin + */ get("/api/v3/repos/:owner/:repo/raw/*")(referrersOnly { repository => val (id, path) = repository.splitPath(multiParams("splat").head) - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) getPathObjectId(git, path, revCommit).map { objectId => @@ -719,10 +848,9 @@ }) /** - * non-GitHub compatible API for listing plugins - */ - get("/api/v3/gitbucket/plugins"){ - PluginRegistry().getPlugins().map{ApiPlugin(_)} + * non-GitHub compatible API for listing plugins + */ + get("/api/v3/gitbucket/plugins") { + PluginRegistry().getPlugins().map { ApiPlugin(_) } } } - diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index b4ad4a7..ecb3d13 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -31,9 +31,14 @@ /** * Provides generic features for controller implementations. */ -abstract class ControllerBase extends ScalatraFilter - with ValidationSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations - with SystemSettingsService { +abstract class ControllerBase + extends ScalatraFilter + with ValidationSupport + with JacksonJsonSupport + with I18nSupport + with FlashMapSupport + with Validations + with SystemSettingsService { private val logger = LoggerFactory.getLogger(getClass) @@ -45,31 +50,32 @@ override def requestPath(uri: String, idx: Int): String = { val path = super.requestPath(uri, idx) - if(path != "/" && path.endsWith("/")){ + if (path != "/" && path.endsWith("/")) { path.substring(0, path.length - 1) } else { path } } - override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try { - val httpRequest = request.asInstanceOf[HttpServletRequest] - val context = request.getServletContext.getContextPath - val path = httpRequest.getRequestURI.substring(context.length) + override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = + try { + val httpRequest = request.asInstanceOf[HttpServletRequest] + val context = request.getServletContext.getContextPath + val path = httpRequest.getRequestURI.substring(context.length) - if(path.startsWith("/git/") || path.startsWith("/git-lfs/")){ - // Git repository - chain.doFilter(request, response) - } else { - if(path.startsWith("/api/v3/")){ - httpRequest.setAttribute(Keys.Request.APIv3, true) + if (path.startsWith("/git/") || path.startsWith("/git-lfs/")) { + // Git repository + chain.doFilter(request, response) + } else { + if (path.startsWith("/api/v3/")) { + httpRequest.setAttribute(Keys.Request.APIv3, true) + } + // Scalatra actions + super.doFilter(request, response, chain) } - // Scalatra actions - super.doFilter(request, response, chain) + } finally { + contextCache.remove(); } - } finally { - contextCache.remove(); - } private val contextCache = new java.lang.ThreadLocal[Context]() @@ -87,36 +93,37 @@ } } - private def LoginAccount: Option[Account] = request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount)) + private def LoginAccount: Option[Account] = + request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount)) - def ajaxGet(path : String)(action : => Any) : Route = - super.get(path){ + def ajaxGet(path: String)(action: => Any): Route = + super.get(path) { request.setAttribute(Keys.Request.Ajax, "true") action } - override def ajaxGet[T](path : String, form : ValueType[T])(action : T => Any) : Route = - super.ajaxGet(path, form){ form => + override def ajaxGet[T](path: String, form: ValueType[T])(action: T => Any): Route = + super.ajaxGet(path, form) { form => request.setAttribute(Keys.Request.Ajax, "true") action(form) } - def ajaxPost(path : String)(action : => Any) : Route = - super.post(path){ + def ajaxPost(path: String)(action: => Any): Route = + super.post(path) { request.setAttribute(Keys.Request.Ajax, "true") action } - override def ajaxPost[T](path : String, form : ValueType[T])(action : T => Any) : Route = - super.ajaxPost(path, form){ form => + override def ajaxPost[T](path: String, form: ValueType[T])(action: T => Any): Route = + super.ajaxPost(path, form) { form => request.setAttribute(Keys.Request.Ajax, "true") action(form) } protected def NotFound() = - if(request.hasAttribute(Keys.Request.Ajax)){ + if (request.hasAttribute(Keys.Request.Ajax)) { org.scalatra.NotFound() - } else if(request.hasAttribute(Keys.Request.APIv3)){ + } else if (request.hasAttribute(Keys.Request.APIv3)) { contentType = formats("json") org.scalatra.NotFound(ApiError("Not Found")) } else { @@ -124,7 +131,7 @@ } private def isBrowser(userAgent: String): Boolean = { - if(userAgent == null || userAgent.isEmpty){ + if (userAgent == null || userAgent.isEmpty) { false } else { val data = Classifier.parse(userAgent) @@ -134,35 +141,41 @@ } protected def Unauthorized()(implicit context: Context) = - if(request.hasAttribute(Keys.Request.Ajax)){ + if (request.hasAttribute(Keys.Request.Ajax)) { org.scalatra.Unauthorized() - } else if(request.hasAttribute(Keys.Request.APIv3)){ + } else if (request.hasAttribute(Keys.Request.APIv3)) { contentType = formats("json") org.scalatra.Unauthorized(ApiError("Requires authentication")) - } else if(!isBrowser(request.getHeader("USER-AGENT"))){ + } else if (!isBrowser(request.getHeader("USER-AGENT"))) { org.scalatra.Unauthorized() } else { - if(context.loginAccount.isDefined){ + if (context.loginAccount.isDefined) { org.scalatra.Unauthorized(redirect("/")) } else { - if(request.getMethod.toUpperCase == "POST"){ + if (request.getMethod.toUpperCase == "POST") { org.scalatra.Unauthorized(redirect("/signin")) } else { - org.scalatra.Unauthorized(redirect("/signin?redirect=" + StringUtil.urlEncode( - defining(request.getQueryString){ queryString => - request.getRequestURI.substring(request.getContextPath.length) + (if(queryString != null) "?" + queryString else "") - } - ))) + org.scalatra.Unauthorized( + redirect( + "/signin?redirect=" + StringUtil.urlEncode( + defining(request.getQueryString) { queryString => + request.getRequestURI.substring(request.getContextPath.length) + (if (queryString != null) + "?" + queryString + else "") + } + ) + ) + ) } } } - error{ + error { case e => { logger.error(s"Catch unhandled error in request: ${request}", e) - if(request.hasAttribute(Keys.Request.Ajax)){ + if (request.hasAttribute(Keys.Request.Ajax)) { org.scalatra.InternalServerError() - } else if(request.hasAttribute(Keys.Request.APIv3)){ + } else if (request.hasAttribute(Keys.Request.APIv3)) { contentType = formats("json") org.scalatra.InternalServerError(ApiError("Internal Server Error")) } else { @@ -171,30 +184,39 @@ } } - override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty, - includeContextPath: Boolean = true, includeServletPath: Boolean = true, - absolutize: Boolean = true, withSessionId: Boolean = true) - (implicit request: HttpServletRequest, response: HttpServletResponse): String = + override def url( + path: String, + params: Iterable[(String, Any)] = Iterable.empty, + includeContextPath: Boolean = true, + includeServletPath: Boolean = true, + absolutize: Boolean = true, + withSessionId: Boolean = true + )(implicit request: HttpServletRequest, response: HttpServletResponse): String = if (path.startsWith("http")) path else baseUrl + super.url(path, params, false, false, false) /** * Extends scalatra-form's trim rule to eliminate CR and LF. */ - protected def trim2[T](valueType: SingleValueType[T]): SingleValueType[T] = new SingleValueType[T](){ + protected def trim2[T](valueType: SingleValueType[T]): SingleValueType[T] = new SingleValueType[T]() { def convert(value: String, messages: Messages): T = valueType.convert(trim(value), messages) - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Seq[(String, String)] = valueType.validate(name, trim(value), params, messages) - private def trim(value: String): String = if(value == null) null else value.replace("\r\n", "").trim + private def trim(value: String): String = if (value == null) null else value.replace("\r\n", "").trim } /** * Use this method to response the raw data against XSS. */ protected def RawData[T](contentType: String, rawData: T): T = { - if(contentType.split(";").head.trim.toLowerCase.startsWith("text/html")){ + if (contentType.split(";").head.trim.toLowerCase.startsWith("text/html")) { this.contentType = "text/plain" } else { this.contentType = contentType @@ -204,35 +226,39 @@ } // jenkins send message as 'application/x-www-form-urlencoded' but scalatra already parsed as multi-part-request. - def extractFromJsonBody[A](implicit request:HttpServletRequest, mf:Manifest[A]): Option[A] = { - (request.contentType.map(_.split(";").head.toLowerCase) match{ + def extractFromJsonBody[A](implicit request: HttpServletRequest, mf: Manifest[A]): Option[A] = { + (request.contentType.map(_.split(";").head.toLowerCase) match { case Some("application/x-www-form-urlencoded") => multiParams.keys.headOption.map(parse(_)) - case Some("application/json") => Some(parsedBody) - case _ => Some(parse(request.body)) + case Some("application/json") => Some(parsedBody) + case _ => Some(parse(request.body)) }).filterNot(_ == JNothing).flatMap(j => Try(j.extract[A]).toOption) } protected def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = { @scala.annotation.tailrec def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match { - case true if(walk.getPathString == path) => Some(walk.getObjectId(0)) - case true => _getPathObjectId(path, walk) - case false => None + case true if (walk.getPathString == path) => Some(walk.getObjectId(0)) + case true => _getPathObjectId(path, walk) + case false => None } - using(new TreeWalk(git.getRepository)){ treeWalk => + using(new TreeWalk(git.getRepository)) { treeWalk => treeWalk.addTree(revCommit.getTree) treeWalk.setRecursive(true) _getPathObjectId(path, treeWalk) } } - protected def responseRawFile(git: Git, objectId: ObjectId, path: String, - repository: RepositoryService.RepositoryInfo): Unit = { - JGitUtil.getObjectLoaderFromId(git, objectId){ loader => + protected def responseRawFile( + git: Git, + objectId: ObjectId, + path: String, + repository: RepositoryService.RepositoryInfo + ): Unit = { + JGitUtil.getObjectLoaderFromId(git, objectId) { loader => contentType = FileUtil.getMimeType(path) - if(loader.isLarge){ + if (loader.isLarge) { response.setContentLength(loader.getSize.toInt) loader.copyTo(response.outputStream) } else { @@ -240,11 +266,11 @@ val text = new String(bytes, "UTF-8") val attrs = JGitUtil.getLfsObjects(text) - if(attrs.nonEmpty) { + if (attrs.nonEmpty) { response.setContentLength(attrs("size").toInt) val oid = attrs("oid").split(":")(1) - using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))){ in => + using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))) { in => IOUtils.copy(in, response.getOutputStream) } } else { @@ -259,17 +285,21 @@ /** * Context object for the current request. */ -case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){ +case class Context( + settings: SystemSettingsService.SystemSettings, + loginAccount: Option[Account], + request: HttpServletRequest +) { val path = settings.baseUrl.getOrElse(request.getContextPath) val currentPath = request.getRequestURI.substring(request.getContextPath.length) val baseUrl = settings.baseUrl(request) val host = new java.net.URL(baseUrl).getHost val platform = request.getHeader("User-Agent") match { - case null => null - case agent if agent.contains("Mac") => "mac" + case null => null + case agent if agent.contains("Mac") => "mac" case agent if agent.contains("Linux") => "linux" - case agent if agent.contains("Win") => "windows" - case _ => null + case agent if agent.contains("Win") => "windows" + case _ => null } val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null @@ -280,7 +310,7 @@ * Cached object are available during a request. */ def cache[A](key: String)(action: => A): A = - defining(Keys.Request.Cache(key)){ cacheKey => + defining(Keys.Request.Cache(key)) { cacheKey => Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse { val newObject = action request.setAttribute(cacheKey, newObject) @@ -294,10 +324,10 @@ * Base trait for controllers which manages account information. */ trait AccountManagementControllerBase extends ControllerBase { - self: AccountService => + self: AccountService => protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit = - if(clearImage){ + if (clearImage) { getAccountByUserName(userName).flatMap(_.image).map { image => new java.io.File(getUserUploadDir(userName), image).delete() updateAvatarImage(userName, None) @@ -306,36 +336,63 @@ fileId.map { fileId => val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get) val uploadDir = getUserUploadDir(userName) - if(!uploadDir.exists){ + if (!uploadDir.exists) { uploadDir.mkdirs() } - Thumbnails.of(new java.io.File(getTemporaryDir(session.getId), fileId)) + Thumbnails + .of(new java.io.File(getTemporaryDir(session.getId), fileId)) .size(324, 324) .toFile(new java.io.File(uploadDir, filename)) updateAvatarImage(userName, Some(filename)) } } - protected def uniqueUserName: Constraint = new Constraint(){ + protected def uniqueUserName: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - getAccountByUserName(value, true).map { _ => "User already exists." } + getAccountByUserName(value, true).map { _ => + "User already exists." + } } - protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = { + protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = { getAccountByMailAddress(value, true) - .filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.optionValue(paramName) } - .map { _ => "Mail address is already registered." } + .filter { x => + if (paramName.isEmpty) true else Some(x.userName) != params.optionValue(paramName) + } + .map { _ => + "Mail address is already registered." + } } } - val allReservedNames = Set("git", "admin", "upload", "api", "assets", "plugin-assets", "signin", "signout", "register", "activities.atom", "sidebar-collapse", "groups", "new") - protected def reservedNames(): Constraint = new Constraint(){ - override def validate(name: String, value: String, messages: Messages): Option[String] = if(allReservedNames.contains(value)){ - Some(s"${value} is reserved") - } else { - None - } + val allReservedNames = Set( + "git", + "admin", + "upload", + "api", + "assets", + "plugin-assets", + "signin", + "signout", + "register", + "activities.atom", + "sidebar-collapse", + "groups", + "new" + ) + protected def reservedNames(): Constraint = new Constraint() { + override def validate(name: String, value: String, messages: Messages): Option[String] = + if (allReservedNames.contains(value)) { + Some(s"${value} is reserved") + } else { + None + } } } diff --git a/src/main/scala/gitbucket/core/controller/DashboardController.scala b/src/main/scala/gitbucket/core/controller/DashboardController.scala index f422ba5..2c8a982 100644 --- a/src/main/scala/gitbucket/core/controller/DashboardController.scala +++ b/src/main/scala/gitbucket/core/controller/DashboardController.scala @@ -6,13 +6,20 @@ import gitbucket.core.util.Implicits._ import gitbucket.core.service.IssuesService._ -class DashboardController extends DashboardControllerBase - with IssuesService with PullRequestService with RepositoryService with AccountService with CommitsService - with LabelsService with PrioritiesService with MilestonesService with UsersAuthenticator +class DashboardController + extends DashboardControllerBase + with IssuesService + with PullRequestService + with RepositoryService + with AccountService + with CommitsService + with LabelsService + with PrioritiesService + with MilestonesService + with UsersAuthenticator trait DashboardControllerBase extends ControllerBase { - self: IssuesService with PullRequestService with RepositoryService with AccountService - with UsersAuthenticator => + self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator => get("/dashboard/issues")(usersOnly { searchIssues("created_by") @@ -59,51 +66,52 @@ private def searchIssues(filter: String) = { import IssuesService._ - val userName = context.loginAccount.get.userName + val userName = context.loginAccount.get.userName val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName) val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name) - val page = IssueSearchCondition.page(request) + val page = IssueSearchCondition.page(request) html.issues( searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*), page, - countIssue(condition.copy(state = "open" ), false, userRepos: _*), + countIssue(condition.copy(state = "open"), false, userRepos: _*), countIssue(condition.copy(state = "closed"), false, userRepos: _*), filter match { - case "assigned" => condition.copy(assigned = Some(Some(userName))) + case "assigned" => condition.copy(assigned = Some(Some(userName))) case "mentioned" => condition.copy(mentioned = Some(userName)) - case _ => condition.copy(author = Some(userName)) + case _ => condition.copy(author = Some(userName)) }, filter, getGroupNames(userName), Nil, - getUserRepositories(userName, withoutPhysicalInfo = true)) + getUserRepositories(userName, withoutPhysicalInfo = true) + ) } private def searchPullRequests(filter: String) = { import IssuesService._ import PullRequestService._ - val userName = context.loginAccount.get.userName + val userName = context.loginAccount.get.userName val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName) - val allRepos = getAllRepositories(userName) - val page = IssueSearchCondition.page(request) + val allRepos = getAllRepositories(userName) + val page = IssueSearchCondition.page(request) html.pulls( searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*), page, - countIssue(condition.copy(state = "open" ), true, allRepos: _*), + countIssue(condition.copy(state = "open"), true, allRepos: _*), countIssue(condition.copy(state = "closed"), true, allRepos: _*), filter match { - case "assigned" => condition.copy(assigned = Some(Some(userName))) + case "assigned" => condition.copy(assigned = Some(Some(userName))) case "mentioned" => condition.copy(mentioned = Some(userName)) - case _ => condition.copy(author = Some(userName)) + case _ => condition.copy(author = Some(userName)) }, filter, getGroupNames(userName), Nil, - getUserRepositories(userName, withoutPhysicalInfo = true)) + getUserRepositories(userName, withoutPhysicalInfo = true) + ) } - } diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala index ca00ee2..3df5d8e 100644 --- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala +++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala @@ -19,95 +19,133 @@ * * This servlet saves uploaded file. */ -class FileUploadController extends ScalatraServlet - with FileUploadSupport - with RepositoryService - with AccountService - with ReleaseService{ +class FileUploadController + extends ScalatraServlet + with FileUploadSupport + with RepositoryService + with AccountService + with ReleaseService { configureMultipartHandling(MultipartConfig(maxFileSize = Some(FileUtil.MaxFileSize))) - post("/image"){ - execute({ (file, fileId) => - FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get) - session += Keys.Session.Upload(fileId) -> file.name - }, FileUtil.isImage) + post("/image") { + execute( + { (file, fileId) => + FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get) + session += Keys.Session.Upload(fileId) -> file.name + }, + 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("/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( - getAttachedDir(params("owner"), params("repository")), - fileId + "." + FileUtil.getExtension(file.getName)), file.get) - }, _ => true) + post("/file/:owner/:repository") { + execute( + { (file, fileId) => + FileUtils.writeByteArrayToFile( + new java.io.File( + getAttachedDir(params("owner"), params("repository")), + fileId + "." + FileUtil.getExtension(file.getName) + ), + file.get + ) + }, + _ => true + ) } - post("/wiki/:owner/:repository"){ + post("/wiki/:owner/:repository") { // Don't accept not logged-in users - session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account => - val owner = params("owner") - val repository = params("repository") + session.get(Keys.Session.LoginAccount).collect { + case loginAccount: Account => + val owner = params("owner") + val repository = params("repository") - // Check whether logged-in user is collaborator - onlyWikiEditable(owner, repository, loginAccount){ - execute({ (file, fileId) => - val fileName = file.getName - LockUtil.lock(s"${owner}/${repository}/wiki") { - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => - val builder = DirCache.newInCore.builder() - val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") + // Check whether logged-in user is collaborator + onlyWikiEditable(owner, repository, loginAccount) { + execute( + { (file, fileId) => + val fileName = file.getName + LockUtil.lock(s"${owner}/${repository}/wiki") { + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { + git => + val builder = DirCache.newInCore.builder() + val inserter = git.getRepository.newObjectInserter() + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - if(headId != null){ - JGitUtil.processTree(git, headId){ (path, tree) => - if(path != fileName){ - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } + if (headId != null) { + JGitUtil.processTree(git, headId) { (path, tree) => + if (path != fileName) { + builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) + } + } + } + + val bytes = IOUtils.toByteArray(file.getInputStream) + builder.add( + JGitUtil.createDirCacheEntry( + fileName, + FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, bytes) + ) + ) + builder.finish() + + val newHeadId = JGitUtil.createNewCommit( + git, + inserter, + headId, + builder.getDirCache.writeTree(inserter), + Constants.HEAD, + loginAccount.fullName, + loginAccount.mailAddress, + s"Uploaded ${fileName}" + ) + + fileName } } - - val bytes = IOUtils.toByteArray(file.getInputStream) - builder.add(JGitUtil.createDirCacheEntry(fileName, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))) - builder.finish() - - val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, s"Uploaded ${fileName}") - - fileName - } - } - }, _ => true) - } + }, + _ => true + ) + } } getOrElse BadRequest() } - post("/release/:owner/:repository/:tag"){ - session.get(Keys.Session.LoginAccount).collect { case _: Account => - val owner = params("owner") - val repository = params("repository") - val tag = params("tag") - execute({ (file, fileId) => - FileUtils.writeByteArrayToFile( - new java.io.File(getReleaseFilesDir(owner, repository), tag + "/" + fileId), - file.get - ) - }, _ => true) - }.getOrElse(BadRequest()) + post("/release/:owner/:repository/:tag") { + session + .get(Keys.Session.LoginAccount) + .collect { + case _: Account => + val owner = params("owner") + val repository = params("repository") + val tag = params("tag") + execute({ (file, fileId) => + FileUtils.writeByteArrayToFile( + new java.io.File(getReleaseFilesDir(owner, repository), tag + "/" + fileId), + file.get + ) + }, _ => true) + } + .getOrElse(BadRequest()) } post("/import") { import JDBCUtil._ - session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin => - execute({ (file, fileId) => - request2Session(request).conn.importAsSQL(file.getInputStream) - }, _ => true) + session.get(Keys.Session.LoginAccount).collect { + case loginAccount: Account if loginAccount.isAdmin => + execute({ (file, fileId) => + request2Session(request).conn.importAsSQL(file.getInputStream) + }, _ => true) } redirect("/admin/data") } @@ -115,24 +153,26 @@ private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = { implicit val session = Database.getSession(request) getRepository(owner, repository) match { - case Some(x) => x.repository.options.wikiOption match { - case "ALL" if !x.repository.isPrivate => action - case "PUBLIC" if hasGuestRole(owner, repository, Some(loginAccount)) => action - case "PRIVATE" if hasDeveloperRole(owner, repository, Some(loginAccount)) => action - case _ => BadRequest() - } + case Some(x) => + x.repository.options.wikiOption match { + case "ALL" if !x.repository.isPrivate => action + case "PUBLIC" if hasGuestRole(owner, repository, Some(loginAccount)) => action + case "PRIVATE" if hasDeveloperRole(owner, repository, Some(loginAccount)) => action + case _ => BadRequest() + } case None => BadRequest() } } - private def execute(f: (FileItem, String) => Unit , mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match { - case Some(file) if(mimeTypeChcker(file.name)) => - defining(FileUtil.generateFileId){ fileId => - f(file, fileId) - contentType = "text/plain" - Ok(fileId) - } - case _ => BadRequest() - } + private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = + fileParams.get("file") match { + case Some(file) if (mimeTypeChcker(file.name)) => + defining(FileUtil.generateFileId) { fileId => + f(file, fileId) + contentType = "text/plain" + Ok(fileId) + } + case _ => BadRequest() + } } diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index 1cb7838..e6f8311 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -13,22 +13,21 @@ import org.scalatra.Ok import org.scalatra.forms._ - -class IndexController extends IndexControllerBase - with RepositoryService - with ActivityService - with AccountService - with RepositorySearchService - with IssuesService - with LabelsService - with MilestonesService - with PrioritiesService - with UsersAuthenticator - with ReferrerAuthenticator - with AccessTokenService - with AccountFederationService - with OpenIDConnectService - +class IndexController + extends IndexControllerBase + with RepositoryService + with ActivityService + with AccountService + with RepositorySearchService + with IssuesService + with LabelsService + with MilestonesService + with PrioritiesService + with UsersAuthenticator + with ReferrerAuthenticator + with AccessTokenService + with AccountFederationService + with OpenIDConnectService trait IndexControllerBase extends ControllerBase { self: RepositoryService @@ -59,37 +58,43 @@ case class OidcContext(state: State, nonce: Nonce, redirectBackURI: String) - get("/"){ - context.loginAccount.map { account => - val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName) - gitbucket.core.html.index( - getRecentActivitiesByOwners(visibleOwnerSet), - Nil, - getUserRepositories(account.userName, withoutPhysicalInfo = true), - showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(account.userName)) - }.getOrElse { - gitbucket.core.html.index( - getRecentActivities(), - getVisibleRepositories(None, withoutPhysicalInfo = true), - Nil, - showBannerToCreatePersonalAccessToken = false) - } + get("/") { + context.loginAccount + .map { account => + val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName) + gitbucket.core.html.index( + getRecentActivitiesByOwners(visibleOwnerSet), + Nil, + getUserRepositories(account.userName, withoutPhysicalInfo = true), + showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken( + account.userName + ) + ) + } + .getOrElse { + gitbucket.core.html.index( + getRecentActivities(), + getVisibleRepositories(None, withoutPhysicalInfo = true), + Nil, + showBannerToCreatePersonalAccessToken = false + ) + } } - get("/signin"){ + get("/signin") { val redirect = params.get("redirect") - if(redirect.isDefined && redirect.get.startsWith("/")){ + if (redirect.isDefined && redirect.get.startsWith("/")) { flash += Keys.Flash.Redirect -> redirect.get } gitbucket.core.html.signin(flash.get("userName"), flash.get("password"), flash.get("error")) } - post("/signin", signinForm){ form => + post("/signin", signinForm) { form => authenticate(context.settings, form.userName, form.password) match { case Some(account) => flash.get(Keys.Flash.Redirect) match { case Some(redirectUrl: String) => signin(account, redirectUrl + form.hash.getOrElse("")) - case _ => signin(account) + case _ => signin(account) } case None => flash += "userName" -> form.userName @@ -100,17 +105,20 @@ } /** - * Initiate an OpenID Connect authentication request. - */ + * Initiate an OpenID Connect authentication request. + */ post("/signin/oidc") { context.settings.oidc.map { oidc => val redirectURI = new URI(s"$baseUrl/signin/oidc") val authenticationRequest = createOIDCAuthenticationRequest(oidc.issuer, oidc.clientID, redirectURI) val redirectBackURI = flash.get(Keys.Flash.Redirect) match { case Some(redirectBackURI: String) => redirectBackURI + params.getOrElse("hash", "") - case _ => "/" + case _ => "/" } - session.setAttribute(Keys.Session.OidcContext, OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)) + session.setAttribute( + Keys.Session.OidcContext, + OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI) + ) redirect(authenticationRequest.toURI.toString) } getOrElse { NotFound() @@ -118,8 +126,8 @@ } /** - * Handle an OpenID Connect authentication response. - */ + * Handle an OpenID Connect authentication response. + */ get("/signin/oidc") { context.settings.oidc.map { oidc => val redirectURI = new URI(s"$baseUrl/signin/oidc") @@ -142,33 +150,33 @@ } } - get("/signout"){ + get("/signout") { session.invalidate redirect("/") } - get("/activities.atom"){ + get("/activities.atom") { contentType = "application/atom+xml; type=feed" xml.feed(getRecentActivities()) } - post("/sidebar-collapse"){ - if(params("collapse") == "true"){ + post("/sidebar-collapse") { + if (params("collapse") == "true") { session.setAttribute("sidebar-collapse", "true") - } else { + } else { session.setAttribute("sidebar-collapse", null) } Ok() } /** - * Set account information into HttpSession and redirect. - */ + * Set account information into HttpSession and redirect. + */ private def signin(account: Account, redirectUrl: String = "/") = { session.setAttribute(Keys.Session.LoginAccount, account) updateLastLoginDate(account.userName) - if(LDAPUtil.isDummyMailAddress(account)) { + if (LDAPUtil.isDummyMailAddress(account)) { redirect("/" + account.userName + "/_edit") } @@ -184,23 +192,28 @@ */ get("/_user/proposals")(usersOnly { contentType = formats("json") - val user = params("user").toBoolean + val user = params("user").toBoolean val group = params("group").toBoolean org.json4s.jackson.Serialization.write( - Map("options" -> ( - getAllUsers(false) - .withFilter { t => (user, group) match { - case (true, true) => true - case (true, false) => !t.isGroupAccount - case (false, true) => t.isGroupAccount - case (false, false) => false - }}.map { t => - Map( - "label" -> s"@${t.userName} ${t.fullName}", - "value" -> t.userName - ) - } - )) + Map( + "options" -> ( + getAllUsers(false) + .withFilter { t => + (user, group) match { + case (true, true) => true + case (true, false) => !t.isGroupAccount + case (false, true) => t.isGroupAccount + case (false, false) => false + } + } + .map { t => + Map( + "label" -> s"@${t.userName} ${t.fullName}", + "value" -> t.userName + ) + } + ) + ) ) }) @@ -210,47 +223,68 @@ */ post("/_user/existence")(usersOnly { getAccountByUserName(params("userName")).map { account => - if(account.isGroupAccount) "group" else "user" + if (account.isGroupAccount) "group" else "user" } getOrElse "" }) // TODO Move to RepositoryViwerController? get("/:owner/:repository/search")(referrersOnly { repository => - defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")){ case (query, target) => - val page = try { - val i = params.getOrElse("page", "1").toInt - if(i <= 0) 1 else i - } catch { - case e: NumberFormatException => 1 - } + defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")) { + case (query, target) => + val page = try { + val i = params.getOrElse("page", "1").toInt + if (i <= 0) 1 else i + } catch { + case e: NumberFormatException => 1 + } - target.toLowerCase match { - case "issue" => gitbucket.core.search.html.issues( - if(query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil, - query, page, repository) + target.toLowerCase match { + case "issue" => + gitbucket.core.search.html.issues( + if (query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil, + query, + page, + repository + ) - case "wiki" => gitbucket.core.search.html.wiki( - if(query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil, - query, page, repository) + case "wiki" => + gitbucket.core.search.html.wiki( + if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil, + query, + page, + repository + ) - case _ => gitbucket.core.search.html.code( - if(query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil, - query, page, repository) - } + case _ => + gitbucket.core.search.html.code( + if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil, + query, + page, + repository + ) + } } }) - get("/search"){ + get("/search") { val query = params.getOrElse("query", "").trim.toLowerCase - val visibleRepositories = getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true) + val visibleRepositories = + getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true) val repositories = visibleRepositories.filter { repository => repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0 } - context.loginAccount.map { account => - gitbucket.core.search.html.repositories(query, repositories, Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true)) - }.getOrElse { - gitbucket.core.search.html.repositories(query, repositories, visibleRepositories, Nil) - } + context.loginAccount + .map { account => + gitbucket.core.search.html.repositories( + query, + repositories, + Nil, + getUserRepositories(account.userName, withoutPhysicalInfo = true) + ) + } + .getOrElse { + gitbucket.core.search.html.repositories(query, repositories, visibleRepositories, Nil) + } } } diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index a23a0d3..8fb441a 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -11,23 +11,23 @@ import org.scalatra.forms._ import org.scalatra.{BadRequest, Ok} - -class IssuesController extends IssuesControllerBase - with IssuesService - with RepositoryService - with AccountService - with LabelsService - with MilestonesService - with ActivityService - with HandleCommentService - with IssueCreationService - with ReadableUsersAuthenticator - with ReferrerAuthenticator - with WritableUsersAuthenticator - with PullRequestService - with WebHookIssueCommentService - with CommitsService - with PrioritiesService +class IssuesController + extends IssuesControllerBase + with IssuesService + with RepositoryService + with AccountService + with LabelsService + with MilestonesService + with ActivityService + with HandleCommentService + with IssueCreationService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator + with PullRequestService + with WebHookIssueCommentService + with CommitsService + with PrioritiesService trait IssuesControllerBase extends ControllerBase { self: IssuesService @@ -45,40 +45,46 @@ with WebHookIssueCommentService with PrioritiesService => - case class IssueCreateForm(title: String, content: Option[String], - assignedUserName: Option[String], milestoneId: Option[Int], priorityId: Option[Int], labelNames: Option[String]) + case class IssueCreateForm( + title: String, + content: Option[String], + assignedUserName: Option[String], + milestoneId: Option[Int], + priorityId: Option[Int], + labelNames: Option[String] + ) case class CommentForm(issueId: Int, content: String) case class IssueStateForm(issueId: Int, content: Option[String]) val issueCreateForm = mapping( - "title" -> trim(label("Title", text(required))), - "content" -> trim(optional(text())), - "assignedUserName" -> trim(optional(text())), - "milestoneId" -> trim(optional(number())), - "priorityId" -> trim(optional(number())), - "labelNames" -> trim(optional(text())) - )(IssueCreateForm.apply) + "title" -> trim(label("Title", text(required))), + "content" -> trim(optional(text())), + "assignedUserName" -> trim(optional(text())), + "milestoneId" -> trim(optional(number())), + "priorityId" -> trim(optional(number())), + "labelNames" -> trim(optional(text())) + )(IssueCreateForm.apply) val issueTitleEditForm = mapping( "title" -> trim(label("Title", text(required))) - )(x => x) + )(x => x) val issueEditForm = mapping( "content" -> trim(optional(text())) - )(x => x) + )(x => x) val commentForm = mapping( - "issueId" -> label("Issue Id", number()), - "content" -> trim(label("Comment", text(required))) - )(CommentForm.apply) + "issueId" -> label("Issue Id", number()), + "content" -> trim(label("Comment", text(required))) + )(CommentForm.apply) val issueStateForm = mapping( - "issueId" -> label("Issue Id", number()), - "content" -> trim(optional(text())) - )(IssueStateForm.apply) + "issueId" -> label("Issue Id", number()), + "content" -> trim(optional(text())) + )(IssueStateForm.apply) get("/:owner/:repository/issues")(referrersOnly { repository => val q = request.getParameter("q") - if(Option(q).exists(_.contains("is:pr"))){ + if (Option(q).exists(_.contains("is:pr"))) { redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}") } else { searchIssues(repository) @@ -86,45 +92,50 @@ }) get("/:owner/:repository/issues/:id")(referrersOnly { repository => - defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) => - getIssue(owner, name, issueId) map { issue => - if(issue.isPullRequest){ - redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") - } else { - html.issue( - issue, - getComments(owner, name, issueId.toInt), - getIssueLabels(owner, name, issueId.toInt), - getAssignableUserNames(owner, name), - getMilestonesWithIssueCount(owner, name), - getPriorities(owner, name), - getLabels(owner, name), - isIssueEditable(repository), - isIssueManageable(repository), - repository) - } - } getOrElse NotFound() + defining(repository.owner, repository.name, params("id")) { + case (owner, name, issueId) => + getIssue(owner, name, issueId) map { + issue => + if (issue.isPullRequest) { + redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") + } else { + html.issue( + issue, + getComments(owner, name, issueId.toInt), + getIssueLabels(owner, name, issueId.toInt), + getAssignableUserNames(owner, name), + getMilestonesWithIssueCount(owner, name), + getPriorities(owner, name), + getLabels(owner, name), + isIssueEditable(repository), + isIssueManageable(repository), + repository + ) + } + } getOrElse NotFound() } }) get("/:owner/:repository/issues/new")(readableUsersOnly { repository => - if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator? - defining(repository.owner, repository.name){ case (owner, name) => - html.create( - getAssignableUserNames(owner, name), - getMilestones(owner, name), - getPriorities(owner, name), - getDefaultPriority(owner, name), - getLabels(owner, name), - isIssueManageable(repository), - getContentTemplate(repository, "ISSUE_TEMPLATE"), - repository) + if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator? + defining(repository.owner, repository.name) { + case (owner, name) => + html.create( + getAssignableUserNames(owner, name), + getMilestones(owner, name), + getPriorities(owner, name), + getDefaultPriority(owner, name), + getLabels(owner, name), + isIssueManageable(repository), + getContentTemplate(repository, "ISSUE_TEMPLATE"), + repository + ) } } else Unauthorized() }) post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) => - if(isIssueEditable(repository)){ // TODO Should this check is provided by authenticator? + if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator? val issue = createIssue( repository, form.title, @@ -133,133 +144,146 @@ form.milestoneId, form.priorityId, form.labelNames.toArray.flatMap(_.split(",")), - context.loginAccount.get) + context.loginAccount.get + ) redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}") } else Unauthorized() }) ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) => - defining(repository.owner, repository.name){ case (owner, name) => - getIssue(owner, name, params("id")).map { issue => - if(isEditableContent(owner, name, issue.openedUserName)){ - // update issue - updateIssue(owner, name, issue.issueId, title, issue.content) - // extract references and create refer comment - createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get) + defining(repository.owner, repository.name) { + case (owner, name) => + getIssue(owner, name, params("id")).map { issue => + if (isEditableContent(owner, name, issue.openedUserName)) { + // update issue + updateIssue(owner, name, issue.issueId, title, issue.content) + // extract references and create refer comment + createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get) - redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") - } else Unauthorized() - } getOrElse NotFound() + redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") + } else Unauthorized() + } getOrElse NotFound() } }) ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) => - defining(repository.owner, repository.name){ case (owner, name) => - getIssue(owner, name, params("id")).map { issue => - if(isEditableContent(owner, name, issue.openedUserName)){ - // update issue - updateIssue(owner, name, issue.issueId, issue.title, content) - // extract references and create refer comment - createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get) + defining(repository.owner, repository.name) { + case (owner, name) => + getIssue(owner, name, params("id")).map { issue => + if (isEditableContent(owner, name, issue.openedUserName)) { + // update issue + updateIssue(owner, name, issue.issueId, issue.title, content) + // extract references and create refer comment + createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get) - redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") - } else Unauthorized() - } getOrElse NotFound() + redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") + } else Unauthorized() + } getOrElse NotFound() } }) post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => - val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) - handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) => - redirect(s"/${repository.owner}/${repository.name}/${ - if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") + val actionOpt = + params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) + handleComment(issue, Some(form.content), repository, actionOpt) map { + case (issue, id) => + redirect( + s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}" + ) } } getOrElse NotFound() }) post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => - val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) - handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) => - redirect(s"/${repository.owner}/${repository.name}/${ - if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") + val actionOpt = + params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) + handleComment(issue, form.content, repository, actionOpt) map { + case (issue, id) => + redirect( + s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}" + ) } } getOrElse NotFound() }) ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => - defining(repository.owner, repository.name){ case (owner, name) => - getComment(owner, name, params("id")).map { comment => - if(isEditableContent(owner, name, comment.commentedUserName)){ - updateComment(comment.issueId, comment.commentId, form.content) - redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}") - } else Unauthorized() - } getOrElse NotFound() + defining(repository.owner, repository.name) { + case (owner, name) => + getComment(owner, name, params("id")).map { comment => + if (isEditableContent(owner, name, comment.commentedUserName)) { + updateComment(comment.issueId, comment.commentId, form.content) + redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}") + } else Unauthorized() + } getOrElse NotFound() } }) ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository => - defining(repository.owner, repository.name){ case (owner, name) => - getComment(owner, name, params("id")).map { comment => - if(isEditableContent(owner, name, comment.commentedUserName)){ - Ok(deleteComment(comment.issueId, comment.commentId)) - } else Unauthorized() - } getOrElse NotFound() + defining(repository.owner, repository.name) { + case (owner, name) => + getComment(owner, name, params("id")).map { comment => + if (isEditableContent(owner, name, comment.commentedUserName)) { + Ok(deleteComment(comment.issueId, comment.commentId)) + } else Unauthorized() + } getOrElse NotFound() } }) ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository => - getIssue(repository.owner, repository.name, params("id")) map { x => - if(isEditableContent(x.userName, x.repositoryName, x.openedUserName)){ - params.get("dataType") collect { - case t if t == "html" => html.editissue(x.content, x.issueId, repository) - } getOrElse { - contentType = formats("json") - org.json4s.jackson.Serialization.write( - Map( - "title" -> x.title, - "content" -> Markdown.toHtml( - markdown = x.content getOrElse "No description given.", - repository = repository, - enableWikiLink = false, - enableRefsLink = true, - enableAnchor = true, - enableLineBreaks = true, - enableTaskList = true, - hasWritePermission = true + getIssue(repository.owner, repository.name, params("id")) map { + x => + if (isEditableContent(x.userName, x.repositoryName, x.openedUserName)) { + params.get("dataType") collect { + case t if t == "html" => html.editissue(x.content, x.issueId, repository) + } getOrElse { + contentType = formats("json") + org.json4s.jackson.Serialization.write( + Map( + "title" -> x.title, + "content" -> Markdown.toHtml( + markdown = x.content getOrElse "No description given.", + repository = repository, + enableWikiLink = false, + enableRefsLink = true, + enableAnchor = true, + enableLineBreaks = true, + enableTaskList = true, + hasWritePermission = true + ) ) ) - ) - } - } else Unauthorized() + } + } else Unauthorized() } getOrElse NotFound() }) ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository => - getComment(repository.owner, repository.name, params("id")) map { x => - if(isEditableContent(x.userName, x.repositoryName, x.commentedUserName)){ - params.get("dataType") collect { - case t if t == "html" => html.editcomment(x.content, x.commentId, repository) - } getOrElse { - contentType = formats("json") - org.json4s.jackson.Serialization.write( - Map( - "content" -> view.Markdown.toHtml( - markdown = x.content, - repository = repository, - enableWikiLink = false, - enableRefsLink = true, - enableAnchor = true, - enableLineBreaks = true, - enableTaskList = true, - hasWritePermission = true + getComment(repository.owner, repository.name, params("id")) map { + x => + if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName)) { + params.get("dataType") collect { + case t if t == "html" => html.editcomment(x.content, x.commentId, repository) + } getOrElse { + contentType = formats("json") + org.json4s.jackson.Serialization.write( + Map( + "content" -> view.Markdown.toHtml( + markdown = x.content, + repository = repository, + enableWikiLink = false, + enableRefsLink = true, + enableAnchor = true, + enableLineBreaks = true, + enableTaskList = true, + hasWritePermission = true + ) ) ) - ) - } - } else Unauthorized() + } + } else Unauthorized() } getOrElse NotFound() }) @@ -270,21 +294,27 @@ }) ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository => - defining(params("id").toInt){ issueId => + defining(params("id").toInt) { issueId => registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true) html.labellist(getIssueLabels(repository.owner, repository.name, issueId)) } }) ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository => - defining(params("id").toInt){ issueId => + defining(params("id").toInt) { issueId => deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true) html.labellist(getIssueLabels(repository.owner, repository.name, issueId)) } }) ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository => - updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"), true) + updateAssignedUserName( + repository.owner, + repository.name, + params("id").toInt, + assignedUserName("assignedUserName"), + true + ) Ok("updated") }) @@ -292,9 +322,11 @@ updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"), true) milestoneId("milestoneId").map { milestoneId => getMilestonesWithIssueCount(repository.owner, repository.name) - .find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) => - gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount) - } getOrElse NotFound() + .find(_._1.milestoneId == milestoneId) + .map { + case (_, openCount, closeCount) => + gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount) + } getOrElse NotFound() } getOrElse Ok() }) @@ -305,25 +337,28 @@ }) post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository => - defining(params.get("value")){ action => - action match { - case Some("open") => executeBatch(repository) { issueId => - getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => - handleComment(issue, None, repository, Some("reopen")) - } + defining(params.get("value")) { + action => + action match { + case Some("open") => + executeBatch(repository) { issueId => + getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => + handleComment(issue, None, repository, Some("reopen")) + } + } + case Some("close") => + executeBatch(repository) { issueId => + getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => + handleComment(issue, None, repository, Some("close")) + } + } + case _ => BadRequest() } - case Some("close") => executeBatch(repository) { issueId => - getIssue(repository.owner, repository.name, issueId.toString).foreach { issue => - handleComment(issue, None, repository, Some("close")) - } - } - case _ => BadRequest() - } } }) post("/:owner/:repository/issues/batchedit/label")(writableUsersOnly { repository => - params("value").toIntOpt.map{ labelId => + params("value").toIntOpt.map { labelId => executeBatch(repository) { issueId => getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse { registerIssueLabel(repository.owner, repository.name, issueId, labelId, true) @@ -333,7 +368,7 @@ }) post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository => - defining(assignedUserName("value")){ value => + defining(assignedUserName("value")) { value => executeBatch(repository) { updateAssignedUserName(repository.owner, repository.name, _, value, true) } @@ -341,7 +376,7 @@ }) post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository => - defining(milestoneId("value")){ value => + defining(milestoneId("value")) { value => executeBatch(repository) { updateMilestoneId(repository.owner, repository.name, _, value, true) } @@ -349,7 +384,7 @@ }) post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository => - defining(priorityId("value")){ value => + defining(priorityId("value")) { value => executeBatch(repository) { updatePriorityId(repository.owner, repository.name, _, value, true) } @@ -358,7 +393,7 @@ get("/:owner/:repository/_attached/:file")(referrersOnly { repository => (Directory.getAttachedDir(repository.owner, repository.name) match { - case dir if(dir.exists && dir.isDirectory) => + case dir if (dir.exists && dir.isDirectory) => dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file => response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""") RawData(FileUtil.getMimeType(file.getName), file) @@ -372,7 +407,7 @@ val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt) private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = { - params("checked").split(',') map(_.toInt) foreach execute + params("checked").split(',') map (_.toInt) foreach execute params("from") match { case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues") case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls") @@ -380,13 +415,14 @@ } private def searchIssues(repository: RepositoryService.RepositoryInfo) = { - defining(repository.owner, repository.name){ case (owner, repoName) => - val page = IssueSearchCondition.page(request) + defining(repository.owner, repository.name) { + case (owner, repoName) => + val page = IssueSearchCondition.page(request) - // retrieve search condition - val condition = IssueSearchCondition(request) + // retrieve search condition + val condition = IssueSearchCondition(request) - html.list( + html.list( "issues", searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), page, @@ -394,19 +430,22 @@ getMilestones(owner, repoName), getPriorities(owner, repoName), getLabels(owner, repoName), - countIssue(condition.copy(state = "open" ), false, owner -> repoName), + countIssue(condition.copy(state = "open"), false, owner -> repoName), countIssue(condition.copy(state = "closed"), false, owner -> repoName), condition, repository, isIssueEditable(repository), - isIssueManageable(repository)) + isIssueManageable(repository) + ) } } /** * Tests whether an issue or a comment is editable by a logged-in user. */ - private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = { + private def isEditableContent(owner: String, repository: String, author: String)( + implicit context: Context + ): Boolean = { hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName } } diff --git a/src/main/scala/gitbucket/core/controller/LabelsController.scala b/src/main/scala/gitbucket/core/controller/LabelsController.scala index 9ab083e..224a571 100644 --- a/src/main/scala/gitbucket/core/controller/LabelsController.scala +++ b/src/main/scala/gitbucket/core/controller/LabelsController.scala @@ -1,7 +1,14 @@ package gitbucket.core.controller import gitbucket.core.issues.labels.html -import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService, MilestonesService, PrioritiesService} +import gitbucket.core.service.{ + RepositoryService, + AccountService, + IssuesService, + LabelsService, + MilestonesService, + PrioritiesService +} import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} import gitbucket.core.util.Implicits._ import gitbucket.core.util.SyntaxSugars._ @@ -9,29 +16,38 @@ import org.scalatra.i18n.Messages import org.scalatra.Ok -class LabelsController extends LabelsControllerBase - with IssuesService with RepositoryService with AccountService - with LabelsService with PrioritiesService with MilestonesService - with ReferrerAuthenticator with WritableUsersAuthenticator +class LabelsController + extends LabelsControllerBase + with IssuesService + with RepositoryService + with AccountService + with LabelsService + with PrioritiesService + with MilestonesService + with ReferrerAuthenticator + with WritableUsersAuthenticator trait LabelsControllerBase extends ControllerBase { - self: LabelsService with IssuesService with RepositoryService - with ReferrerAuthenticator with WritableUsersAuthenticator => + self: LabelsService + with IssuesService + with RepositoryService + with ReferrerAuthenticator + with WritableUsersAuthenticator => case class LabelForm(labelName: String, color: String) val labelForm = mapping( - "labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))), - "labelColor" -> trim(label("Color", text(required, color))) + "labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))), + "labelColor" -> trim(label("Color", text(required, color))) )(LabelForm.apply) - get("/:owner/:repository/issues/labels")(referrersOnly { repository => html.list( getLabels(repository.owner, repository.name), countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository => @@ -45,7 +61,8 @@ // TODO futility countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository => @@ -61,7 +78,8 @@ // TODO futility countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository => @@ -72,26 +90,34 @@ /** * Constraint for the identifier such as user name, repository name or page name. */ - private def labelName: Constraint = new Constraint(){ + private def labelName: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(value.contains(',')){ + if (value.contains(',')) { Some(s"${name} contains invalid character.") - } else if(value.startsWith("_") || value.startsWith("-")){ + } else if (value.startsWith("_") || value.startsWith("-")) { Some(s"${name} starts with invalid character.") } else { None } } - private def uniqueLabelName: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = { - val owner = params.value("owner") + private def uniqueLabelName: Constraint = new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = { + val owner = params.value("owner") val repository = params.value("repository") - params.optionValue("labelId").map { labelId => - getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.") - }.getOrElse { - getLabel(owner, repository, value).map(_ => "Name has already been taken.") - } + params + .optionValue("labelId") + .map { labelId => + getLabel(owner, repository, value).filter(_.labelId != labelId.toInt).map(_ => "Name has already been taken.") + } + .getOrElse { + getLabel(owner, repository, value).map(_ => "Name has already been taken.") + } } } diff --git a/src/main/scala/gitbucket/core/controller/MilestonesController.scala b/src/main/scala/gitbucket/core/controller/MilestonesController.scala index b373b1b..3f2f593 100644 --- a/src/main/scala/gitbucket/core/controller/MilestonesController.scala +++ b/src/main/scala/gitbucket/core/controller/MilestonesController.scala @@ -6,20 +6,23 @@ import gitbucket.core.util.Implicits._ import org.scalatra.forms._ -class MilestonesController extends MilestonesControllerBase - with MilestonesService with RepositoryService with AccountService - with ReferrerAuthenticator with WritableUsersAuthenticator +class MilestonesController + extends MilestonesControllerBase + with MilestonesService + with RepositoryService + with AccountService + with ReferrerAuthenticator + with WritableUsersAuthenticator trait MilestonesControllerBase extends ControllerBase { - self: MilestonesService with RepositoryService - with ReferrerAuthenticator with WritableUsersAuthenticator => + self: MilestonesService with RepositoryService with ReferrerAuthenticator with WritableUsersAuthenticator => case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date]) val milestoneForm = mapping( - "title" -> trim(label("Title", text(required, maxlength(100)))), + "title" -> trim(label("Title", text(required, maxlength(100)))), "description" -> trim(label("Description", optional(text()))), - "dueDate" -> trim(label("Due Date", optional(date()))) + "dueDate" -> trim(label("Due Date", optional(date()))) )(MilestoneForm.apply) get("/:owner/:repository/issues/milestones")(referrersOnly { repository => @@ -27,7 +30,8 @@ params.getOrElse("state", "open"), getMilestonesWithIssueCount(repository.owner, repository.name), repository, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) get("/:owner/:repository/issues/milestones/new")(writableUsersOnly { @@ -40,22 +44,23 @@ }) get("/:owner/:repository/issues/milestones/:milestoneId/edit")(writableUsersOnly { repository => - params("milestoneId").toIntOpt.map{ milestoneId => + params("milestoneId").toIntOpt.map { milestoneId => html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository) } getOrElse NotFound() }) - post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableUsersOnly { (form, repository) => - params("milestoneId").toIntOpt.flatMap{ milestoneId => - getMilestone(repository.owner, repository.name, milestoneId).map { milestone => - updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate)) - redirect(s"/${repository.owner}/${repository.name}/issues/milestones") - } - } getOrElse NotFound() + post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableUsersOnly { + (form, repository) => + params("milestoneId").toIntOpt.flatMap { milestoneId => + getMilestone(repository.owner, repository.name, milestoneId).map { milestone => + updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate)) + redirect(s"/${repository.owner}/${repository.name}/issues/milestones") + } + } getOrElse NotFound() }) get("/:owner/:repository/issues/milestones/:milestoneId/close")(writableUsersOnly { repository => - params("milestoneId").toIntOpt.flatMap{ milestoneId => + params("milestoneId").toIntOpt.flatMap { milestoneId => getMilestone(repository.owner, repository.name, milestoneId).map { milestone => closeMilestone(milestone) redirect(s"/${repository.owner}/${repository.name}/issues/milestones") @@ -64,7 +69,7 @@ }) get("/:owner/:repository/issues/milestones/:milestoneId/open")(writableUsersOnly { repository => - params("milestoneId").toIntOpt.flatMap{ milestoneId => + params("milestoneId").toIntOpt.flatMap { milestoneId => getMilestone(repository.owner, repository.name, milestoneId).map { milestone => openMilestone(milestone) redirect(s"/${repository.owner}/${repository.name}/issues/milestones") @@ -73,7 +78,7 @@ }) get("/:owner/:repository/issues/milestones/:milestoneId/delete")(writableUsersOnly { repository => - params("milestoneId").toIntOpt.flatMap{ milestoneId => + params("milestoneId").toIntOpt.flatMap { milestoneId => getMilestone(repository.owner, repository.name, milestoneId).map { milestone => deleteMilestone(repository.owner, repository.name, milestone.milestoneId) redirect(s"/${repository.owner}/${repository.name}/issues/milestones") diff --git a/src/main/scala/gitbucket/core/controller/PreProcessController.scala b/src/main/scala/gitbucket/core/controller/PreProcessController.scala index 68ce0e9..766bbb2 100644 --- a/src/main/scala/gitbucket/core/controller/PreProcessController.scala +++ b/src/main/scala/gitbucket/core/controller/PreProcessController.scala @@ -28,13 +28,12 @@ * But if it's not allowed, demands authentication except some paths. */ get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) { - if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") && - !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs")) { + if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") && + !context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs")) { Unauthorized() } else { pass() } } - } diff --git a/src/main/scala/gitbucket/core/controller/PrioritiesController.scala b/src/main/scala/gitbucket/core/controller/PrioritiesController.scala index 2141ffe..f34ed63 100644 --- a/src/main/scala/gitbucket/core/controller/PrioritiesController.scala +++ b/src/main/scala/gitbucket/core/controller/PrioritiesController.scala @@ -1,7 +1,14 @@ package gitbucket.core.controller import gitbucket.core.issues.priorities.html -import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService, MilestonesService, PrioritiesService} +import gitbucket.core.service.{ + RepositoryService, + AccountService, + IssuesService, + LabelsService, + MilestonesService, + PrioritiesService +} import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator} import gitbucket.core.util.Implicits._ import gitbucket.core.util.SyntaxSugars._ @@ -9,30 +16,39 @@ import org.scalatra.i18n.Messages import org.scalatra.Ok -class PrioritiesController extends PrioritiesControllerBase - with IssuesService with RepositoryService with AccountService - with LabelsService with PrioritiesService with MilestonesService - with ReferrerAuthenticator with WritableUsersAuthenticator +class PrioritiesController + extends PrioritiesControllerBase + with IssuesService + with RepositoryService + with AccountService + with LabelsService + with PrioritiesService + with MilestonesService + with ReferrerAuthenticator + with WritableUsersAuthenticator trait PrioritiesControllerBase extends ControllerBase { - self: PrioritiesService with IssuesService with RepositoryService - with ReferrerAuthenticator with WritableUsersAuthenticator => + self: PrioritiesService + with IssuesService + with RepositoryService + with ReferrerAuthenticator + with WritableUsersAuthenticator => case class PriorityForm(priorityName: String, description: Option[String], color: String) val priorityForm = mapping( - "priorityName" -> trim(label("Priority name", text(required, priorityName, uniquePriorityName, maxlength(100)))), - "description" -> trim(label("Description", optional(text(maxlength(255))))), + "priorityName" -> trim(label("Priority name", text(required, priorityName, uniquePriorityName, maxlength(100)))), + "description" -> trim(label("Description", optional(text(maxlength(255))))), "priorityColor" -> trim(label("Color", text(required, color))) )(PriorityForm.apply) - get("/:owner/:repository/issues/priorities")(referrersOnly { repository => html.list( getPriorities(repository.owner, repository.name), countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) ajaxGet("/:owner/:repository/issues/priorities/new")(writableUsersOnly { repository => @@ -40,12 +56,14 @@ }) ajaxPost("/:owner/:repository/issues/priorities/new", priorityForm)(writableUsersOnly { (form, repository) => - val priorityId = createPriority(repository.owner, repository.name, form.priorityName, form.description, form.color.substring(1)) + val priorityId = + createPriority(repository.owner, repository.name, form.priorityName, form.description, form.color.substring(1)) html.priority( getPriority(repository.owner, repository.name, priorityId).get, countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) ajaxGet("/:owner/:repository/issues/priorities/:priorityId/edit")(writableUsersOnly { repository => @@ -54,21 +72,34 @@ } getOrElse NotFound() }) - ajaxPost("/:owner/:repository/issues/priorities/:priorityId/edit", priorityForm)(writableUsersOnly { (form, repository) => - updatePriority(repository.owner, repository.name, params("priorityId").toInt, form.priorityName, form.description, form.color.substring(1)) - html.priority( - getPriority(repository.owner, repository.name, params("priorityId").toInt).get, - countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), - repository, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + ajaxPost("/:owner/:repository/issues/priorities/:priorityId/edit", priorityForm)(writableUsersOnly { + (form, repository) => + updatePriority( + repository.owner, + repository.name, + params("priorityId").toInt, + form.priorityName, + form.description, + form.color.substring(1) + ) + html.priority( + getPriority(repository.owner, repository.name, params("priorityId").toInt).get, + countIssueGroupByPriorities(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), + repository, + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) ajaxPost("/:owner/:repository/issues/priorities/reorder")(writableUsersOnly { (repository) => - reorderPriorities(repository.owner, repository.name, params("order") - .split(",") - .map(id => id.toInt) - .zipWithIndex - .toMap) + reorderPriorities( + repository.owner, + repository.name, + params("order") + .split(",") + .map(id => id.toInt) + .zipWithIndex + .toMap + ) Ok() }) @@ -88,26 +119,36 @@ /** * Constraint for the identifier such as user name, repository name or page name. */ - private def priorityName: Constraint = new Constraint(){ + private def priorityName: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(value.contains(',')){ + if (value.contains(',')) { Some(s"${name} contains invalid character.") - } else if(value.startsWith("_") || value.startsWith("-")){ + } else if (value.startsWith("_") || value.startsWith("-")) { Some(s"${name} starts with invalid character.") } else { None } } - private def uniquePriorityName: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = { - val owner = params.value("owner") + private def uniquePriorityName: Constraint = new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = { + val owner = params.value("owner") val repository = params.value("repository") - params.optionValue("priorityId").map { priorityId => - getPriority(owner, repository, value).filter(_.priorityId != priorityId.toInt).map(_ => "Name has already been taken.") - }.getOrElse { - getPriority(owner, repository, value).map(_ => "Name has already been taken.") - } + params + .optionValue("priorityId") + .map { priorityId => + getPriority(owner, repository, value) + .filter(_.priorityId != priorityId.toInt) + .map(_ => "Name has already been taken.") + } + .getOrElse { + getPriority(owner, repository, value).map(_ => "Name has already been taken.") + } } } } diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index a4bef94..8fea200 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -21,38 +21,61 @@ import scala.collection.JavaConverters._ - -class PullRequestsController extends PullRequestsControllerBase - with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService - with CommitsService with ActivityService with WebHookPullRequestService - with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator - with CommitStatusService with MergeService with ProtectedBranchService with PrioritiesService - +class PullRequestsController + extends PullRequestsControllerBase + with RepositoryService + with AccountService + with IssuesService + with PullRequestService + with MilestonesService + with LabelsService + with CommitsService + with ActivityService + with WebHookPullRequestService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator + with CommitStatusService + with MergeService + with ProtectedBranchService + with PrioritiesService trait PullRequestsControllerBase extends ControllerBase { - self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService - with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService - with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator - with CommitStatusService with MergeService with ProtectedBranchService with PrioritiesService => + self: RepositoryService + with AccountService + with IssuesService + with MilestonesService + with LabelsService + with CommitsService + with ActivityService + with PullRequestService + with WebHookPullRequestService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator + with CommitStatusService + with MergeService + with ProtectedBranchService + with PrioritiesService => val pullRequestForm = mapping( - "title" -> trim(label("Title" , text(required, maxlength(100)))), - "content" -> trim(label("Content", optional(text()))), - "targetUserName" -> trim(text(required, maxlength(100))), - "targetBranch" -> trim(text(required, maxlength(100))), - "requestUserName" -> trim(text(required, maxlength(100))), + "title" -> trim(label("Title", text(required, maxlength(100)))), + "content" -> trim(label("Content", optional(text()))), + "targetUserName" -> trim(text(required, maxlength(100))), + "targetBranch" -> trim(text(required, maxlength(100))), + "requestUserName" -> trim(text(required, maxlength(100))), "requestRepositoryName" -> trim(text(required, maxlength(100))), - "requestBranch" -> trim(text(required, maxlength(100))), - "commitIdFrom" -> trim(text(required, maxlength(40))), - "commitIdTo" -> trim(text(required, maxlength(40))), - "assignedUserName" -> trim(optional(text())), - "milestoneId" -> trim(optional(number())), - "priorityId" -> trim(optional(number())), - "labelNames" -> trim(optional(text())) + "requestBranch" -> trim(text(required, maxlength(100))), + "commitIdFrom" -> trim(text(required, maxlength(40))), + "commitIdTo" -> trim(text(required, maxlength(40))), + "assignedUserName" -> trim(optional(text())), + "milestoneId" -> trim(optional(number())), + "priorityId" -> trim(optional(number())), + "labelNames" -> trim(optional(text())) )(PullRequestForm.apply) val mergeForm = mapping( - "message" -> trim(label("Message", text(required))), + "message" -> trim(label("Message", text(required))), "strategy" -> trim(label("Strategy", text(required))) )(MergeForm.apply) @@ -76,7 +99,7 @@ get("/:owner/:repository/pulls")(referrersOnly { repository => val q = request.getParameter("q") - if(Option(q).exists(_.contains("is:issue"))){ + if (Option(q).exists(_.contains("is:issue"))) { redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q)) } else { searchPullRequests(None, repository) @@ -84,66 +107,90 @@ }) get("/:owner/:repository/pull/:id")(referrersOnly { repository => - params("id").toIntOpt.flatMap{ issueId => - val owner = repository.owner - val name = repository.name - getPullRequest(owner, name, issueId) map { case(issue, pullreq) => - using(Git.open(getRepositoryDir(owner, name))){ git => - val (commits, diffs) = - getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo) - html.pullreq( - issue, pullreq, - (commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId)) - .sortWith((a, b) => a.registeredDate before b.registeredDate), - getIssueLabels(owner, name, issueId), - getAssignableUserNames(owner, name), - getMilestonesWithIssueCount(owner, name), - getPriorities(owner, name), - getLabels(owner, name), - commits, - diffs, - isEditable(repository), - isManageable(repository), - hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount), - repository, - getRepository(pullreq.requestUserName, pullreq.requestRepositoryName), - flash.toMap.map(f => f._1 -> f._2.toString)) + params("id").toIntOpt.flatMap { + issueId => + val owner = repository.owner + val name = repository.name + getPullRequest(owner, name, issueId) map { + case (issue, pullreq) => + using(Git.open(getRepositoryDir(owner, name))) { + git => + val (commits, diffs) = + getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo) + html.pullreq( + issue, + pullreq, + (commits.flatten + .map(commit => getCommitComments(owner, name, commit.id, true)) + .flatten + .toList ::: getComments(owner, name, issueId)) + .sortWith((a, b) => a.registeredDate before b.registeredDate), + getIssueLabels(owner, name, issueId), + getAssignableUserNames(owner, name), + getMilestonesWithIssueCount(owner, name), + getPriorities(owner, name), + getLabels(owner, name), + commits, + diffs, + isEditable(repository), + isManageable(repository), + hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount), + repository, + getRepository(pullreq.requestUserName, pullreq.requestRepositoryName), + flash.toMap.map(f => f._1 -> f._2.toString) + ) + } } - } } getOrElse NotFound() }) ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository => - params("id").toIntOpt.flatMap{ issueId => - val owner = repository.owner - val name = repository.name - getPullRequest(owner, name, issueId) map { case(issue, pullreq) => - val conflictMessage = LockUtil.lock(s"${owner}/${name}"){ - checkConflict(owner, name, pullreq.branch, issueId) + params("id").toIntOpt.flatMap { + issueId => + val owner = repository.owner + val name = repository.name + getPullRequest(owner, name, issueId) map { + case (issue, pullreq) => + val conflictMessage = LockUtil.lock(s"${owner}/${name}") { + checkConflict(owner, name, pullreq.branch, issueId) + } + val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount) + val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch) + val mergeStatus = PullRequestService.MergeStatus( + conflictMessage = conflictMessage, + commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo), + branchProtection = branchProtection, + branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom), + needStatusCheck = context.loginAccount + .map { u => + branchProtection.needStatusCheck(u.userName) + } + .getOrElse(true), + hasUpdatePermission = hasDeveloperRole( + pullreq.requestUserName, + pullreq.requestRepositoryName, + context.loginAccount + ) && + context.loginAccount + .map { u => + !getProtectedBranchInfo( + pullreq.requestUserName, + pullreq.requestRepositoryName, + pullreq.requestBranch + ).needStatusCheck(u.userName) + } + .getOrElse(false), + hasMergePermission = hasMergePermission, + commitIdTo = pullreq.commitIdTo + ) + html.mergeguide( + mergeStatus, + issue, + pullreq, + repository, + getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get + ) } - val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount) - val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch) - val mergeStatus = PullRequestService.MergeStatus( - conflictMessage = conflictMessage, - commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo), - branchProtection = branchProtection, - branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom), - needStatusCheck = context.loginAccount.map{ u => - branchProtection.needStatusCheck(u.userName) - }.getOrElse(true), - hasUpdatePermission = hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) && - context.loginAccount.map{ u => - !getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch).needStatusCheck(u.userName) - }.getOrElse(false), - hasMergePermission = hasMergePermission, - commitIdTo = pullreq.commitIdTo) - html.mergeguide( - mergeStatus, - issue, - pullreq, - repository, - getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get) - } } getOrElse NotFound() }) @@ -153,21 +200,28 @@ loginAccount <- context.loginAccount (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId) owner = pullreq.requestUserName - name = pullreq.requestRepositoryName + name = pullreq.requestRepositoryName if hasDeveloperRole(owner, name, context.loginAccount) } yield { val repository = getRepository(owner, name).get val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch) - if(branchProtection.enabled){ + if (branchProtection.enabled) { flash += "error" -> s"branch ${pullreq.requestBranch} is protected." } else { - if(repository.repository.defaultBranch != pullreq.requestBranch){ + if (repository.repository.defaultBranch != pullreq.requestBranch) { val userName = context.loginAccount.get.userName - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call() recordDeleteBranchActivity(repository.owner, repository.name, userName, pullreq.requestBranch) } - createComment(baseRepository.owner, baseRepository.name, userName, issueId, pullreq.requestBranch, "delete_branch") + createComment( + baseRepository.owner, + baseRepository.name, + userName, + issueId, + pullreq.requestBranch, + "delete_branch" + ) } else { flash += "error" -> s"""Can't delete the default branch "${pullreq.requestBranch}".""" } @@ -182,59 +236,92 @@ loginAccount <- context.loginAccount (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId) owner = pullreq.requestUserName - name = pullreq.requestRepositoryName + name = pullreq.requestRepositoryName if hasDeveloperRole(owner, name, context.loginAccount) } yield { val repository = getRepository(owner, name).get val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch) - if(branchProtection.needStatusCheck(loginAccount.userName)){ + if (branchProtection.needStatusCheck(loginAccount.userName)) { flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check." } else { - LockUtil.lock(s"${owner}/${name}"){ - val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){ - pullreq.branch - } else { - s"${pullreq.userName}:${pullreq.branch}" - } - val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet - pullRemote(owner, name, pullreq.requestBranch, pullreq.userName, pullreq.repositoryName, pullreq.branch, loginAccount, - s"Merge branch '${alias}' into ${pullreq.requestBranch}") match { + LockUtil.lock(s"${owner}/${name}") { + val alias = + if (pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName) { + pullreq.branch + } else { + s"${pullreq.userName}:${pullreq.branch}" + } + val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => + JGitUtil.getAllCommitIds(git) + }.toSet + pullRemote( + owner, + name, + pullreq.requestBranch, + pullreq.userName, + pullreq.repositoryName, + pullreq.branch, + loginAccount, + s"Merge branch '${alias}' into ${pullreq.requestBranch}" + ) match { case None => // conflict flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}." case Some(oldId) => // update pull request updatePullRequests(owner, name, pullreq.requestBranch) - using(Git.open(Directory.getRepositoryDir(owner, name))) { git => - // after update branch - val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}") - val commits = git.log.addRange(oldId, newCommitId).call.iterator.asScala.map(c => new JGitUtil.CommitInfo(c)).toList + using(Git.open(Directory.getRepositoryDir(owner, name))) { + git => + // after update branch + val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}") + val commits = git.log + .addRange(oldId, newCommitId) + .call + .iterator + .asScala + .map(c => new JGitUtil.CommitInfo(c)) + .toList - commits.foreach { commit => - if(!existIds.contains(commit.id)){ - createIssueComment(owner, name, commit) + commits.foreach { commit => + if (!existIds.contains(commit.id)) { + createIssueComment(owner, name, commit) + } } - } - // record activity - recordPushActivity(owner, name, loginAccount.userName, pullreq.branch, commits) + // record activity + recordPushActivity(owner, name, loginAccount.userName, pullreq.branch, commits) - // close issue by commit message - if(pullreq.requestBranch == repository.repository.defaultBranch){ - commits.map { commit => - closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) + // close issue by commit message + if (pullreq.requestBranch == repository.repository.defaultBranch) { + commits.map { commit => + closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) + } } - } - // call web hook - callPullRequestWebHookByRequestBranch("synchronize", repository, pullreq.requestBranch, baseUrl, loginAccount) - callWebHookOf(owner, name, WebHook.Push) { - for { - ownerAccount <- getAccountByUserName(owner) - } yield { - WebHookService.WebHookPushPayload(git, loginAccount, pullreq.requestBranch, repository, commits, ownerAccount, oldId = oldId, newId = newCommitId) + // call web hook + callPullRequestWebHookByRequestBranch( + "synchronize", + repository, + pullreq.requestBranch, + baseUrl, + loginAccount + ) + callWebHookOf(owner, name, WebHook.Push) { + for { + ownerAccount <- getAccountByUserName(owner) + } yield { + WebHookService.WebHookPushPayload( + git, + loginAccount, + pullreq.requestBranch, + repository, + commits, + ownerAccount, + oldId = oldId, + newId = newCommitId + ) + } } - } } flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}" } @@ -246,95 +333,130 @@ }) post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) => - params("id").toIntOpt.flatMap { issueId => - val owner = repository.owner - val name = repository.name - if(repository.repository.options.mergeOptions.split(",").contains(form.strategy)){ - LockUtil.lock(s"${owner}/${name}"){ - getPullRequest(owner, name, issueId).map { case (issue, pullreq) => - using(Git.open(getRepositoryDir(owner, name))) { git => - // mark issue as merged and close. - val loginAccount = context.loginAccount.get - val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge") - createComment(owner, name, loginAccount.userName, issueId, "Close", "close") - updateClosed(owner, name, issueId, true) + params("id").toIntOpt.flatMap { + issueId => + val owner = repository.owner + val name = repository.name + if (repository.repository.options.mergeOptions.split(",").contains(form.strategy)) { + LockUtil.lock(s"${owner}/${name}") { + getPullRequest(owner, name, issueId).map { + case (issue, pullreq) => + using(Git.open(getRepositoryDir(owner, name))) { + git => + // mark issue as merged and close. + val loginAccount = context.loginAccount.get + val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge") + createComment(owner, name, loginAccount.userName, issueId, "Close", "close") + updateClosed(owner, name, issueId, true) - // record activity - recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message) + // record activity + recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message) - val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom, - pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo) + val (commits, _) = getRequestCompareInfo( + owner, + name, + pullreq.commitIdFrom, + pullreq.requestUserName, + pullreq.requestRepositoryName, + pullreq.commitIdTo + ) - val revCommits = using(new RevWalk( git.getRepository )){ revWalk => - commits.flatten.map { commit => - revWalk.parseCommit(git.getRepository.resolve(commit.id)) + val revCommits = using(new RevWalk(git.getRepository)) { revWalk => + commits.flatten.map { commit => + revWalk.parseCommit(git.getRepository.resolve(commit.id)) + } + }.reverse + + // merge git repository + form.strategy match { + case "merge-commit" => + mergePullRequest( + git, + pullreq.branch, + issueId, + s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + form.message, + new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) + ) + case "rebase" => + rebasePullRequest( + git, + pullreq.branch, + issueId, + revCommits, + new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) + ) + case "squash" => + squashPullRequest( + git, + pullreq.branch, + issueId, + s"${issue.title} (#${issueId})\n\n" + form.message, + new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) + ) + } + + // close issue by content of pull request + val defaultBranch = getRepository(owner, name).get.repository.defaultBranch + if (pullreq.branch == defaultBranch) { + commits.flatten.foreach { commit => + closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) + } + closeIssuesFromMessage( + issue.title + " " + issue.content.getOrElse(""), + loginAccount.userName, + owner, + name + ) + closeIssuesFromMessage(form.message, loginAccount.userName, owner, name) + } + + updatePullRequests(owner, name, pullreq.branch) + + // call web hook + callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get) + + // call hooks + PluginRegistry().getPullRequestHooks.foreach { h => + h.addedComment(commentId, form.message, issue, repository) + h.merged(issue, repository) + } + + redirect(s"/${owner}/${name}/pull/${issueId}") } - }.reverse - - // merge git repository - form.strategy match { - case "merge-commit" => - mergePullRequest(git, pullreq.branch, issueId, - s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + form.message, - new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) - case "rebase" => - rebasePullRequest(git, pullreq.branch, issueId, revCommits, - new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) - case "squash" => - squashPullRequest(git, pullreq.branch, issueId, - s"${issue.title} (#${issueId})\n\n" + form.message, - new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) - } - - // close issue by content of pull request - val defaultBranch = getRepository(owner, name).get.repository.defaultBranch - if(pullreq.branch == defaultBranch){ - commits.flatten.foreach { commit => - closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) - } - closeIssuesFromMessage(issue.title + " " + issue.content.getOrElse(""), loginAccount.userName, owner, name) - closeIssuesFromMessage(form.message, loginAccount.userName, owner, name) - } - - updatePullRequests(owner, name, pullreq.branch) - - // call web hook - callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get) - - // call hooks - PluginRegistry().getPullRequestHooks.foreach{ h => - h.addedComment(commentId, form.message, issue, repository) - h.merged(issue, repository) - } - - redirect(s"/${owner}/${name}/pull/${issueId}") } } - } - } else Some(BadRequest()) + } else Some(BadRequest()) } getOrElse NotFound() }) get("/:owner/:repository/compare")(referrersOnly { forkedRepository => - val headBranch:Option[String] = params.get("head") + val headBranch: Option[String] = params.get("head") (forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { case (Some(originUserName), Some(originRepositoryName)) => { - getRepository(originUserName, originRepositoryName).map { originRepository => - using( - Git.open(getRepositoryDir(originUserName, originRepositoryName)), - Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) - ){ (oldGit, newGit) => - val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2) - val oldBranch = originRepository.branchList.find( _ == newBranch).getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2) + getRepository(originUserName, originRepositoryName).map { + originRepository => + using( + Git.open(getRepositoryDir(originUserName, originRepositoryName)), + Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) + ) { (oldGit, newGit) => + val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2) + val oldBranch = originRepository.branchList + .find(_ == newBranch) + .getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2) - redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}") - } + redirect( + s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}" + ) + } } getOrElse NotFound() } case _ => { - using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git => - JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) => - redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}") + using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git => + JGitUtil.getDefaultBranch(git, forkedRepository).map { + case (_, defaultBranch) => + redirect( + s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}" + ) } getOrElse { redirect(s"/${forkedRepository.owner}/${forkedRepository.name}") } @@ -348,91 +470,118 @@ val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner) val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner) - (for( - originRepositoryName <- if(originOwner == forkedOwner) { - // Self repository - Some(forkedRepository.name) - } else if(forkedRepository.repository.originUserName.isEmpty){ - // when ForkedRepository is the original repository - getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_.userName == originOwner).map(_.repositoryName) - } else if(Some(originOwner) == forkedRepository.repository.originUserName){ - // Original repository - forkedRepository.repository.originRepositoryName - } else { - // Sibling repository - getUserRepositories(originOwner).find { x => - x.repository.originUserName == forkedRepository.repository.originUserName && - x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName - }.map(_.repository.repositoryName) - }; - originRepository <- getRepository(originOwner, originRepositoryName) - ) yield { + (for (originRepositoryName <- if (originOwner == forkedOwner) { + // Self repository + Some(forkedRepository.name) + } else if (forkedRepository.repository.originUserName.isEmpty) { + // when ForkedRepository is the original repository + getForkedRepositories(forkedRepository.owner, forkedRepository.name) + .find(_.userName == originOwner) + .map(_.repositoryName) + } else if (Some(originOwner) == forkedRepository.repository.originUserName) { + // Original repository + forkedRepository.repository.originRepositoryName + } else { + // Sibling repository + getUserRepositories(originOwner) + .find { x => + x.repository.originUserName == forkedRepository.repository.originUserName && + x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName + } + .map(_.repository.repositoryName) + }; + originRepository <- getRepository(originOwner, originRepositoryName)) yield { using( Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) - ){ case (oldGit, newGit) => - val (oldId, newId) = - if(originRepository.branchList.contains(originId)){ - val forkedId2 = forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) + ) { + case (oldGit, newGit) => + val (oldId, newId) = + if (originRepository.branchList.contains(originId)) { + val forkedId2 = + forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) - val originId2 = JGitUtil.getForkedCommitId(oldGit, newGit, - originRepository.owner, originRepository.name, originId, - forkedRepository.owner, forkedRepository.name, forkedId2) + val originId2 = JGitUtil.getForkedCommitId( + oldGit, + newGit, + originRepository.owner, + originRepository.name, + originId, + forkedRepository.owner, + forkedRepository.name, + forkedId2 + ) - (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) + (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) - } else { - val originId2 = originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId) - val forkedId2 = forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) - - (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) - } - - (oldId, newId) match { - case (Some(oldId), Some(newId)) => { - val (commits, diffs) = getRequestCompareInfo( - originRepository.owner, originRepository.name, oldId.getName, - forkedRepository.owner, forkedRepository.name, newId.getName) - - val title = if(commits.flatten.length == 1){ - commits.flatten.head.shortMessage } else { - val text = forkedId.replaceAll("[\\-_]", " ") - text.substring(0, 1).toUpperCase + text.substring(1) + val originId2 = + originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId) + val forkedId2 = + forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId) + + (Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2))) } - html.compare( - title, - commits, - diffs, - ((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { - case (Some(userName), Some(repositoryName)) => getRepository(userName, repositoryName) match { - case Some(x) => x.repository :: getForkedRepositories(userName, repositoryName) - case None => getForkedRepositories(userName, repositoryName) - } - case _ => forkedRepository.repository :: getForkedRepositories(forkedRepository.owner, forkedRepository.name) - }).map { repository => (repository.userName, repository.repositoryName) }, - commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList, - originId, - forkedId, - oldId.getName, - newId.getName, - getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"), - forkedRepository, - originRepository, - forkedRepository, - hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount), - getAssignableUserNames(originRepository.owner, originRepository.name), - getMilestones(originRepository.owner, originRepository.name), - getPriorities(originRepository.owner, originRepository.name), - getLabels(originRepository.owner, originRepository.name) - ) + (oldId, newId) match { + case (Some(oldId), Some(newId)) => { + val (commits, diffs) = getRequestCompareInfo( + originRepository.owner, + originRepository.name, + oldId.getName, + forkedRepository.owner, + forkedRepository.name, + newId.getName + ) + + val title = if (commits.flatten.length == 1) { + commits.flatten.head.shortMessage + } else { + val text = forkedId.replaceAll("[\\-_]", " ") + text.substring(0, 1).toUpperCase + text.substring(1) + } + + html.compare( + title, + commits, + diffs, + ((forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match { + case (Some(userName), Some(repositoryName)) => + getRepository(userName, repositoryName) match { + case Some(x) => x.repository :: getForkedRepositories(userName, repositoryName) + case None => getForkedRepositories(userName, repositoryName) + } + case _ => + forkedRepository.repository :: getForkedRepositories(forkedRepository.owner, forkedRepository.name) + }).map { repository => + (repository.userName, repository.repositoryName) + }, + commits.flatten + .map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)) + .flatten + .toList, + originId, + forkedId, + oldId.getName, + newId.getName, + getContentTemplate(originRepository, "PULL_REQUEST_TEMPLATE"), + forkedRepository, + originRepository, + forkedRepository, + hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount), + getAssignableUserNames(originRepository.owner, originRepository.name), + getMilestones(originRepository.owner, originRepository.name), + getPriorities(originRepository.owner, originRepository.name), + getLabels(originRepository.owner, originRepository.name) + ) + } + case (oldId, newId) => + redirect( + s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" + + s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." + + s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}" + ) } - case (oldId, newId) => - redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" + - s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." + - s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}") - } } }) getOrElse NotFound() }) @@ -442,102 +591,119 @@ val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner) val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner) - (for( - originRepositoryName <- if(originOwner == forkedOwner){ - Some(forkedRepository.name) - } else { - forkedRepository.repository.originRepositoryName.orElse { - getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_.userName == originOwner).map(_.repositoryName) - } - }; - originRepository <- getRepository(originOwner, originRepositoryName) - ) yield { + (for (originRepositoryName <- if (originOwner == forkedOwner) { + Some(forkedRepository.name) + } else { + forkedRepository.repository.originRepositoryName.orElse { + getForkedRepositories(forkedRepository.owner, forkedRepository.name) + .find(_.userName == originOwner) + .map(_.repositoryName) + } + }; + originRepository <- getRepository(originOwner, originRepositoryName)) yield { using( Git.open(getRepositoryDir(originRepository.owner, originRepository.name)), Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name)) - ){ case (oldGit, newGit) => - val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2 - val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2 - val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}"){ - checkConflict(originRepository.owner, originRepository.name, originBranch, - forkedRepository.owner, forkedRepository.name, forkedBranch) - } - html.mergecheck(conflict.isDefined) + ) { + case (oldGit, newGit) => + val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2 + val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2 + val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}") { + checkConflict( + originRepository.owner, + originRepository.name, + originBranch, + forkedRepository.owner, + forkedRepository.name, + forkedBranch + ) + } + html.mergecheck(conflict.isDefined) } }) getOrElse NotFound() }) post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) => - defining(repository.owner, repository.name){ case (owner, name) => - val manageable = isManageable(repository) - val loginUserName = context.loginAccount.get.userName + defining(repository.owner, repository.name) { + case (owner, name) => + val manageable = isManageable(repository) + val loginUserName = context.loginAccount.get.userName - val issueId = insertIssue( - owner = repository.owner, - repository = repository.name, - loginUser = loginUserName, - title = form.title, - content = form.content, - assignedUserName = if (manageable) form.assignedUserName else None, - milestoneId = if (manageable) form.milestoneId else None, - priorityId = if (manageable) form.priorityId else None, - isPullRequest = true) + val issueId = insertIssue( + owner = repository.owner, + repository = repository.name, + loginUser = loginUserName, + title = form.title, + content = form.content, + assignedUserName = if (manageable) form.assignedUserName else None, + milestoneId = if (manageable) form.milestoneId else None, + priorityId = if (manageable) form.priorityId else None, + isPullRequest = true + ) - createPullRequest( - originUserName = repository.owner, - originRepositoryName = repository.name, - issueId = issueId, - originBranch = form.targetBranch, - requestUserName = form.requestUserName, - requestRepositoryName = form.requestRepositoryName, - requestBranch = form.requestBranch, - commitIdFrom = form.commitIdFrom, - commitIdTo = form.commitIdTo) + createPullRequest( + originUserName = repository.owner, + originRepositoryName = repository.name, + issueId = issueId, + originBranch = form.targetBranch, + requestUserName = form.requestUserName, + requestRepositoryName = form.requestRepositoryName, + requestBranch = form.requestBranch, + commitIdFrom = form.commitIdFrom, + commitIdTo = form.commitIdTo + ) - // insert labels - if (manageable) { - form.labelNames.map { value => - val labels = getLabels(owner, name) - value.split(",").foreach { labelName => - labels.find(_.labelName == labelName).map { label => - registerIssueLabel(repository.owner, repository.name, issueId, label.labelId) + // insert labels + if (manageable) { + form.labelNames.map { value => + val labels = getLabels(owner, name) + value.split(",").foreach { labelName => + labels.find(_.labelName == labelName).map { label => + registerIssueLabel(repository.owner, repository.name, issueId, label.labelId) + } } } } - } - // fetch requested branch - fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId) + // fetch requested branch + fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId) - // record activity - recordPullRequestActivity(owner, name, loginUserName, issueId, form.title) + // record activity + recordPullRequestActivity(owner, name, loginUserName, issueId, form.title) - // call web hook - callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get) + // call web hook + callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get) - getIssue(owner, name, issueId.toString) foreach { issue => - // extract references and create refer comment - createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) + getIssue(owner, name, issueId.toString) foreach { issue => + // extract references and create refer comment + createReferComment( + owner, + name, + issue, + form.title + " " + form.content.getOrElse(""), + context.loginAccount.get + ) - // call hooks - PluginRegistry().getPullRequestHooks.foreach(_.created(issue, repository)) - } + // call hooks + PluginRegistry().getPullRequestHooks.foreach(_.created(issue, repository)) + } - redirect(s"/${owner}/${name}/pull/${issueId}") + redirect(s"/${owner}/${name}/pull/${issueId}") } }) ajaxGet("/:owner/:repository/pulls/proposals")(readableUsersOnly { repository => - val branches = JGitUtil.getBranches( - owner = repository.owner, - name = repository.name, - defaultBranch = repository.repository.defaultBranch, - origin = repository.repository.originUserName.isEmpty - ) - .filter(x => x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0) - .sortBy(br => (br.mergeInfo.isEmpty, br.commitTime)) - .map(_.name) - .reverse + val branches = JGitUtil + .getBranches( + owner = repository.owner, + name = repository.name, + defaultBranch = repository.repository.defaultBranch, + origin = repository.repository.originUserName.isEmpty + ) + .filter(x => x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0) + .sortBy(br => (br.mergeInfo.isEmpty, br.commitTime)) + .map(_.name) + .reverse val targetRepository = (for { parentUserName <- repository.repository.parentUserName @@ -563,7 +729,7 @@ * - "branch" to ("defaultOwner", "branch") */ private def parseCompareIdentifier(value: String, defaultOwner: String): (String, String) = - if(value.contains(':')){ + if (value.contains(':')) { val array = value.split(":") (array(0), array(1)) } else { @@ -571,26 +737,28 @@ } private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = - defining(repository.owner, repository.name){ case (owner, repoName) => - val page = IssueSearchCondition.page(request) + defining(repository.owner, repository.name) { + case (owner, repoName) => + val page = IssueSearchCondition.page(request) - // retrieve search condition - val condition = IssueSearchCondition(request) + // retrieve search condition + val condition = IssueSearchCondition(request) - gitbucket.core.issues.html.list( - "pulls", - searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), - page, - getAssignableUserNames(owner, repoName), - getMilestones(owner, repoName), - getPriorities(owner, repoName), - getLabels(owner, repoName), - countIssue(condition.copy(state = "open" ), true, owner -> repoName), - countIssue(condition.copy(state = "closed"), true, owner -> repoName), - condition, - repository, - isEditable(repository), - isManageable(repository)) + gitbucket.core.issues.html.list( + "pulls", + searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), + page, + getAssignableUserNames(owner, repoName), + getMilestones(owner, repoName), + getPriorities(owner, repoName), + getLabels(owner, repoName), + countIssue(condition.copy(state = "open"), true, owner -> repoName), + countIssue(condition.copy(state = "closed"), true, owner -> repoName), + condition, + repository, + isEditable(repository), + isManageable(repository) + ) } /** diff --git a/src/main/scala/gitbucket/core/controller/ReleasesController.scala b/src/main/scala/gitbucket/core/controller/ReleasesController.scala index 7bb9753..8ffd023 100644 --- a/src/main/scala/gitbucket/core/controller/ReleasesController.scala +++ b/src/main/scala/gitbucket/core/controller/ReleasesController.scala @@ -11,14 +11,15 @@ import org.apache.commons.io.FileUtils import scala.collection.JavaConverters._ -class ReleaseController extends ReleaseControllerBase - with RepositoryService - with AccountService - with ReleaseService - with ActivityService - with ReadableUsersAuthenticator - with ReferrerAuthenticator - with WritableUsersAuthenticator +class ReleaseController + extends ReleaseControllerBase + with RepositoryService + with AccountService + with ReleaseService + with ActivityService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator trait ReleaseControllerBase extends ControllerBase { self: RepositoryService @@ -35,36 +36,45 @@ ) val releaseForm = mapping( - "name" -> trim(text(required)), + "name" -> trim(text(required)), "content" -> trim(optional(text())) )(ReleaseForm.apply) - get("/:owner/:repository/releases")(referrersOnly {repository => + get("/:owner/:repository/releases")(referrersOnly { repository => val releases = getReleases(repository.owner, repository.name) val assets = getReleaseAssetsMap(repository.owner, repository.name) html.list( repository, repository.tags.reverse.map { tag => - (tag, releases.find(_.tag == tag.name).map { release => (release, assets(release)) }) + (tag, releases.find(_.tag == tag.name).map { release => + (release, assets(release)) + }) }, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) }) get("/:owner/:repository/releases/:tag")(referrersOnly { repository => val tagName = params("tag") - getRelease(repository.owner, repository.name, tagName).map { release => - html.release(release, getReleaseAssets(repository.owner, repository.name, tagName), - hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository) - }.getOrElse(NotFound()) + getRelease(repository.owner, repository.name, tagName) + .map { release => + html.release( + release, + getReleaseAssets(repository.owner, repository.name, tagName), + hasDeveloperRole(repository.owner, repository.name, context.loginAccount), + repository + ) + } + .getOrElse(NotFound()) }) - get("/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly {repository => + get("/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository => val tagName = params("tag") val fileId = params("fileId") (for { - _ <- repository.tags.find(_.name == tagName) - _ <- getRelease(repository.owner, repository.name, tagName) + _ <- repository.tags.find(_.name == tagName) + _ <- getRelease(repository.owner, repository.name, tagName) asset <- getReleaseAsset(repository.owner, repository.name, tagName, fileId) } yield { response.setHeader("Content-Disposition", s"attachment; filename=${asset.label}") @@ -75,11 +85,14 @@ }).getOrElse(NotFound()) }) - get("/:owner/:repository/releases/:tag/create")(writableUsersOnly {repository => + get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository => val tagName = params("tag") - repository.tags.find(_.name == tagName).map { tag => - html.form(repository, tag, None) - }.getOrElse(NotFound()) + repository.tags + .find(_.name == tagName) + .map { tag => + html.form(repository, tag, None) + } + .getOrElse(NotFound()) }) post("/:owner/:repository/releases/:tag/create", releaseForm)(writableUsersOnly { (form, repository) => @@ -90,13 +103,16 @@ createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount) // Insert into RELEASE_ASSET - val files = params.collect { case (name, value) if name.startsWith("file:") => - val Array(_, fileId) = name.split(":") - (fileId, value) + val files = params.collect { + case (name, value) if name.startsWith("file:") => + val Array(_, fileId) = name.split(":") + (fileId, value) } - files.foreach { case (fileId, fileName) => - val size = new java.io.File(getReleaseFilesDir(repository.owner, repository.name), tagName + "/" + fileId).length - createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount) + files.foreach { + case (fileId, fileName) => + val size = + new java.io.File(getReleaseFilesDir(repository.owner, repository.name), tagName + "/" + fileId).length + createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount) } recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name) @@ -104,47 +120,56 @@ redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}") }) - get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly {repository => + get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository => val tagName = params("tag") (for { release <- getRelease(repository.owner, repository.name, tagName) - tag <- repository.tags.find(_.name == tagName) + tag <- repository.tags.find(_.name == tagName) } yield { html.form(repository, tag, Some(release, getReleaseAssets(repository.owner, repository.name, tagName))) }).getOrElse(NotFound()) }) - post("/:owner/:repository/releases/:tag/edit", releaseForm)(writableUsersOnly { (form, repository) => - val tagName = params("tag") - val loginAccount = context.loginAccount.get + post("/:owner/:repository/releases/:tag/edit", releaseForm)(writableUsersOnly { + (form, repository) => + val tagName = params("tag") + val loginAccount = context.loginAccount.get - getRelease(repository.owner, repository.name, tagName).map { release => - // Update RELEASE - updateRelease(repository.owner, repository.name, tagName, form.name, form.content) + getRelease(repository.owner, repository.name, tagName) + .map { release => + // Update RELEASE + updateRelease(repository.owner, repository.name, tagName, form.name, form.content) - // Delete and Insert RELEASE_ASSET - val assets = getReleaseAssets(repository.owner, repository.name, tagName) - deleteReleaseAssets(repository.owner, repository.name, tagName) + // Delete and Insert RELEASE_ASSET + val assets = getReleaseAssets(repository.owner, repository.name, tagName) + deleteReleaseAssets(repository.owner, repository.name, tagName) - val files = params.collect { case (name, value) if name.startsWith("file:") => - val Array(_, fileId) = name.split(":") - (fileId, value) - } - files.foreach { case (fileId, fileName) => - val size = new java.io.File(getReleaseFilesDir(repository.owner, repository.name), tagName + "/" + fileId).length - createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount) - } + val files = params.collect { + case (name, value) if name.startsWith("file:") => + val Array(_, fileId) = name.split(":") + (fileId, value) + } + files.foreach { + case (fileId, fileName) => + val size = + new java.io.File(getReleaseFilesDir(repository.owner, repository.name), tagName + "/" + fileId).length + createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount) + } - assets.foreach { asset => - if(!files.exists { case (fileId, _) => fileId == asset.fileName }){ - val file = new java.io.File(getReleaseFilesDir(repository.owner, repository.name), release.tag + "/" + asset.fileName) - FileUtils.forceDelete(file) + assets.foreach { asset => + if (!files.exists { case (fileId, _) => fileId == asset.fileName }) { + val file = new java.io.File( + getReleaseFilesDir(repository.owner, repository.name), + release.tag + "/" + asset.fileName + ) + FileUtils.forceDelete(file) + } + } + + redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}") } - } - - redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}") - }.getOrElse(NotFound()) + .getOrElse(NotFound()) }) post("/:owner/:repository/releases/:tag/delete")(writableUsersOnly { repository => diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index b824898..adfdfc4 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -21,14 +21,26 @@ import gitbucket.core.model.WebHookContentType import gitbucket.core.plugin.PluginRegistry - -class RepositorySettingsController extends RepositorySettingsControllerBase - with RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService with DeployKeyService - with OwnerAuthenticator with UsersAuthenticator +class RepositorySettingsController + extends RepositorySettingsControllerBase + with RepositoryService + with AccountService + with WebHookService + with ProtectedBranchService + with CommitStatusService + with DeployKeyService + with OwnerAuthenticator + with UsersAuthenticator trait RepositorySettingsControllerBase extends ControllerBase { - self: RepositoryService with AccountService with WebHookService with ProtectedBranchService with CommitStatusService with DeployKeyService - with OwnerAuthenticator with UsersAuthenticator => + self: RepositoryService + with AccountService + with WebHookService + with ProtectedBranchService + with CommitStatusService + with DeployKeyService + with OwnerAuthenticator + with UsersAuthenticator => // for repository options case class OptionsForm( @@ -45,18 +57,20 @@ ) val optionsForm = mapping( - "repositoryName" -> trim(label("Repository Name" , text(required, maxlength(100), repository, renameRepositoryName))), - "description" -> trim(label("Description" , optional(text()))), - "isPrivate" -> trim(label("Repository Type" , boolean())), - "issuesOption" -> trim(label("Issues Option" , text(required, featureOption))), - "externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))), - "wikiOption" -> trim(label("Wiki Option" , text(required, featureOption))), - "externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))), - "allowFork" -> trim(label("Allow Forking" , boolean())), - "mergeOptions" -> mergeOptions, + "repositoryName" -> trim( + label("Repository Name", text(required, maxlength(100), repository, renameRepositoryName)) + ), + "description" -> trim(label("Description", optional(text()))), + "isPrivate" -> trim(label("Repository Type", boolean())), + "issuesOption" -> trim(label("Issues Option", text(required, featureOption))), + "externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))), + "wikiOption" -> trim(label("Wiki Option", text(required, featureOption))), + "externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))), + "allowFork" -> trim(label("Allow Forking", boolean())), + "mergeOptions" -> mergeOptions, "defaultMergeOption" -> trim(label("Default merge strategy", text(required))) )(OptionsForm.apply).verifying { form => - if(!form.mergeOptions.contains(form.defaultMergeOption)){ + if (!form.mergeOptions.contains(form.defaultMergeOption)) { Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.") } else Nil } @@ -65,30 +79,30 @@ case class DefaultBranchForm(defaultBranch: String) val defaultBranchForm = mapping( - "defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))) + "defaultBranch" -> trim(label("Default Branch", text(required, maxlength(100)))) )(DefaultBranchForm.apply) - // for deploy key case class DeployKeyForm(title: String, publicKey: String, allowWrite: Boolean) val deployKeyForm = mapping( - "title" -> trim(label("Title", text(required, maxlength(100)))), - "publicKey" -> trim2(label("Key" , text(required))), // TODO duplication check in the repository? - "allowWrite" -> trim(label("Key" , boolean())) + "title" -> trim(label("Title", text(required, maxlength(100)))), + "publicKey" -> trim2(label("Key", text(required))), // TODO duplication check in the repository? + "allowWrite" -> trim(label("Key", boolean())) )(DeployKeyForm.apply) // for web hook url addition case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String]) - def webHookForm(update:Boolean) = mapping( - "url" -> trim(label("url", text(required, webHook(update)))), - "events" -> webhookEvents, - "ctype" -> label("ctype", text()), - "token" -> optional(trim(label("token", text(maxlength(100))))) - )( - (url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token) - ) + def webHookForm(update: Boolean) = + mapping( + "url" -> trim(label("url", text(required, webHook(update)))), + "events" -> webhookEvents, + "ctype" -> label("ctype", text()), + "token" -> optional(trim(label("token", text(maxlength(100))))) + )( + (url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token) + ) // for transfer ownership case class TransferOwnerShipForm(newOwner: String) @@ -131,24 +145,24 @@ form.defaultMergeOption ) // Change repository name - if(repository.name != form.repositoryName){ + if (repository.name != form.repositoryName) { // Update database renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) // Move git repository - defining(getRepositoryDir(repository.owner, repository.name)){ dir => - if(dir.isDirectory){ + defining(getRepositoryDir(repository.owner, repository.name)) { dir => + if (dir.isDirectory) { FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName)) } } // Move wiki repository - defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir => - if(dir.isDirectory) { + defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir => + if (dir.isDirectory) { FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName)) } } // Move files directory - defining(getRepositoryFilesDir(repository.owner, repository.name)){ dir => - if(dir.isDirectory) { + defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir => + if (dir.isDirectory) { FileUtils.moveDirectory(dir, getRepositoryFilesDir(repository.owner, form.repositoryName)) } } @@ -170,7 +184,7 @@ /** Update default branch */ post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) => - if(!repository.branchList.contains(form.defaultBranch)){ + if (!repository.branchList.contains(form.defaultBranch)) { redirect(s"/${repository.owner}/${repository.name}/settings/options") } else { saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch) @@ -187,12 +201,15 @@ get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository => import gitbucket.core.api._ val branch = params("branch") - if(!repository.branchList.contains(branch)){ + if (!repository.branchList.contains(branch)) { redirect(s"/${repository.owner}/${repository.name}/settings/branches") } else { val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch)) - val lastWeeks = getRecentStatuesContexts(repository.owner, repository.name, - Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.UTC))).toSet + val lastWeeks = getRecentStatuesContexts( + repository.owner, + repository.name, + Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.UTC)) + ).toSet val knownContexts = (lastWeeks ++ protection.status.contexts).toSeq.sortBy(identity) html.branchprotection(repository, branch, protection, knownContexts, flash.get("info")) } @@ -205,7 +222,8 @@ html.collaborators( getCollaborators(repository.owner, repository.name), getAccountByUserName(repository.owner).get.isGroupAccount, - repository) + repository + ) }) post("/:owner/:repository/settings/collaborators")(ownerOnly { repository => @@ -255,62 +273,90 @@ * Send the test request to registered web hook URLs. */ ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository => - def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => Array(h.getName, h.getValue) } + def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => + Array(h.getName, h.getValue) + } - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - import scala.collection.JavaConverters._ - import scala.concurrent.duration._ - import scala.concurrent._ - import scala.util.control.NonFatal - import org.apache.http.util.EntityUtils - import scala.concurrent.ExecutionContext.Implicits.global + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { + git => + import scala.collection.JavaConverters._ + import scala.concurrent.duration._ + import scala.concurrent._ + import scala.util.control.NonFatal + import org.apache.http.util.EntityUtils + import scala.concurrent.ExecutionContext.Implicits.global - val url = params("url") - val token = Some(params("token")) - val ctype = WebHookContentType.valueOf(params("ctype")) - val dummyWebHookInfo = RepositoryWebHook(repository.owner, repository.name, url, ctype, token) - val dummyPayload = { - val ownerAccount = getAccountByUserName(repository.owner).get - val commits = if(JGitUtil.isEmpty(git)) List.empty else git.log - .add(git.getRepository.resolve(repository.repository.defaultBranch)) - .setMaxCount(4) - .call.iterator.asScala.map(new CommitInfo(_)).toList - val pushedCommit = commits.drop(1) + val url = params("url") + val token = Some(params("token")) + val ctype = WebHookContentType.valueOf(params("ctype")) + val dummyWebHookInfo = RepositoryWebHook(repository.owner, repository.name, url, ctype, token) + val dummyPayload = { + val ownerAccount = getAccountByUserName(repository.owner).get + val commits = + if (JGitUtil.isEmpty(git)) List.empty + else + git.log + .add(git.getRepository.resolve(repository.repository.defaultBranch)) + .setMaxCount(4) + .call + .iterator + .asScala + .map(new CommitInfo(_)) + .toList + val pushedCommit = commits.drop(1) - WebHookPushPayload( - git = git, - sender = ownerAccount, - refName = "refs/heads/" + repository.repository.defaultBranch, - repositoryInfo = repository, - commits = pushedCommit, - repositoryOwner = ownerAccount, - oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()), - newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()) + WebHookPushPayload( + git = git, + sender = ownerAccount, + refName = "refs/heads/" + repository.repository.defaultBranch, + repositoryInfo = repository, + commits = pushedCommit, + repositoryOwner = ownerAccount, + oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()), + newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()) + ) + } + + val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head + + val toErrorMap: PartialFunction[Throwable, Map[String, String]] = { + case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage)) + case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url")) + case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url")) + case NonFatal(e) => Map("error" -> (e.getClass + " " + e.getMessage)) + } + + contentType = formats("json") + org.json4s.jackson.Serialization.write( + Map( + "url" -> url, + "request" -> Await.result( + reqFuture + .map( + req => + Map( + "headers" -> _headers(req.getAllHeaders), + "payload" -> json + ) + ) + .recover(toErrorMap), + 20 seconds + ), + "response" -> Await.result( + resFuture + .map( + res => + Map( + "status" -> res.getStatusLine(), + "body" -> EntityUtils.toString(res.getEntity()), + "headers" -> _headers(res.getAllHeaders()) + ) + ) + .recover(toErrorMap), + 20 seconds + ) + ) ) - } - - val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head - - val toErrorMap: PartialFunction[Throwable, Map[String,String]] = { - case e: java.net.UnknownHostException => Map("error"-> ("Unknown host " + e.getMessage)) - case e: java.lang.IllegalArgumentException => Map("error"-> ("invalid url")) - case e: org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url")) - case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage)) - } - - contentType = formats("json") - org.json4s.jackson.Serialization.write(Map( - "url" -> url, - "request" -> Await.result(reqFuture.map(req => Map( - "headers" -> _headers(req.getAllHeaders), - "payload" -> json - )).recover(toErrorMap), 20 seconds), - "response" -> Await.result(resFuture.map(res => Map( - "status" -> res.getStatusLine(), - "body" -> EntityUtils.toString(res.getEntity()), - "headers" -> _headers(res.getAllHeaders()) - )).recover(toErrorMap), 20 seconds) - )) } }) @@ -318,8 +364,9 @@ * Display the web hook edit page. */ get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository => - getWebHook(repository.owner, repository.name, params("url")).map{ case (webhook, events) => - html.edithook(webhook, events, repository, false) + getWebHook(repository.owner, repository.name, params("url")).map { + case (webhook, events) => + html.edithook(webhook, events, repository, false) } getOrElse NotFound() }) @@ -344,25 +391,25 @@ */ post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) => // Change repository owner - if(repository.owner != form.newOwner){ - LockUtil.lock(s"${repository.owner}/${repository.name}"){ + if (repository.owner != form.newOwner) { + LockUtil.lock(s"${repository.owner}/${repository.name}") { // Update database renameRepository(repository.owner, repository.name, form.newOwner, repository.name) // Move git repository - defining(getRepositoryDir(repository.owner, repository.name)){ dir => - if(dir.isDirectory){ + defining(getRepositoryDir(repository.owner, repository.name)) { dir => + if (dir.isDirectory) { FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name)) } } // Move wiki repository - defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir => - if(dir.isDirectory) { + defining(getWikiRepositoryDir(repository.owner, repository.name)) { dir => + if (dir.isDirectory) { FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name)) } } // Move files directory - defining(getRepositoryFilesDir(repository.owner, repository.name)){ dir => - if(dir.isDirectory) { + defining(getRepositoryFilesDir(repository.owner, repository.name)) { dir => + if (dir.isDirectory) { FileUtils.moveDirectory(dir, getRepositoryFilesDir(form.newOwner, repository.name)) } } @@ -378,7 +425,7 @@ * Delete the repository. */ post("/:owner/:repository/settings/delete")(ownerOnly { repository => - LockUtil.lock(s"${repository.owner}/${repository.name}"){ + LockUtil.lock(s"${repository.owner}/${repository.name}") { // Delete the repository and related files deleteRepository(repository.owner, repository.name) @@ -428,10 +475,10 @@ /** * Provides duplication check for web hook url. */ - private def webHook(needExists: Boolean): Constraint = new Constraint(){ + private def webHook(needExists: Boolean): Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(getWebHook(params("owner"), params("repository"), value).isDefined != needExists){ - Some(if(needExists){ + if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) { + Some(if (needExists) { "URL had not been registered yet." } else { "URL had been registered already." @@ -441,17 +488,18 @@ } } - private def webhookEvents = new ValueType[Set[WebHook.Event]]{ + private def webhookEvents = new ValueType[Set[WebHook.Event]] { def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = { WebHook.Event.values.flatMap { t => params.get(name + "." + t.name).map(_ => t) }.toSet } - def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){ - Seq(name -> messages("error.required").format(name)) - } else { - Nil - } + def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = + if (convert(name, params, messages).isEmpty) { + Seq(name -> messages("error.required").format(name)) + } else { + Nil + } } // /** @@ -472,12 +520,17 @@ /** * Duplicate check for the rename repository name. */ - private def renameRepositoryName: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = { + private def renameRepositoryName: Constraint = new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = { for { repoName <- params.optionValue("repository") if repoName != value userName <- params.optionValue("owner") - _ <- getRepositoryNamesOfUser(userName).find(_ == value) + _ <- getRepositoryNamesOfUser(userName).find(_ == value) } yield { "Repository already exists." } @@ -487,38 +540,45 @@ /** * */ - private def featureOption: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = - if(Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.") + private def featureOption: Constraint = new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = + if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.") } - /** * Provides Constraint to validate the repository transfer user. */ - private def transferUser: Constraint = new Constraint(){ + private def transferUser: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = getAccountByUserName(value) match { - case None => Some("User does not exist.") - case Some(x) => if(x.userName == params("owner")){ - Some("This is current repository owner.") - } else { - params.get("repository").flatMap { repositoryName => - getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." } + case None => Some("User does not exist.") + case Some(x) => + if (x.userName == params("owner")) { + Some("This is current repository owner.") + } else { + params.get("repository").flatMap { repositoryName => + getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ => + "User already has same repository." + } + } } - } } } - private def mergeOptions = new ValueType[Seq[String]]{ + private def mergeOptions = new ValueType[Seq[String]] { override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = { params.get("mergeOptions").getOrElse(Nil) } override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = { val mergeOptions = params.get("mergeOptions").getOrElse(Nil) - if(mergeOptions.isEmpty){ + if (mergeOptions.isEmpty) { Seq("mergeOptions" -> "At least one option must be enabled.") - } else if(!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))){ + } else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) { Seq("mergeOptions" -> "mergeOptions are invalid.") } else { Nil diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 312a828..38216c9 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -29,20 +29,44 @@ import org.scalatra._ import org.scalatra.i18n.Messages - -class RepositoryViewerController extends RepositoryViewerControllerBase - with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService - with LabelsService with MilestonesService with PrioritiesService - with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with CommitStatusService - with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService +class RepositoryViewerController + extends RepositoryViewerControllerBase + with RepositoryService + with AccountService + with ActivityService + with IssuesService + with WebHookService + with CommitsService + with LabelsService + with MilestonesService + with PrioritiesService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator + with PullRequestService + with CommitStatusService + with WebHookPullRequestService + with WebHookPullRequestReviewCommentService + with ProtectedBranchService /** * The repository viewer. */ trait RepositoryViewerControllerBase extends ControllerBase { - self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService - with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with CommitStatusService - with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService => + self: RepositoryService + with AccountService + with ActivityService + with IssuesService + with WebHookService + with CommitsService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator + with PullRequestService + with CommitStatusService + with WebHookPullRequestService + with WebHookPullRequestReviewCommentService + with ProtectedBranchService => ArchiveCommand.registerFormat("zip", new ZipFormat) ArchiveCommand.registerFormat("tar.gz", new TgzFormat) @@ -83,38 +107,38 @@ ) 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()))), + "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())), - "content" -> trim(label("Content", text(required))), - "message" -> trim(label("Message", optional(text()))), - "charset" -> trim(label("Charset", text(required))), + "branch" -> trim(label("Branch", text(required))), + "path" -> trim(label("Path", text())), + "content" -> trim(label("Content", text(required))), + "message" -> trim(label("Message", optional(text()))), + "charset" -> trim(label("Charset", text(required))), "lineSeparator" -> trim(label("Line Separator", text(required))), - "newFileName" -> trim(label("Filename", text(required))), - "oldFileName" -> trim(label("Old filename", optional(text()))), - "commit" -> trim(label("Commit", text(required, conflict))) + "newFileName" -> trim(label("Filename", text(required))), + "oldFileName" -> trim(label("Old filename", optional(text()))), + "commit" -> trim(label("Commit", text(required, conflict))) )(EditorForm.apply) val deleteForm = mapping( - "branch" -> trim(label("Branch", text(required))), - "path" -> trim(label("Path", text())), - "message" -> trim(label("Message", optional(text()))), + "branch" -> trim(label("Branch", text(required))), + "path" -> trim(label("Path", text())), + "message" -> trim(label("Message", optional(text()))), "fileName" -> trim(label("Filename", text(required))), - "commit" -> trim(label("Commit", text(required, conflict))) + "commit" -> trim(label("Commit", text(required, conflict))) )(DeleteForm.apply) val commentForm = mapping( - "fileName" -> trim(label("Filename", optional(text()))), + "fileName" -> trim(label("Filename", optional(text()))), "oldLineNumber" -> trim(label("Old line number", optional(number()))), "newLineNumber" -> trim(label("New line number", optional(number()))), - "content" -> trim(label("Content", text(required))), - "issueId" -> trim(label("Issue Id", optional(number()))) + "content" -> trim(label("Content", text(required))), + "issueId" -> trim(label("Issue Id", optional(number()))) )(CommentForm.apply) /** @@ -124,7 +148,8 @@ contentType = "text/html" val filename = params.get("filename") filename match { - case Some(f) => helpers.renderMarkup( + case Some(f) => + helpers.renderMarkup( filePath = List(f), fileContent = params("content"), branch = "master", @@ -133,7 +158,8 @@ enableRefsLink = params("enableRefsLink").toBoolean, enableAnchor = false ) - case None => helpers.markdown( + case None => + helpers.markdown( markdown = params("content"), repository = repository, enableWikiLink = params("enableWikiLink").toBoolean, @@ -157,9 +183,10 @@ gitbucket.core.repo.html.creating(owner, repository) } else { params.get("go-get") match { - case Some("1") => defining(request.paths) { paths => - getRepository(owner, repository).map(gitbucket.core.html.goget(_)) getOrElse NotFound() - } + case Some("1") => + defining(request.paths) { paths => + getRepository(owner, repository).map(gitbucket.core.html.goget(_)) getOrElse NotFound() + } case _ => referrersOnly(fileList(_)) } } @@ -170,10 +197,12 @@ val repository = params("repository") contentType = formats("json") val creating = RepositoryCreationService.isCreating(owner, repository) - Serialization.write(Map( - "creating" -> creating, - "error" -> (if(creating) None else RepositoryCreationService.getCreationError(owner, repository)) - )) + Serialization.write( + Map( + "creating" -> creating, + "error" -> (if (creating) None else RepositoryCreationService.getCreationError(owner, repository)) + ) + ) } /** @@ -181,7 +210,7 @@ */ get("/:owner/:repository/tree/*")(referrersOnly { repository => val (id, path) = repository.splitPath(multiParams("splat").head) - if(path.isEmpty){ + if (path.isEmpty) { fileList(repository, id) } else { fileList(repository, id, path) @@ -202,45 +231,57 @@ def getSummary(statuses: List[CommitStatus]): (CommitState, String) = { val stateMap = statuses.groupBy(_.state) val state = CommitState.combine(stateMap.keySet) - val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ") + val summary = stateMap.map { case (keyState, states) => states.size + " " + keyState.name }.mkString(", ") state -> summary } - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - JGitUtil.getCommitLog(git, branchName, page, 30, path) match { - case Right((logs, hasNext)) => - html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository, - logs.splitWith{ (commit1, commit2) => - view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) - }, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), getStatuses, getSummary) - case Left(_) => NotFound() - } + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { + git => + JGitUtil.getCommitLog(git, branchName, page, 30, path) match { + case Right((logs, hasNext)) => + html.commits( + if (path.isEmpty) Nil else path.split("/").toList, + branchName, + repository, + logs.splitWith { (commit1, commit2) => + view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) + }, + page, + hasNext, + hasDeveloperRole(repository.owner, repository.name, context.loginAccount), + getStatuses, + getSummary + ) + case Left(_) => NotFound() + } } }) get("/:owner/:repository/new/*")(writableUsersOnly { repository => val (branch, path) = repository.splitPath(multiParams("splat").head) - val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName) + val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch) + .needStatusCheck(context.loginAccount.get.userName) using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) html.editor( - branch = branch, - repository = repository, - pathList = if (path.length == 0) Nil else path.split("/").toList, - fileName = None, - content = JGitUtil.ContentInfo("text", None, None, Some("UTF-8")), + branch = branch, + repository = repository, + pathList = if (path.length == 0) Nil else path.split("/").toList, + fileName = None, + content = JGitUtil.ContentInfo("text", None, None, Some("UTF-8")), protectedBranch = protectedBranch, - commit = revCommit.getName + commit = revCommit.getName ) } }) 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) + 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) => @@ -251,120 +292,126 @@ commitFiles( repository = repository, - branch = form.branch, - path = form.path, - files = files, - message = form.message.getOrElse("Add files via upload") + branch = form.branch, + path = form.path, + files = files, + message = form.message.getOrElse("Add files via upload") ) - if(form.path.length == 0){ + 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) + val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch) + .needStatusCheck(context.loginAccount.get.userName) - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { + git => + val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) - getPathObjectId(git, path, revCommit).map { objectId => - val paths = path.split("/") - html.editor( - branch = branch, - repository = repository, - pathList = paths.take(paths.size - 1).toList, - fileName = Some(paths.last), - content = JGitUtil.getContentInfo(git, path, objectId), - protectedBranch = protectedBranch, - commit = revCommit.getName - ) - } getOrElse NotFound() + getPathObjectId(git, path, revCommit).map { objectId => + val paths = path.split("/") + html.editor( + branch = branch, + repository = repository, + pathList = paths.take(paths.size - 1).toList, + fileName = Some(paths.last), + content = JGitUtil.getContentInfo(git, path, objectId), + protectedBranch = protectedBranch, + commit = revCommit.getName + ) + } getOrElse NotFound() } }) get("/:owner/:repository/remove/*")(writableUsersOnly { repository => val (branch, path) = repository.splitPath(multiParams("splat").head) - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { + git => + val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch)) - getPathObjectId(git, path, revCommit).map { objectId => - val paths = path.split("/") - html.delete( - branch = branch, - repository = repository, - pathList = paths.take(paths.size - 1).toList, - fileName = paths.last, - content = JGitUtil.getContentInfo(git, path, objectId), - commit = revCommit.getName - ) - } getOrElse NotFound() + getPathObjectId(git, path, revCommit).map { objectId => + val paths = path.split("/") + html.delete( + branch = branch, + repository = repository, + pathList = paths.take(paths.size - 1).toList, + fileName = paths.last, + content = JGitUtil.getContentInfo(git, path, objectId), + commit = revCommit.getName + ) + } getOrElse NotFound() } }) post("/:owner/:repository/create", editorForm)(writableUsersOnly { (form, repository) => commitFile( - repository = repository, - branch = form.branch, - path = form.path, + repository = repository, + branch = form.branch, + path = form.path, newFileName = Some(form.newFileName), oldFileName = None, - content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), - charset = form.charset, - message = form.message.getOrElse(s"Create ${form.newFileName}"), - commit = form.commit + content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), + charset = form.charset, + message = form.message.getOrElse(s"Create ${form.newFileName}"), + commit = form.commit ) - redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${ - if(form.path.length == 0) urlEncode(form.newFileName) else s"${form.path}/${urlEncode(form.newFileName)}" - }") + redirect( + s"/${repository.owner}/${repository.name}/blob/${form.branch}/${if (form.path.length == 0) urlEncode(form.newFileName) + else s"${form.path}/${urlEncode(form.newFileName)}"}" + ) }) post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) => commitFile( - repository = repository, - branch = form.branch, - path = form.path, + repository = repository, + branch = form.branch, + path = form.path, newFileName = Some(form.newFileName), oldFileName = form.oldFileName, - content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), - charset = form.charset, - message = if (form.oldFileName.contains(form.newFileName)) { + content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator), + charset = form.charset, + message = if (form.oldFileName.contains(form.newFileName)) { form.message.getOrElse(s"Update ${form.newFileName}") } else { form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}") }, - commit = form.commit + commit = form.commit ) - redirect(s"/${repository.owner}/${repository.name}/blob/${urlEncode(form.branch)}/${ - if (form.path.length == 0) urlEncode(form.newFileName) else s"${form.path}/${urlEncode(form.newFileName)}" - }") + redirect( + s"/${repository.owner}/${repository.name}/blob/${urlEncode(form.branch)}/${if (form.path.length == 0) urlEncode(form.newFileName) + else s"${form.path}/${urlEncode(form.newFileName)}"}" + ) }) post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) => commitFile( - repository = repository, - branch = form.branch, - path = form.path, + repository = repository, + branch = form.branch, + path = form.path, newFileName = None, oldFileName = Some(form.fileName), - content = "", - charset = "", - message = form.message.getOrElse(s"Delete ${form.fileName}"), - commit = form.commit + content = "", + charset = "", + message = form.message.getOrElse(s"Delete ${form.fileName}"), + commit = form.commit ) - redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}") + redirect( + s"/${repository.owner}/${repository.name}/tree/${form.branch}${if (form.path.length == 0) "" else form.path}" + ) }) get("/:owner/:repository/raw/*")(referrersOnly { repository => val (id, path) = repository.splitPath(multiParams("splat").head) - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) getPathObjectId(git, path, revCommit).map { objectId => @@ -379,25 +426,27 @@ val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository => val (id, path) = repository.splitPath(multiParams("splat").head) val raw = params.get("raw").getOrElse("false").toBoolean - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) - getPathObjectId(git, path, revCommit).map { objectId => - if(raw){ - // Download (This route is left for backword compatibility) - responseRawFile(git, objectId, path, repository) - } else { - html.blob( - branch = id, - repository = repository, - pathList = path.split("/").toList, - content = JGitUtil.getContentInfo(git, path, objectId), - latestCommit = new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)), - hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount), - isBlame = request.paths(2) == "blame", - isLfsFile = isLfsFile(git, objectId) - ) - } - } getOrElse NotFound() + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { + git => + val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) + getPathObjectId(git, path, revCommit).map { + objectId => + if (raw) { + // Download (This route is left for backword compatibility) + responseRawFile(git, objectId, path, repository) + } else { + html.blob( + branch = id, + repository = repository, + pathList = path.split("/").toList, + content = JGitUtil.getContentInfo(git, path, objectId), + latestCommit = new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)), + hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount), + isBlame = request.paths(2) == "blame", + isLfsFile = isLfsFile(git, objectId) + ) + } + } getOrElse NotFound() } }) @@ -405,7 +454,7 @@ JGitUtil.getObjectLoaderFromId(git, objectId)(JGitUtil.isLfsPointer).getOrElse(false) } - get("/:owner/:repository/blame/*"){ + get("/:owner/:repository/blame/*") { blobRoute.action() } @@ -415,26 +464,31 @@ ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository => val (id, path) = repository.splitPath(multiParams("splat").head) contentType = formats("json") - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name - Serialization.write(Map( - "root" -> s"${context.baseUrl}/${repository.owner}/${repository.name}", - "id" -> id, - "path" -> path, - "last" -> last, - "blame" -> JGitUtil.getBlame(git, id, path).map{ blame => + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { + git => + val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name + Serialization.write( Map( - "id" -> blame.id, - "author" -> view.helpers.user(blame.authorName, blame.authorEmailAddress).toString, - "avatar" -> view.helpers.avatarLink(blame.authorName, 32, blame.authorEmailAddress).toString, - "authed" -> helper.html.datetimeago(blame.authorTime).toString, - "prev" -> blame.prev, - "prevPath" -> blame.prevPath, - "commited" -> blame.commitTime.getTime, - "message" -> blame.message, - "lines" -> blame.lines + "root" -> s"${context.baseUrl}/${repository.owner}/${repository.name}", + "id" -> id, + "path" -> path, + "last" -> last, + "blame" -> JGitUtil.getBlame(git, id, path).map { + blame => + Map( + "id" -> blame.id, + "author" -> view.helpers.user(blame.authorName, blame.authorEmailAddress).toString, + "avatar" -> view.helpers.avatarLink(blame.authorName, 32, blame.authorEmailAddress).toString, + "authed" -> helper.html.datetimeago(blame.authorTime).toString, + "prev" -> blame.prev, + "prevPath" -> blame.prevPath, + "commited" -> blame.commitTime.getTime, + "message" -> blame.message, + "lines" -> blame.lines + ) + } ) - })) + ) } }) @@ -445,20 +499,28 @@ val id = params("id") try { - using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => - defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) { revCommit => - val diffs = JGitUtil.getDiffs(git, None, id, true, false) - val oldCommitId = JGitUtil.getParentCommitId(git, id) + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { + git => + defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) { + revCommit => + val diffs = JGitUtil.getDiffs(git, None, id, true, false) + val oldCommitId = JGitUtil.getParentCommitId(git, id) - html.commit(id, new JGitUtil.CommitInfo(revCommit), - JGitUtil.getBranchesOfCommit(git, revCommit.getName), - JGitUtil.getTagsOfCommit(git, revCommit.getName), - getCommitComments(repository.owner, repository.name, id, true), - repository, diffs, oldCommitId, hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) - } + html.commit( + id, + new JGitUtil.CommitInfo(revCommit), + JGitUtil.getBranchesOfCommit(git, revCommit.getName), + JGitUtil.getTagsOfCommit(git, revCommit.getName), + getCommitComments(repository.owner, repository.name, id, true), + repository, + diffs, + oldCommitId, + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) + } } } catch { - case e:MissingObjectException => NotFound() + case e: MissingObjectException => NotFound() } }) @@ -470,7 +532,7 @@ diff } } catch { - case e:MissingObjectException => NotFound() + case e: MissingObjectException => NotFound() } }) @@ -489,24 +551,50 @@ post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) => val id = params("id") - createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content, - form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId) + createCommitComment( + repository.owner, + repository.name, + id, + context.loginAccount.get.userName, + form.content, + form.fileName, + form.oldLineNumber, + form.newLineNumber, + form.issueId + ) form.issueId match { - case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content) - case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content) + case Some(issueId) => + recordCommentPullRequestActivity( + repository.owner, + repository.name, + context.loginAccount.get.userName, + issueId, + form.content + ) + case None => + recordCommentCommitActivity( + repository.owner, + repository.name, + context.loginAccount.get.userName, + id, + form.content + ) } redirect(s"/${repository.owner}/${repository.name}/commit/${id}") }) ajaxGet("/:owner/:repository/commit/:id/comment/_form")(readableUsersOnly { repository => - val id = params("id") - val fileName = params.get("fileName") + val id = params("id") + val fileName = params.get("fileName") val oldLineNumber = params.get("oldLineNumber") map (_.toInt) val newLineNumber = params.get("newLineNumber") map (_.toInt) - val issueId = params.get("issueId") map (_.toInt) + val issueId = params.get("issueId") map (_.toInt) html.commentform( commitId = id, - fileName, oldLineNumber, newLineNumber, issueId, + fileName, + oldLineNumber, + newLineNumber, + issueId, hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository = repository ) @@ -514,64 +602,99 @@ ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) => val id = params("id") - val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, - form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId) + val commentId = createCommitComment( + repository.owner, + repository.name, + id, + context.loginAccount.get.userName, + form.content, + form.fileName, + form.oldLineNumber, + form.newLineNumber, + form.issueId + ) val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get form.issueId match { case Some(issueId) => - getPullRequest(repository.owner, repository.name, issueId).foreach { case (issue, pullRequest) => - recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content) - PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, form.content, issue, repository)) - callPullRequestReviewCommentWebHook("create", comment, repository, issue, pullRequest, context.baseUrl, context.loginAccount.get) + getPullRequest(repository.owner, repository.name, issueId).foreach { + case (issue, pullRequest) => + recordCommentPullRequestActivity( + repository.owner, + repository.name, + context.loginAccount.get.userName, + issueId, + form.content + ) + PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, form.content, issue, repository)) + callPullRequestReviewCommentWebHook( + "create", + comment, + repository, + issue, + pullRequest, + context.baseUrl, + context.loginAccount.get + ) } case None => - recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content) + recordCommentCommitActivity( + repository.owner, + repository.name, + context.loginAccount.get.userName, + id, + form.content + ) } - helper.html.commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository) + helper.html + .commitcomment(comment, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository) }) ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository => - getCommitComment(repository.owner, repository.name, params("id")) map { x => - if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){ - params.get("dataType") collect { - case t if t == "html" => html.editcomment(x.content, x.commentId, repository) - } getOrElse { - contentType = formats("json") - org.json4s.jackson.Serialization.write( - Map( - "content" -> view.Markdown.toHtml( - markdown = x.content, - repository = repository, - enableWikiLink = false, - enableRefsLink = true, - enableAnchor = true, - enableLineBreaks = true, - hasWritePermission = true + getCommitComment(repository.owner, repository.name, params("id")) map { + x => + if (isEditable(x.userName, x.repositoryName, x.commentedUserName)) { + params.get("dataType") collect { + case t if t == "html" => html.editcomment(x.content, x.commentId, repository) + } getOrElse { + contentType = formats("json") + org.json4s.jackson.Serialization.write( + Map( + "content" -> view.Markdown.toHtml( + markdown = x.content, + repository = repository, + enableWikiLink = false, + enableRefsLink = true, + enableAnchor = true, + enableLineBreaks = true, + hasWritePermission = true + ) ) - )) - } - } else Unauthorized() + ) + } + } else Unauthorized() } getOrElse NotFound() }) ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => - defining(repository.owner, repository.name){ case (owner, name) => - getCommitComment(owner, name, params("id")).map { comment => - if(isEditable(owner, name, comment.commentedUserName)){ - updateCommitComment(comment.commentId, form.content) - redirect(s"/${owner}/${name}/commit_comments/_data/${comment.commentId}") - } else Unauthorized() - } getOrElse NotFound() + defining(repository.owner, repository.name) { + case (owner, name) => + getCommitComment(owner, name, params("id")).map { comment => + if (isEditable(owner, name, comment.commentedUserName)) { + updateCommitComment(comment.commentId, form.content) + redirect(s"/${owner}/${name}/commit_comments/_data/${comment.commentId}") + } else Unauthorized() + } getOrElse NotFound() } }) ajaxPost("/:owner/:repository/commit_comments/delete/:id")(readableUsersOnly { repository => - defining(repository.owner, repository.name){ case (owner, name) => - getCommitComment(owner, name, params("id")).map { comment => - if(isEditable(owner, name, comment.commentedUserName)){ - Ok(deleteCommitComment(comment.commentId)) - } else Unauthorized() - } getOrElse NotFound() + defining(repository.owner, repository.name) { + case (owner, name) => + getCommitComment(owner, name, params("id")).map { comment => + if (isEditable(owner, name, comment.commentedUserName)) { + Ok(deleteCommitComment(comment.commentId)) + } else Unauthorized() + } getOrElse NotFound() } }) @@ -580,15 +703,29 @@ */ get("/:owner/:repository/branches")(referrersOnly { repository => val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet - val branches = JGitUtil.getBranches( - owner = repository.owner, - name = repository.name, - defaultBranch = repository.repository.defaultBranch, - origin = repository.repository.originUserName.isEmpty - ) - .sortBy(br => (br.mergeInfo.isEmpty, br.commitTime)) - .map(br => (br, getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId), protectedBranches.contains(br.name))) - .reverse + val branches = JGitUtil + .getBranches( + owner = repository.owner, + name = repository.name, + defaultBranch = repository.repository.defaultBranch, + origin = repository.repository.originUserName.isEmpty + ) + .sortBy(br => (br.mergeInfo.isEmpty, br.commitTime)) + .map( + br => + ( + br, + getPullRequestByRequestCommit( + repository.owner, + repository.name, + repository.repository.defaultBranch, + br.name, + br.commitId + ), + protectedBranches.contains(br.name) + ) + ) + .reverse html.branches(branches, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository) }) @@ -599,12 +736,14 @@ post("/:owner/:repository/branches")(writableUsersOnly { repository => val newBranchName = params.getOrElse("new", halt(400)) val fromBranchName = params.getOrElse("from", halt(400)) - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => JGitUtil.createBranch(git, fromBranchName, newBranchName) } match { case Right(message) => flash += "info" -> message - redirect(s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}") + redirect( + s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}" + ) case Left(message) => flash += "error" -> message redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}") @@ -616,9 +755,9 @@ */ get("/:owner/:repository/delete/*")(writableUsersOnly { repository => val branchName = multiParams("splat").head - val userName = context.loginAccount.get.userName - if(repository.repository.defaultBranch != branchName){ - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + val userName = context.loginAccount.get.userName + if (repository.repository.defaultBranch != branchName) { + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => git.branchDelete().setForce(true).setBranchNames(branchName).call() recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName) } @@ -647,20 +786,24 @@ }) get("/:owner/:repository/network/members")(referrersOnly { repository => - if(repository.repository.options.allowFork) { + if (repository.repository.options.allowFork) { html.forked( getRepository( repository.repository.originUserName.getOrElse(repository.owner), - repository.repository.originRepositoryName.getOrElse(repository.name)), + repository.repository.originRepositoryName.getOrElse(repository.name) + ), getForkedRepositories( repository.repository.originUserName.getOrElse(repository.owner), repository.repository.originRepositoryName.getOrElse(repository.name) - ).map { repository => (repository.userName, repository.repositoryName) }, + ).map { repository => + (repository.userName, repository.repositoryName) + }, context.loginAccount match { - case None => List() + case None => List() case account: Option[Account] => getGroupsByUserName(account.get.userName) }, // groups of current user - repository) + repository + ) } else BadRequest() }) @@ -668,9 +811,9 @@ * Displays the file find of branch. */ get("/:owner/:repository/find/*")(referrersOnly { repository => - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => val ref = multiParams("splat").head - JGitUtil.getTreeId(git, ref).map{ treeId => + JGitUtil.getTreeId(git, ref).map { treeId => html.find(ref, treeId, repository) } getOrElse NotFound() } @@ -680,73 +823,96 @@ * Get all file list of branch. */ ajaxGet("/:owner/:repository/tree-list/:tree")(referrersOnly { repository => - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => val treeId = params("tree") contentType = formats("json") Map("paths" -> JGitUtil.getAllFileListByTreeId(git, treeId)) } }) - case class UploadFiles(branch: String, path: String, fileIds: Map[String,String], message: String) { + case class UploadFiles(branch: String, path: String, fileIds: Map[String, String], message: String) { lazy val isValid: Boolean = fileIds.nonEmpty } case class CommitFile(id: String, name: String) - private def commitFiles(repository: RepositoryService.RepositoryInfo, - files: Seq[CommitFile], - branch: String, path: String, message: String) = { + private def commitFiles( + repository: RepositoryService.RepositoryInfo, + 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}") + file.copy(name = if (path.length == 0) file.name else s"${path}/${file.name}") } - _commitFile(repository, branch, message) { case (git, headTip, builder, inserter) => - JGitUtil.processTree(git, headTip) { (path, tree) => - if(!newFiles.exists(_.name.contains(path))) { - builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) - } - } - - 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() - } - } - } - - private def commitFile(repository: RepositoryService.RepositoryInfo, - branch: String, path: String, newFileName: Option[String], oldFileName: Option[String], - content: String, charset: String, message: String, commit: 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, message){ case (git, headTip, builder, inserter) => - if(headTip.getName == commit){ - val permission = JGitUtil.processTree(git, headTip) { (path, tree) => - // Add all entries except the editing file - if (!newPath.contains(path) && !oldPath.contains(path)) { + _commitFile(repository, branch, message) { + case (git, headTip, builder, inserter) => + JGitUtil.processTree(git, headTip) { (path, tree) => + if (!newFiles.exists(_.name.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() - } + + 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() + } } } - private def _commitFile(repository: RepositoryService.RepositoryInfo, - branch: String, message: String)(f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit) = { + private def commitFile( + repository: RepositoryService.RepositoryInfo, + branch: String, + path: String, + newFileName: Option[String], + oldFileName: Option[String], + content: String, + charset: String, + message: String, + commit: 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, message) { + case (git, headTip, builder, inserter) => + if (headTip.getName == commit) { + 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, message: String)( + f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit + ) = { LockUtil.lock(s"${repository.owner}/${repository.name}") { using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => @@ -758,8 +924,16 @@ f(git, headTip, builder, inserter) - val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), - headName, loginAccount.fullName, loginAccount.mailAddress, message) + val commitId = JGitUtil.createNewCommit( + git, + inserter, + headTip, + builder.getDirCache.writeTree(inserter), + headName, + loginAccount.fullName, + loginAccount.mailAddress, + message + ) inserter.flush() inserter.close() @@ -811,9 +985,17 @@ 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) + getAccountByUserName(repository.owner).map { ownerAccount => + WebHookPushPayload( + git, + loginAccount, + headName, + repository, + List(commit), + ownerAccount, + oldId = headTip, + newId = commitId + ) } } } @@ -834,38 +1016,52 @@ * @return HTML of the file list */ private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = { - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - if(JGitUtil.isEmpty(git)){ + using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => + if (JGitUtil.isEmpty(git)) { html.guide(repository, hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) } else { // get specified commit - JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) => - defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit => - val lastModifiedCommit = if(path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path) - // get files - val files = JGitUtil.getFileList(git, revision, path, context.settings.baseUrl) - val parentPath = if (path == ".") Nil else path.split("/").toList - // process README.md or README.markdown - val readme = files.find { file => - !file.isDirectory && readmeFiles.contains(file.name.toLowerCase) - }.map { file => - val path = (file.name :: parentPath.reverse).reverse - path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId( - Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get) - } + JGitUtil.getDefaultBranch(git, repository, revstr).map { + case (objectId, revision) => + defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit => + val lastModifiedCommit = + if (path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path) + // get files + val files = JGitUtil.getFileList(git, revision, path, context.settings.baseUrl) + val parentPath = if (path == ".") Nil else path.split("/").toList + // process README.md or README.markdown + val readme = files + .find { file => + !file.isDirectory && readmeFiles.contains(file.name.toLowerCase) + } + .map { file => + val path = (file.name :: parentPath.reverse).reverse + path -> StringUtil.convertFromByteArray( + JGitUtil + .getContentFromId(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true) + .get + ) + } - html.files(revision, repository, - if(path == ".") Nil else path.split("/").toList, // current path - new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit - JGitUtil.getCommitCount(repository.owner, repository.name, lastModifiedCommit.getName), - files, - readme, - hasDeveloperRole(repository.owner, repository.name, context.loginAccount), - getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch), - flash.get("info"), - flash.get("error") - ) - } + html.files( + revision, + repository, + if (path == ".") Nil else path.split("/").toList, // current path + new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit + JGitUtil.getCommitCount(repository.owner, repository.name, lastModifiedCommit.getName), + files, + readme, + hasDeveloperRole(repository.owner, repository.name, context.loginAccount), + getPullRequestFromBranch( + repository.owner, + repository.name, + revstr, + repository.repository.defaultBranch + ), + flash.get("info"), + flash.get("error") + ) + } } getOrElse NotFound() } } @@ -874,11 +1070,11 @@ 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 => + 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 repositorySuffix = (if(sha1.startsWith(revision)) sha1 else revision).replace('/','-') + val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-') val filename = repository.name + "-" + repositorySuffix + suffix contentType = "application/octet-stream" @@ -886,28 +1082,28 @@ response.setBufferSize(1024 * 1024); git.archive - .setFormat(suffix.tail) - .setPrefix(repository.name + "-" + repositorySuffix + "/") - .setTree(revCommit) - .setOutputStream(response.getOutputStream) - .call() + .setFormat(suffix.tail) + .setPrefix(repository.name + "-" + repositorySuffix + "/") + .setTree(revCommit) + .setOutputStream(response.getOutputStream) + .call() } } private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean = hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName - private def conflict: Constraint = new Constraint(){ + private def conflict: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = { - val owner = params("owner") + val owner = params("owner") val repository = params("repository") - val branch = params("branch") + val branch = params("branch") LockUtil.lock(s"${owner}/${repository}") { using(Git.open(getRepositoryDir(owner, repository))) { git => val headName = s"refs/heads/${branch}" val headTip = git.getRepository.resolve(headName) - if(headTip.getName != value){ + if (headTip.getName != value) { Some("Someone pushed new commits before you. Please reload this page and re-apply your changes.") } else { None @@ -917,7 +1113,9 @@ } } - override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = { + override protected def renderUncaughtException( + e: Throwable + )(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = { e.printStackTrace() } diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index 5629dec..d2d0649 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -23,8 +23,11 @@ import scala.collection.JavaConverters._ import scala.collection.mutable.ListBuffer -class SystemSettingsController extends SystemSettingsControllerBase - with AccountService with RepositoryService with AdminAuthenticator +class SystemSettingsController + extends SystemSettingsControllerBase + with AccountService + with RepositoryService + with AdminAuthenticator case class Table(name: String, columns: Seq[Column]) case class Column(name: String, primaryKey: Boolean) @@ -33,72 +36,81 @@ self: AccountService with RepositoryService with AdminAuthenticator => private val form = mapping( - "baseUrl" -> trim(label("Base URL", optional(text()))), - "information" -> trim(label("Information", optional(text()))), + "baseUrl" -> trim(label("Base URL", optional(text()))), + "information" -> trim(label("Information", optional(text()))), "allowAccountRegistration" -> trim(label("Account registration", boolean())), - "allowAnonymousAccess" -> trim(label("Anonymous access", boolean())), + "allowAnonymousAccess" -> trim(label("Anonymous access", boolean())), "isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())), - "gravatar" -> trim(label("Gravatar", boolean())), - "notification" -> trim(label("Notification", boolean())), - "activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))), - "ssh" -> trim(label("SSH access", boolean())), - "sshHost" -> trim(label("SSH host", optional(text()))), - "sshPort" -> trim(label("SSH port", optional(number()))), - "useSMTP" -> trim(label("SMTP", boolean())), - "smtp" -> optionalIfNotChecked("useSMTP", mapping( - "host" -> trim(label("SMTP Host", text(required))), - "port" -> trim(label("SMTP Port", optional(number()))), - "user" -> trim(label("SMTP User", optional(text()))), - "password" -> trim(label("SMTP Password", optional(text()))), - "ssl" -> trim(label("Enable SSL", optional(boolean()))), - "starttls" -> trim(label("Enable STARTTLS", optional(boolean()))), - "fromAddress" -> trim(label("FROM Address", optional(text()))), - "fromName" -> trim(label("FROM Name", optional(text()))) - )(Smtp.apply)), - "ldapAuthentication" -> trim(label("LDAP", boolean())), - "ldap" -> optionalIfNotChecked("ldapAuthentication", mapping( - "host" -> trim(label("LDAP host", text(required))), - "port" -> trim(label("LDAP port", optional(number()))), - "bindDN" -> trim(label("Bind DN", optional(text()))), - "bindPassword" -> trim(label("Bind Password", optional(text()))), - "baseDN" -> trim(label("Base DN", text(required))), - "userNameAttribute" -> trim(label("User name attribute", text(required))), - "additionalFilterCondition"-> trim(label("Additional filter condition", optional(text()))), - "fullNameAttribute" -> trim(label("Full name attribute", optional(text()))), - "mailAttribute" -> trim(label("Mail address attribute", optional(text()))), - "tls" -> trim(label("Enable TLS", optional(boolean()))), - "ssl" -> trim(label("Enable SSL", optional(boolean()))), - "keystore" -> trim(label("Keystore", optional(text()))) - )(Ldap.apply)), - "oidcAuthentication" -> trim(label("OIDC", boolean())), - "oidc" -> optionalIfNotChecked("oidcAuthentication", mapping( - "issuer" -> trim(label("Issuer", text(required))), - "clientID" -> trim(label("Client ID", text(required))), - "clientSecret" -> trim(label("Client secret", text(required))), - "jwsAlgorithm" -> trim(label("Signature algorithm", optional(text()))) - )(OIDC.apply)), + "gravatar" -> trim(label("Gravatar", boolean())), + "notification" -> trim(label("Notification", boolean())), + "activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))), + "ssh" -> trim(label("SSH access", boolean())), + "sshHost" -> trim(label("SSH host", optional(text()))), + "sshPort" -> trim(label("SSH port", optional(number()))), + "useSMTP" -> trim(label("SMTP", boolean())), + "smtp" -> optionalIfNotChecked( + "useSMTP", + mapping( + "host" -> trim(label("SMTP Host", text(required))), + "port" -> trim(label("SMTP Port", optional(number()))), + "user" -> trim(label("SMTP User", optional(text()))), + "password" -> trim(label("SMTP Password", optional(text()))), + "ssl" -> trim(label("Enable SSL", optional(boolean()))), + "starttls" -> trim(label("Enable STARTTLS", optional(boolean()))), + "fromAddress" -> trim(label("FROM Address", optional(text()))), + "fromName" -> trim(label("FROM Name", optional(text()))) + )(Smtp.apply) + ), + "ldapAuthentication" -> trim(label("LDAP", boolean())), + "ldap" -> optionalIfNotChecked( + "ldapAuthentication", + mapping( + "host" -> trim(label("LDAP host", text(required))), + "port" -> trim(label("LDAP port", optional(number()))), + "bindDN" -> trim(label("Bind DN", optional(text()))), + "bindPassword" -> trim(label("Bind Password", optional(text()))), + "baseDN" -> trim(label("Base DN", text(required))), + "userNameAttribute" -> trim(label("User name attribute", text(required))), + "additionalFilterCondition" -> trim(label("Additional filter condition", optional(text()))), + "fullNameAttribute" -> trim(label("Full name attribute", optional(text()))), + "mailAttribute" -> trim(label("Mail address attribute", optional(text()))), + "tls" -> trim(label("Enable TLS", optional(boolean()))), + "ssl" -> trim(label("Enable SSL", optional(boolean()))), + "keystore" -> trim(label("Keystore", optional(text()))) + )(Ldap.apply) + ), + "oidcAuthentication" -> trim(label("OIDC", boolean())), + "oidc" -> optionalIfNotChecked( + "oidcAuthentication", + mapping( + "issuer" -> trim(label("Issuer", text(required))), + "clientID" -> trim(label("Client ID", text(required))), + "clientSecret" -> trim(label("Client secret", text(required))), + "jwsAlgorithm" -> trim(label("Signature algorithm", optional(text()))) + )(OIDC.apply) + ), "skinName" -> trim(label("AdminLTE skin name", text(required))) )(SystemSettings.apply).verifying { settings => Vector( - if(settings.ssh && settings.baseUrl.isEmpty){ + if (settings.ssh && settings.baseUrl.isEmpty) { Some("baseUrl" -> "Base URL is required if SSH access is enabled.") } else None, - if(settings.ssh && settings.sshHost.isEmpty){ + if (settings.ssh && settings.sshHost.isEmpty) { Some("sshHost" -> "SSH host is required if SSH access is enabled.") } else None ).flatten } private val sendMailForm = mapping( - "smtp" -> mapping( - "host" -> trim(label("SMTP Host", text(required))), - "port" -> trim(label("SMTP Port", optional(number()))), - "user" -> trim(label("SMTP User", optional(text()))), - "password" -> trim(label("SMTP Password", optional(text()))), - "ssl" -> trim(label("Enable SSL", optional(boolean()))), - "starttls" -> trim(label("Enable STARTTLS", optional(boolean()))), + "smtp" -> mapping( + "host" -> trim(label("SMTP Host", text(required))), + "port" -> trim(label("SMTP Port", optional(number()))), + "user" -> trim(label("SMTP User", optional(text()))), + "password" -> trim(label("SMTP Password", optional(text()))), + "ssl" -> trim(label("Enable SSL", optional(boolean()))), + "starttls" -> trim(label("Enable STARTTLS", optional(boolean()))), "fromAddress" -> trim(label("FROM Address", optional(text()))), - "fromName" -> trim(label("FROM Name", optional(text()))) + "fromName" -> trim(label("FROM Name", optional(text()))) )(Smtp.apply), "testAddress" -> trim(label("", text(required))) )(SendMailForm.apply) @@ -107,126 +119,156 @@ case class DataExportForm(tableNames: List[String]) - case class NewUserForm(userName: String, password: String, fullName: String, - mailAddress: String, isAdmin: Boolean, - description: Option[String], url: Option[String], fileId: Option[String]) + case class NewUserForm( + userName: String, + password: String, + fullName: String, + mailAddress: String, + isAdmin: Boolean, + description: Option[String], + url: Option[String], + fileId: Option[String] + ) - case class EditUserForm(userName: String, password: Option[String], fullName: String, - mailAddress: String, isAdmin: Boolean, description: Option[String], url: Option[String], - fileId: Option[String], clearImage: Boolean, isRemoved: Boolean) + case class EditUserForm( + userName: String, + password: Option[String], + fullName: String, + mailAddress: String, + isAdmin: Boolean, + description: Option[String], + url: Option[String], + fileId: Option[String], + clearImage: Boolean, + isRemoved: Boolean + ) - case class NewGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], - members: String) + case class NewGroupForm( + groupName: String, + description: Option[String], + url: Option[String], + fileId: Option[String], + members: String + ) - case class EditGroupForm(groupName: String, description: Option[String], url: Option[String], fileId: Option[String], - members: String, clearImage: Boolean, isRemoved: Boolean) - + case class EditGroupForm( + groupName: String, + description: Option[String], + url: Option[String], + fileId: Option[String], + members: String, + clearImage: Boolean, + isRemoved: Boolean + ) val newUserForm = mapping( - "userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), - "password" -> trim(label("Password" ,text(required, maxlength(20), password))), - "fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))), - "mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))), - "isAdmin" -> trim(label("User Type" ,boolean())), - "description" -> trim(label("bio" ,optional(text()))), - "url" -> trim(label("URL" ,optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" ,optional(text()))) + "userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), + "password" -> trim(label("Password", text(required, maxlength(20), password))), + "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), + "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))), + "isAdmin" -> trim(label("User Type", boolean())), + "description" -> trim(label("bio", optional(text()))), + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))) )(NewUserForm.apply) val editUserForm = mapping( - "userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))), - "password" -> trim(label("Password" ,optional(text(maxlength(20), password)))), - "fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))), - "mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))), - "isAdmin" -> trim(label("User Type" ,boolean())), - "description" -> trim(label("bio" ,optional(text()))), - "url" -> trim(label("URL" ,optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" ,optional(text()))), - "clearImage" -> trim(label("Clear image" ,boolean())), - "removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName")))) + "userName" -> trim(label("Username", text(required, maxlength(100), identifier))), + "password" -> trim(label("Password", optional(text(maxlength(20), password)))), + "fullName" -> trim(label("Full Name", text(required, maxlength(100)))), + "mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))), + "isAdmin" -> trim(label("User Type", boolean())), + "description" -> trim(label("bio", optional(text()))), + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))), + "clearImage" -> trim(label("Clear image", boolean())), + "removed" -> trim(label("Disable", boolean(disableByNotYourself("userName")))) )(EditUserForm.apply) val newGroupForm = mapping( - "groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), + "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))), "description" -> trim(label("Group description", optional(text()))), - "url" -> trim(label("URL" ,optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" ,optional(text()))), - "members" -> trim(label("Members" ,text(required, members))) + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))), + "members" -> trim(label("Members", text(required, members))) )(NewGroupForm.apply) val editGroupForm = mapping( - "groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))), + "groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))), "description" -> trim(label("Group description", optional(text()))), - "url" -> trim(label("URL" ,optional(text(maxlength(200))))), - "fileId" -> trim(label("File ID" ,optional(text()))), - "members" -> trim(label("Members" ,text(required, members))), - "clearImage" -> trim(label("Clear image" ,boolean())), - "removed" -> trim(label("Disable" ,boolean())) + "url" -> trim(label("URL", optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID", optional(text()))), + "members" -> trim(label("Members", text(required, members))), + "clearImage" -> trim(label("Clear image", boolean())), + "removed" -> trim(label("Disable", boolean())) )(EditGroupForm.apply) - get("/admin/dbviewer")(adminOnly { val conn = request2Session(request).conn val meta = conn.getMetaData val tables = ListBuffer[Table]() - using(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))){ rs => - while(rs.next()){ - val tableName = rs.getString("TABLE_NAME") + using(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) { + rs => + while (rs.next()) { + val tableName = rs.getString("TABLE_NAME") - val pkColumns = ListBuffer[String]() - using(meta.getPrimaryKeys(null, null, tableName)){ rs => - while(rs.next()){ - pkColumns += rs.getString("COLUMN_NAME").toUpperCase + val pkColumns = ListBuffer[String]() + using(meta.getPrimaryKeys(null, null, tableName)) { rs => + while (rs.next()) { + pkColumns += rs.getString("COLUMN_NAME").toUpperCase + } } - } - val columns = ListBuffer[Column]() - using(meta.getColumns(null, "%", tableName, "%")){ rs => - while(rs.next()){ - val columnName = rs.getString("COLUMN_NAME").toUpperCase - columns += Column(columnName, pkColumns.contains(columnName)) + val columns = ListBuffer[Column]() + using(meta.getColumns(null, "%", tableName, "%")) { rs => + while (rs.next()) { + val columnName = rs.getString("COLUMN_NAME").toUpperCase + columns += Column(columnName, pkColumns.contains(columnName)) + } } - } - tables += Table(tableName.toUpperCase, columns) - } + tables += Table(tableName.toUpperCase, columns) + } } html.dbviewer(tables) }) post("/admin/dbviewer/_query")(adminOnly { contentType = formats("json") - params.get("query").collectFirst { case query if query.trim.nonEmpty => - val trimmedQuery = query.trim - if(trimmedQuery.nonEmpty){ - try { - val conn = request2Session(request).conn - using(conn.prepareStatement(query)){ stmt => - if(trimmedQuery.toUpperCase.startsWith("SELECT")){ - using(stmt.executeQuery()){ rs => - val meta = rs.getMetaData - val columns = for(i <- 1 to meta.getColumnCount) yield { - meta.getColumnName(i) + params.get("query").collectFirst { + case query if query.trim.nonEmpty => + val trimmedQuery = query.trim + if (trimmedQuery.nonEmpty) { + try { + val conn = request2Session(request).conn + using(conn.prepareStatement(query)) { + stmt => + if (trimmedQuery.toUpperCase.startsWith("SELECT")) { + using(stmt.executeQuery()) { + rs => + val meta = rs.getMetaData + val columns = for (i <- 1 to meta.getColumnCount) yield { + meta.getColumnName(i) + } + val result = ListBuffer[Map[String, String]]() + while (rs.next()) { + val row = columns.map { columnName => + columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("") + }.toMap + result += row + } + Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result))) + } + } else { + val rows = stmt.executeUpdate() + Ok(Serialization.write(Map("type" -> "update", "rows" -> rows))) } - val result = ListBuffer[Map[String, String]]() - while(rs.next()){ - val row = columns.map { columnName => - columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("") - }.toMap - result += row - } - Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result))) - } - } else { - val rows = stmt.executeUpdate() - Ok(Serialization.write(Map("type" -> "update", "rows" -> rows))) } + } catch { + case e: Exception => + Ok(Serialization.write(Map("type" -> "error", "message" -> e.toString))) } - } catch { - case e: Exception => - Ok(Serialization.write(Map("type" -> "error", "message" -> e.toString))) } - } } getOrElse Ok(Serialization.write(Map("type" -> "error", "message" -> "query is empty"))) }) @@ -239,11 +281,10 @@ if (form.sshAddress != context.settings.sshAddress) { SshServer.stop() - for { - sshAddress <- form.sshAddress - baseUrl <- form.baseUrl - } - SshServer.start(sshAddress, baseUrl) + for { + sshAddress <- form.sshAddress + baseUrl <- form.baseUrl + } SshServer.start(sshAddress, baseUrl) } flash += "info" -> "System settings has been updated." @@ -253,10 +294,10 @@ post("/admin/system/sendmail", sendMailForm)(adminOnly { form => try { new Mailer(context.settings.copy(smtp = Some(form.smtp), notification = true)).send( - to = form.testAddress, - subject = "Test message from GitBucket", - textMsg = "This is a test message from GitBucket.", - htmlMsg = None, + to = form.testAddress, + subject = "Test message from GitBucket", + textMsg = "This is a test message from GitBucket.", + htmlMsg = None, loginAccount = context.loginAccount ) @@ -274,20 +315,27 @@ val gitbucketVersion = Semver.valueOf(GitBucketCoreModule.getVersions.asScala.last.getVersion) // Plugins in the local repository - val repositoryPlugins = PluginRepository.getPlugins() + val repositoryPlugins = PluginRepository + .getPlugins() .filterNot { meta => - enabledPlugins.exists { plugin => plugin.pluginId == meta.id && + enabledPlugins.exists { plugin => + plugin.pluginId == meta.id && Semver.valueOf(plugin.pluginVersion).greaterThanOrEqualTo(Semver.valueOf(meta.latestVersion.version)) } - }.map { meta => - (meta, meta.versions.reverse.find { version => gitbucketVersion.satisfies(version.range) }) - }.collect { case (meta, Some(version)) => - new PluginInfoBase( - pluginId = meta.id, - pluginName = meta.name, - pluginVersion = version.version, - description = meta.description - ) + } + .map { meta => + (meta, meta.versions.reverse.find { version => + gitbucketVersion.satisfies(version.range) + }) + } + .collect { + case (meta, Some(version)) => + new PluginInfoBase( + pluginId = meta.id, + pluginName = meta.name, + pluginVersion = version.version, + description = meta.description + ) } // Merge @@ -304,11 +352,13 @@ post("/admin/plugins/:pluginId/:version/_uninstall")(adminOnly { val pluginId = params("pluginId") - val version = params("version") - PluginRegistry().getPlugins() + val version = params("version") + PluginRegistry() + .getPlugins() .collect { case plugin if (plugin.pluginId == pluginId && plugin.pluginVersion == version) => plugin } .foreach { _ => - PluginRegistry.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn) + PluginRegistry + .uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn) flash += "info" -> s"${pluginId} was uninstalled." } redirect("/admin/plugins") @@ -316,32 +366,34 @@ post("/admin/plugins/:pluginId/:version/_install")(adminOnly { val pluginId = params("pluginId") - val version = params("version") + val version = params("version") /// TODO!!!! - PluginRepository.getPlugins() - .collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version) )} - .foreach { case (meta, version) => - version.foreach { version => - // TODO Install version! - PluginRegistry.install( - new java.io.File(PluginHome, s".repository/${version.file}"), - request.getServletContext, - loadSystemSettings(), - request2Session(request).conn - ) - flash += "info" -> s"${pluginId} was installed." - } + PluginRepository + .getPlugins() + .collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version)) } + .foreach { + case (meta, version) => + version.foreach { version => + // TODO Install version! + PluginRegistry.install( + new java.io.File(PluginHome, s".repository/${version.file}"), + request.getServletContext, + loadSystemSettings(), + request2Session(request).conn + ) + flash += "info" -> s"${pluginId} was installed." + } } redirect("/admin/plugins") }) - get("/admin/users")(adminOnly { val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false) val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false) - val users = getAllUsers(includeRemoved, includeGroups) - val members = users.collect { case account if(account.isGroupAccount) => - account.userName -> getGroupMembers(account.userName).map(_.userName) + val users = getAllUsers(includeRemoved, includeGroups) + val members = users.collect { + case account if (account.isGroupAccount) => + account.userName -> getGroupMembers(account.userName).map(_.userName) }.toMap html.userlist(users, members, includeRemoved, includeGroups) @@ -352,7 +404,15 @@ }) post("/admin/users/_newuser", newUserForm)(adminOnly { form => - createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.description, form.url) + createAccount( + form.userName, + sha1(form.password), + form.fullName, + form.mailAddress, + form.isAdmin, + form.description, + form.url + ) updateImage(form.userName, form.fileId, false) redirect("/admin/users") }) @@ -364,39 +424,43 @@ post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form => val userName = params("userName") - getAccountByUserName(userName, true).map { account => - if(account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)){ - flash += "error" -> "Account can't be turned off because this is last one administrator." - redirect(s"/admin/users/${userName}/_edituser") - } else { - if(form.isRemoved){ - // Remove repositories - // getRepositoryNamesOfUser(userName).foreach { repositoryName => - // deleteRepository(userName, repositoryName) - // FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName)) - // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) - // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) - // } - // Remove from GROUP_MEMBER and COLLABORATOR - removeUserRelatedData(userName) + getAccountByUserName(userName, true).map { + account => + if (account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)) { + flash += "error" -> "Account can't be turned off because this is last one administrator." + redirect(s"/admin/users/${userName}/_edituser") + } else { + if (form.isRemoved) { + // Remove repositories + // getRepositoryNamesOfUser(userName).foreach { repositoryName => + // deleteRepository(userName, repositoryName) + // FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName)) + // FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName)) + // FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName)) + // } + // Remove from GROUP_MEMBER and COLLABORATOR + removeUserRelatedData(userName) + } + + updateAccount( + account.copy( + password = form.password.map(sha1).getOrElse(account.password), + fullName = form.fullName, + mailAddress = form.mailAddress, + isAdmin = form.isAdmin, + description = form.description, + url = form.url, + isRemoved = form.isRemoved + ) + ) + + updateImage(userName, form.fileId, form.clearImage) + + // call hooks + if (form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName)) + + redirect("/admin/users") } - - updateAccount(account.copy( - password = form.password.map(sha1).getOrElse(account.password), - fullName = form.fullName, - mailAddress = form.mailAddress, - isAdmin = form.isAdmin, - description = form.description, - url = form.url, - isRemoved = form.isRemoved)) - - updateImage(userName, form.fileId, form.clearImage) - - // call hooks - if(form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName)) - - redirect("/admin/users") - } } getOrElse NotFound() }) @@ -406,33 +470,47 @@ post("/admin/users/_newgroup", newGroupForm)(adminOnly { form => createGroup(form.groupName, form.description, form.url) - updateGroupMembers(form.groupName, form.members.split(",").map { - _.split(":") match { - case Array(userName, isManager) => (userName, isManager.toBoolean) - } - }.toList) + updateGroupMembers( + form.groupName, + form.members + .split(",") + .map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + } + .toList + ) updateImage(form.groupName, form.fileId, false) redirect("/admin/users") }) get("/admin/users/:groupName/_editgroup")(adminOnly { - defining(params("groupName")){ groupName => + defining(params("groupName")) { groupName => html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName)) } }) post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form => - defining(params("groupName"), form.members.split(",").map { - _.split(":") match { - case Array(userName, isManager) => (userName, isManager.toBoolean) - } - }.toList){ case (groupName, members) => - getAccountByUserName(groupName, true).map { account => - updateGroup(groupName, form.description, form.url, form.isRemoved) + defining( + params("groupName"), + form.members + .split(",") + .map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + } + .toList + ) { + case (groupName, members) => + getAccountByUserName(groupName, true).map { + account => + updateGroup(groupName, form.description, form.url, form.isRemoved) - if(form.isRemoved){ - // Remove from GROUP_MEMBER - updateGroupMembers(form.groupName, Nil) + if (form.isRemoved) { + // Remove from GROUP_MEMBER + updateGroupMembers(form.groupName, Nil) // // Remove repositories // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // deleteRepository(groupName, repositoryName) @@ -440,9 +518,9 @@ // FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName)) // FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName)) // } - } else { - // Update GROUP_MEMBER - updateGroupMembers(form.groupName, members) + } else { + // Update GROUP_MEMBER + updateGroupMembers(form.groupName, members) // // Update COLLABORATOR for group repositories // getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => // removeCollaborators(form.groupName, repositoryName) @@ -450,12 +528,12 @@ // addCollaborator(form.groupName, repositoryName, userName) // } // } - } + } - updateImage(form.groupName, form.fileId, form.clearImage) - redirect("/admin/users") + updateImage(form.groupName, form.fileId, form.clearImage) + redirect("/admin/users") - } getOrElse NotFound() + } getOrElse NotFound() } }) @@ -473,25 +551,26 @@ response.setHeader("Content-Disposition", "attachment; filename=" + file.getName) response.setContentLength(file.length.toInt) - using(new FileInputStream(file)){ in => + using(new FileInputStream(file)) { in => IOUtils.copy(in, response.outputStream) } () }) - private def members: Constraint = new Constraint(){ + private def members: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = { - if(value.split(",").exists { - _.split(":") match { case Array(userName, isManager) => isManager.toBoolean } - }) None else Some("Must select one manager at least.") + if (value.split(",").exists { + _.split(":") match { case Array(userName, isManager) => isManager.toBoolean } + }) None + else Some("Must select one manager at least.") } } protected def disableByNotYourself(paramName: String): Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = { params.get(paramName).flatMap { userName => - if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true")) + if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true")) Some("You can't disable your account yourself") else None diff --git a/src/main/scala/gitbucket/core/controller/ValidationSupport.scala b/src/main/scala/gitbucket/core/controller/ValidationSupport.scala index 9c3e02b..8e403f6 100644 --- a/src/main/scala/gitbucket/core/controller/ValidationSupport.scala +++ b/src/main/scala/gitbucket/core/controller/ValidationSupport.scala @@ -14,58 +14,58 @@ def get[T](path: String, form: ValueType[T])(action: T => Any): Route = { registerValidate(path, form) - get(path){ + get(path) { validate(form)(errors => BadRequest(), form => action(form)) } } def post[T](path: String, form: ValueType[T])(action: T => Any): Route = { registerValidate(path, form) - post(path){ + post(path) { validate(form)(errors => BadRequest(), form => action(form)) } } def put[T](path: String, form: ValueType[T])(action: T => Any): Route = { registerValidate(path, form) - put(path){ + put(path) { validate(form)(errors => BadRequest(), form => action(form)) } } def delete[T](path: String, form: ValueType[T])(action: T => Any): Route = { registerValidate(path, form) - delete(path){ + delete(path) { validate(form)(errors => BadRequest(), form => action(form)) } } def ajaxGet[T](path: String, form: ValueType[T])(action: T => Any): Route = { - get(path){ + get(path) { validate(form)(errors => ajaxError(errors), form => action(form)) } } def ajaxPost[T](path: String, form: ValueType[T])(action: T => Any): Route = { - post(path){ + post(path) { validate(form)(errors => ajaxError(errors), form => action(form)) } } def ajaxDelete[T](path: String, form: ValueType[T])(action: T => Any): Route = { - delete(path){ + delete(path) { validate(form)(errors => ajaxError(errors), form => action(form)) } } def ajaxPut[T](path: String, form: ValueType[T])(action: T => Any): Route = { - put(path){ + put(path) { validate(form)(errors => ajaxError(errors), form => action(form)) } } private def registerValidate[T](path: String, form: ValueType[T]) = { - post(path.replaceFirst("/$", "") + "/validate"){ + post(path.replaceFirst("/$", "") + "/validate") { contentType = "application/json" toJson(form.validate("", multiParams, messages)) } @@ -84,8 +84,9 @@ * Converts errors to JSON. */ private def toJson(errors: Seq[(String, String)]): JObject = - JObject(errors.map { case (key, value) => - JField(key, JString(value)) + JObject(errors.map { + case (key, value) => + JField(key, JString(value)) }.toList) } diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index 5349fb9..2298971 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -14,38 +14,60 @@ import org.eclipse.jgit.api.Git import org.scalatra.i18n.Messages -class WikiController extends WikiControllerBase - with WikiService with RepositoryService with AccountService with ActivityService with WebHookService - with ReadableUsersAuthenticator with ReferrerAuthenticator +class WikiController + extends WikiControllerBase + with WikiService + with RepositoryService + with AccountService + with ActivityService + with WebHookService + with ReadableUsersAuthenticator + with ReferrerAuthenticator trait WikiControllerBase extends ControllerBase { - self: WikiService with RepositoryService with AccountService with ActivityService with WebHookService - with ReadableUsersAuthenticator with ReferrerAuthenticator => + self: WikiService + with RepositoryService + with AccountService + with ActivityService + with WebHookService + with ReadableUsersAuthenticator + with ReferrerAuthenticator => - case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String) + case class WikiPageEditForm( + pageName: String, + content: String, + message: Option[String], + currentPageName: String, + id: String + ) val newForm = mapping( - "pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename, unique))), - "content" -> trim(label("Content" , text(required, conflictForNew))), - "message" -> trim(label("Message" , optional(text()))), - "currentPageName" -> trim(label("Current page name" , text())), - "id" -> trim(label("Latest commit id" , text())) + "pageName" -> trim(label("Page name", text(required, maxlength(40), pagename, unique))), + "content" -> trim(label("Content", text(required, conflictForNew))), + "message" -> trim(label("Message", optional(text()))), + "currentPageName" -> trim(label("Current page name", text())), + "id" -> trim(label("Latest commit id", text())) )(WikiPageEditForm.apply) val editForm = mapping( - "pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename))), - "content" -> trim(label("Content" , text(required, conflictForEdit))), - "message" -> trim(label("Message" , optional(text()))), - "currentPageName" -> trim(label("Current page name" , text(required))), - "id" -> trim(label("Latest commit id" , text(required))) + "pageName" -> trim(label("Page name", text(required, maxlength(40), pagename))), + "content" -> trim(label("Content", text(required, conflictForEdit))), + "message" -> trim(label("Message", optional(text()))), + "currentPageName" -> trim(label("Current page name", text(required))), + "id" -> trim(label("Latest commit id", text(required))) )(WikiPageEditForm.apply) get("/:owner/:repository/wiki")(referrersOnly { repository => getWikiPage(repository.owner, repository.name, "Home").map { page => - html.page("Home", page, getWikiPageList(repository.owner, repository.name), - repository, isEditable(repository), + html.page( + "Home", + page, + getWikiPageList(repository.owner, repository.name), + repository, + isEditable(repository), getWikiPage(repository.owner, repository.name, "_Sidebar"), - getWikiPage(repository.owner, repository.name, "_Footer")) + getWikiPage(repository.owner, repository.name, "_Footer") + ) } getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit") }) @@ -53,20 +75,25 @@ val pageName = StringUtil.urlDecode(params("page")) getWikiPage(repository.owner, repository.name, pageName).map { page => - html.page(pageName, page, getWikiPageList(repository.owner, repository.name), - repository, isEditable(repository), + html.page( + pageName, + page, + getWikiPageList(repository.owner, repository.name), + repository, + isEditable(repository), getWikiPage(repository.owner, repository.name, "_Sidebar"), - getWikiPage(repository.owner, repository.name, "_Footer")) + getWikiPage(repository.owner, repository.name, "_Footer") + ) } getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit") }) get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository => val pageName = StringUtil.urlDecode(params("page")) - using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git => JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match { case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository)) - case Left(_) => NotFound() + case Left(_) => NotFound() } } }) @@ -75,40 +102,56 @@ val pageName = StringUtil.urlDecode(params("page")) val Array(from, to) = params("commitId").split("\\.\\.\\.") - using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => - html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, Some(from), to, true, false).filter(_.newPath == pageName + ".md"), repository, - isEditable(repository), flash.get("info")) + using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git => + html.compare( + Some(pageName), + from, + to, + JGitUtil.getDiffs(git, Some(from), to, true, false).filter(_.newPath == pageName + ".md"), + repository, + isEditable(repository), + flash.get("info") + ) } }) get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository => val Array(from, to) = params("commitId").split("\\.\\.\\.") - using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => - html.compare(None, from, to, JGitUtil.getDiffs(git, Some(from), to, true, false), repository, - isEditable(repository), flash.get("info")) + using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git => + html.compare( + None, + from, + to, + JGitUtil.getDiffs(git, Some(from), to, true, false), + repository, + isEditable(repository), + flash.get("info") + ) } }) get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository => - if(isEditable(repository)){ + if (isEditable(repository)) { val pageName = StringUtil.urlDecode(params("page")) val Array(from, to) = params("commitId").split("\\.\\.\\.") - if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))){ + if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))) { redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}") } else { flash += "info" -> "This patch was not able to be reversed." - redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}") + redirect( + s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}" + ) } } else Unauthorized() }) get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository => - if(isEditable(repository)){ + if (isEditable(repository)) { val Array(from, to) = params("commitId").split("\\.\\.\\.") - if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){ + if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)) { redirect(s"/${repository.owner}/${repository.name}/wiki") } else { flash += "info" -> "This patch was not able to be reversed." @@ -118,85 +161,102 @@ }) get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository => - if(isEditable(repository)){ + if (isEditable(repository)) { val pageName = StringUtil.urlDecode(params("page")) html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository) } else Unauthorized() }) post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) => - if(isEditable(repository)){ - defining(context.loginAccount.get){ loginAccount => - saveWikiPage( - repository.owner, - repository.name, - form.currentPageName, - form.pageName, - appendNewLine(convertLineSeparator(form.content, "LF"), "LF"), - loginAccount, - form.message.getOrElse(""), - Some(form.id) - ).map { commitId => - updateLastActivityDate(repository.owner, repository.name) - recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId) - callWebHookOf(repository.owner, repository.name, WebHook.Gollum){ - getAccountByUserName(repository.owner).map { repositoryUser => - WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount) - } + if (isEditable(repository)) { + defining(context.loginAccount.get) { + loginAccount => + saveWikiPage( + repository.owner, + repository.name, + form.currentPageName, + form.pageName, + appendNewLine(convertLineSeparator(form.content, "LF"), "LF"), + loginAccount, + form.message.getOrElse(""), + Some(form.id) + ).map { + commitId => + updateLastActivityDate(repository.owner, repository.name) + recordEditWikiPageActivity( + repository.owner, + repository.name, + loginAccount.userName, + form.pageName, + commitId + ) + callWebHookOf(repository.owner, repository.name, WebHook.Gollum) { + getAccountByUserName(repository.owner).map { repositoryUser => + WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount) + } + } } - } - if(notReservedPageName(form.pageName)) { - redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") - } else { - redirect(s"/${repository.owner}/${repository.name}/wiki") - } + if (notReservedPageName(form.pageName)) { + redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") + } else { + redirect(s"/${repository.owner}/${repository.name}/wiki") + } } } else Unauthorized() }) get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository => - if(isEditable(repository)){ + if (isEditable(repository)) { html.edit("", None, repository) } else Unauthorized() }) post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) => - if(isEditable(repository)){ - defining(context.loginAccount.get){ loginAccount => - saveWikiPage( - repository.owner, - repository.name, - form.currentPageName, - form.pageName, - form.content, - loginAccount, - form.message.getOrElse(""), - None - ).map { commitId => - updateLastActivityDate(repository.owner, repository.name) - recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName) - callWebHookOf(repository.owner, repository.name, WebHook.Gollum){ - getAccountByUserName(repository.owner).map { repositoryUser => - WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount) - } + if (isEditable(repository)) { + defining(context.loginAccount.get) { + loginAccount => + saveWikiPage( + repository.owner, + repository.name, + form.currentPageName, + form.pageName, + form.content, + loginAccount, + form.message.getOrElse(""), + None + ).map { + commitId => + updateLastActivityDate(repository.owner, repository.name) + recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName) + callWebHookOf(repository.owner, repository.name, WebHook.Gollum) { + getAccountByUserName(repository.owner).map { repositoryUser => + WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount) + } + } } - } - if(notReservedPageName(form.pageName)) { - redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") - } else { - redirect(s"/${repository.owner}/${repository.name}/wiki") - } + if (notReservedPageName(form.pageName)) { + redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}") + } else { + redirect(s"/${repository.owner}/${repository.name}/wiki") + } } } else Unauthorized() }) get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository => - if(isEditable(repository)){ + if (isEditable(repository)) { val pageName = StringUtil.urlDecode(params("page")) - defining(context.loginAccount.get){ loginAccount => - deleteWikiPage(repository.owner, repository.name, pageName, loginAccount.fullName, loginAccount.mailAddress, s"Destroyed ${pageName}") + defining(context.loginAccount.get) { loginAccount => + deleteWikiPage( + repository.owner, + repository.name, + pageName, + loginAccount.fullName, + loginAccount.mailAddress, + s"Destroyed ${pageName}" + ) updateLastActivityDate(repository.owner, repository.name) redirect(s"/${repository.owner}/${repository.name}/wiki") @@ -209,17 +269,17 @@ }) get("/:owner/:repository/wiki/_history")(referrersOnly { repository => - using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git => JGitUtil.getCommitLog(git, "master") match { case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository)) - case Left(_) => NotFound() + case Left(_) => NotFound() } } }) get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository => val path = multiParams("splat").head - using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => + using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git => val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master")) getPathObjectId(git, path, revCommit).map { objectId => @@ -228,25 +288,32 @@ } }) - private def unique: Constraint = new Constraint(){ - override def validate(name: String, value: String, params: Map[String, Seq[String]], messages: Messages): Option[String] = - getWikiPageList(params.value("owner"), params.value("repository")).find(_ == value).map(_ => "Page already exists.") + private def unique: Constraint = new Constraint() { + override def validate( + name: String, + value: String, + params: Map[String, Seq[String]], + messages: Messages + ): Option[String] = + getWikiPageList(params.value("owner"), params.value("repository")) + .find(_ == value) + .map(_ => "Page already exists.") } - private def pagename: Constraint = new Constraint(){ + private def pagename: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(value.exists("\\/:*?\"<>|".contains(_))){ + if (value.exists("\\/:*?\"<>|".contains(_))) { Some(s"${name} contains invalid character.") - } else if(notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))){ + } else if (notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))) { Some(s"${name} starts with invalid character.") } else { None } } - private def notReservedPageName(value: String) = ! (Array[String]("_Sidebar","_Footer") contains value) + private def notReservedPageName(value: String) = !(Array[String]("_Sidebar", "_Footer") contains value) - private def conflictForNew: Constraint = new Constraint(){ + private def conflictForNew: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = { targetWikiPage.map { _ => "Someone has created the wiki since you started. Please reload this page and re-apply your changes." @@ -254,9 +321,9 @@ } } - private def conflictForEdit: Constraint = new Constraint(){ + private def conflictForEdit: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = { - targetWikiPage.filter(_.id != params("id")).map{ _ => + targetWikiPage.filter(_.id != params("id")).map { _ => "Someone has edited the wiki since you started. Please reload this page and re-apply your changes." } } diff --git a/src/main/scala/gitbucket/core/model/AccessToken.scala b/src/main/scala/gitbucket/core/model/AccessToken.scala index a460b90..c952c03 100644 --- a/src/main/scala/gitbucket/core/model/AccessToken.scala +++ b/src/main/scala/gitbucket/core/model/AccessToken.scala @@ -1,6 +1,5 @@ package gitbucket.core.model - trait AccessTokenComponent { self: Profile => import profile.api._ diff --git a/src/main/scala/gitbucket/core/model/Account.scala b/src/main/scala/gitbucket/core/model/Account.scala index 1f4931e..2379328 100644 --- a/src/main/scala/gitbucket/core/model/Account.scala +++ b/src/main/scala/gitbucket/core/model/Account.scala @@ -20,7 +20,22 @@ val groupAccount = column[Boolean]("GROUP_ACCOUNT") val removed = column[Boolean]("REMOVED") val description = column[String]("DESCRIPTION") - def * = (userName, fullName, mailAddress, password, isAdmin, url.?, registeredDate, updatedDate, lastLoginDate.?, image.?, groupAccount, removed, description.?) <> (Account.tupled, Account.unapply) + def * = + ( + userName, + fullName, + mailAddress, + password, + isAdmin, + url.?, + registeredDate, + updatedDate, + lastLoginDate.?, + image.?, + groupAccount, + removed, + description.? + ) <> (Account.tupled, Account.unapply) } } diff --git a/src/main/scala/gitbucket/core/model/AccountWebHook.scala b/src/main/scala/gitbucket/core/model/AccountWebHook.scala index df28993..79f0c4b 100644 --- a/src/main/scala/gitbucket/core/model/AccountWebHook.scala +++ b/src/main/scala/gitbucket/core/model/AccountWebHook.scala @@ -3,7 +3,8 @@ trait AccountWebHookComponent extends TemplateComponent { self: Profile => import profile.api._ - private implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code)) + private implicit val whContentTypeColumnType = + MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code)) lazy val AccountWebHooks = TableQuery[AccountWebHooks] diff --git a/src/main/scala/gitbucket/core/model/AccountWebHookEvent.scala b/src/main/scala/gitbucket/core/model/AccountWebHookEvent.scala index 36ffa3c..f169a36 100644 --- a/src/main/scala/gitbucket/core/model/AccountWebHookEvent.scala +++ b/src/main/scala/gitbucket/core/model/AccountWebHookEvent.scala @@ -8,11 +8,13 @@ lazy val AccountWebHookEvents = TableQuery[AccountWebHookEvents] - class AccountWebHookEvents(tag: Tag) extends Table[AccountWebHookEvent](tag, "ACCOUNT_WEB_HOOK_EVENT") with BasicTemplate { + class AccountWebHookEvents(tag: Tag) + extends Table[AccountWebHookEvent](tag, "ACCOUNT_WEB_HOOK_EVENT") + with BasicTemplate { val url = column[String]("URL") val event = column[WebHook.Event]("EVENT") - def * = (userName, url, event) <> ((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply) + def * = (userName, url, event) <> ((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply) def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind) @@ -28,7 +30,7 @@ } case class AccountWebHookEvent( - userName: String, - url: String, - event: WebHook.Event - ) + userName: String, + url: String, + event: WebHook.Event +) diff --git a/src/main/scala/gitbucket/core/model/Activity.scala b/src/main/scala/gitbucket/core/model/Activity.scala index 8c5b8bd..35c990e 100644 --- a/src/main/scala/gitbucket/core/model/Activity.scala +++ b/src/main/scala/gitbucket/core/model/Activity.scala @@ -13,7 +13,8 @@ val message = column[String]("MESSAGE") val additionalInfo = column[String]("ADDITIONAL_INFO") val activityDate = column[java.util.Date]("ACTIVITY_DATE") - def * = (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply) + def * = + (userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply) } } diff --git a/src/main/scala/gitbucket/core/model/BasicTemplate.scala b/src/main/scala/gitbucket/core/model/BasicTemplate.scala index 5608bbd..7beaf9b 100644 --- a/src/main/scala/gitbucket/core/model/BasicTemplate.scala +++ b/src/main/scala/gitbucket/core/model/BasicTemplate.scala @@ -76,9 +76,11 @@ byRepository(userName, repositoryName) && (this.commitId === commitId) } - trait BranchTemplate extends BasicTemplate{ self: Table[_] => + trait BranchTemplate extends BasicTemplate { self: Table[_] => val branch = column[String]("BRANCH") - def byBranch(owner: String, repository: String, branchName: String) = byRepository(owner, repository) && (branch === branchName.bind) - def byBranch(owner: Rep[String], repository: Rep[String], branchName: Rep[String]) = byRepository(owner, repository) && (this.branch === branchName) + def byBranch(owner: String, repository: String, branchName: String) = + byRepository(owner, repository) && (branch === branchName.bind) + def byBranch(owner: Rep[String], repository: Rep[String], branchName: Rep[String]) = + byRepository(owner, repository) && (this.branch === branchName) } } diff --git a/src/main/scala/gitbucket/core/model/Comment.scala b/src/main/scala/gitbucket/core/model/Comment.scala index 310be7e..8f79a52 100644 --- a/src/main/scala/gitbucket/core/model/Comment.scala +++ b/src/main/scala/gitbucket/core/model/Comment.scala @@ -18,13 +18,14 @@ val content = column[String]("CONTENT") val registeredDate = column[java.util.Date]("REGISTERED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE") - def * = (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply) + def * = + (userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate) <> (IssueComment.tupled, IssueComment.unapply) def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind } } -case class IssueComment ( +case class IssueComment( userName: String, repositoryName: String, issueId: Int, @@ -52,7 +53,21 @@ val registeredDate = column[java.util.Date]("REGISTERED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE") val issueId = column[Option[Int]]("ISSUE_ID") - def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, issueId) <> (CommitComment.tupled, CommitComment.unapply) + def * = + ( + userName, + repositoryName, + commitId, + commentId, + commentedUserName, + content, + fileName, + oldLine, + newLine, + registeredDate, + updatedDate, + issueId + ) <> (CommitComment.tupled, CommitComment.unapply) def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind } @@ -71,4 +86,4 @@ registeredDate: java.util.Date, updatedDate: java.util.Date, issueId: Option[Int] - ) extends Comment +) extends Comment diff --git a/src/main/scala/gitbucket/core/model/CommitStatus.scala b/src/main/scala/gitbucket/core/model/CommitStatus.scala index 4bb01e0..c89f4be 100644 --- a/src/main/scala/gitbucket/core/model/CommitStatus.scala +++ b/src/main/scala/gitbucket/core/model/CommitStatus.scala @@ -4,7 +4,7 @@ import profile.api._ import self._ - implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name , i => CommitState(i)) + implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i)) lazy val CommitStatuses = TableQuery[CommitStatuses] class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate { @@ -16,12 +16,24 @@ val creator = column[String]("CREATOR") val registeredDate = column[java.util.Date]("REGISTERED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE") - def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply) + def * = + ( + commitStatusId, + userName, + repositoryName, + commitId, + context, + state, + targetUrl, + description, + creator, + registeredDate, + updatedDate + ) <> ((CommitStatus.apply _).tupled, CommitStatus.unapply) def byPrimaryKey(id: Int) = commitStatusId === id.bind } } - case class CommitStatus( commitStatusId: Int = 0, userName: String, @@ -36,23 +48,24 @@ updatedDate: java.util.Date ) object CommitStatus { - def pending(owner: String, repository: String, context: String) = CommitStatus( - commitStatusId = 0, - userName = owner, - repositoryName = repository, - commitId = "", - context = context, - state = CommitState.PENDING, - targetUrl = None, - description = Some("Waiting for status to be reported"), - creator = "", - registeredDate = new java.util.Date(), - updatedDate = new java.util.Date()) + def pending(owner: String, repository: String, context: String) = + CommitStatus( + commitStatusId = 0, + userName = owner, + repositoryName = repository, + commitId = "", + context = context, + state = CommitState.PENDING, + targetUrl = None, + description = Some("Waiting for status to be reported"), + creator = "", + registeredDate = new java.util.Date(), + updatedDate = new java.util.Date() + ) } sealed abstract class CommitState(val name: String) - object CommitState { object ERROR extends CommitState("error") @@ -76,11 +89,11 @@ * success if the latest status for all contexts is success */ def combine(statuses: Set[CommitState]): CommitState = { - if(statuses.isEmpty){ + if (statuses.isEmpty) { PENDING - } else if(statuses.contains(CommitState.ERROR) || statuses.contains(CommitState.FAILURE)) { + } else if (statuses.contains(CommitState.ERROR) || statuses.contains(CommitState.FAILURE)) { FAILURE - } else if(statuses.contains(CommitState.PENDING)) { + } else if (statuses.contains(CommitState.PENDING)) { PENDING } else { SUCCESS @@ -88,4 +101,3 @@ } } - diff --git a/src/main/scala/gitbucket/core/model/DeployKey.scala b/src/main/scala/gitbucket/core/model/DeployKey.scala index 71b80a2..dd13785 100644 --- a/src/main/scala/gitbucket/core/model/DeployKey.scala +++ b/src/main/scala/gitbucket/core/model/DeployKey.scala @@ -10,7 +10,8 @@ val title = column[String]("TITLE") val publicKey = column[String]("PUBLIC_KEY") val allowWrite = column[Boolean]("ALLOW_WRITE") - def * = (userName, repositoryName, deployKeyId, title, publicKey, allowWrite) <> (DeployKey.tupled, DeployKey.unapply) + def * = + (userName, repositoryName, deployKeyId, title, publicKey, allowWrite) <> (DeployKey.tupled, DeployKey.unapply) def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) = (this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind) diff --git a/src/main/scala/gitbucket/core/model/Issue.scala b/src/main/scala/gitbucket/core/model/Issue.scala index 7167195..635e94f 100644 --- a/src/main/scala/gitbucket/core/model/Issue.scala +++ b/src/main/scala/gitbucket/core/model/Issue.scala @@ -13,13 +13,19 @@ def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) } - class IssueOutline(tag: Tag) extends Table[(String, String, Int, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") with IssueTemplate { + class IssueOutline(tag: Tag) + extends Table[(String, String, Int, Int, Int)](tag, "ISSUE_OUTLINE_VIEW") + with IssueTemplate { val commentCount = column[Int]("COMMENT_COUNT") val priority = column[Int]("PRIORITY") def * = (userName, repositoryName, issueId, commentCount, priority) } - class Issues(tag: Tag) extends Table[Issue](tag, "ISSUE") with IssueTemplate with MilestoneTemplate with PriorityTemplate { + class Issues(tag: Tag) + extends Table[Issue](tag, "ISSUE") + with IssueTemplate + with MilestoneTemplate + with PriorityTemplate { val openedUserName = column[String]("OPENED_USER_NAME") val assignedUserName = column[String]("ASSIGNED_USER_NAME") val title = column[String]("TITLE") @@ -28,7 +34,22 @@ val registeredDate = column[java.util.Date]("REGISTERED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE") val pullRequest = column[Boolean]("PULL_REQUEST") - def * = (userName, repositoryName, issueId, openedUserName, milestoneId.?, priorityId.?, assignedUserName.?, title, content.?, closed, registeredDate, updatedDate, pullRequest) <> (Issue.tupled, Issue.unapply) + def * = + ( + userName, + repositoryName, + issueId, + openedUserName, + milestoneId.?, + priorityId.?, + assignedUserName.?, + title, + content.?, + closed, + registeredDate, + updatedDate, + pullRequest + ) <> (Issue.tupled, Issue.unapply) def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId) } diff --git a/src/main/scala/gitbucket/core/model/Labels.scala b/src/main/scala/gitbucket/core/model/Labels.scala index a9abf8b..70f4ae4 100644 --- a/src/main/scala/gitbucket/core/model/Labels.scala +++ b/src/main/scala/gitbucket/core/model/Labels.scala @@ -12,23 +12,19 @@ def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply) def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId) - def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) = byLabel(userName, repositoryName, labelId) + def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) = + byLabel(userName, repositoryName, labelId) } } -case class Label( - userName: String, - repositoryName: String, - labelId: Int = 0, - labelName: String, - color: String){ +case class Label(userName: String, repositoryName: String, labelId: Int = 0, labelName: String, color: String) { val fontColor = { val r = color.substring(0, 2) val g = color.substring(2, 4) val b = color.substring(4, 6) - if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){ + if (Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408) { "000000" } else { "ffffff" diff --git a/src/main/scala/gitbucket/core/model/Milestone.scala b/src/main/scala/gitbucket/core/model/Milestone.scala index 491fefd..0074012 100644 --- a/src/main/scala/gitbucket/core/model/Milestone.scala +++ b/src/main/scala/gitbucket/core/model/Milestone.scala @@ -12,10 +12,12 @@ val description = column[Option[String]]("DESCRIPTION") val dueDate = column[Option[java.util.Date]]("DUE_DATE") val closedDate = column[Option[java.util.Date]]("CLOSED_DATE") - def * = (userName, repositoryName, milestoneId, title, description, dueDate, closedDate) <> (Milestone.tupled, Milestone.unapply) + def * = + (userName, repositoryName, milestoneId, title, description, dueDate, closedDate) <> (Milestone.tupled, Milestone.unapply) def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId) - def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) = byMilestone(userName, repositoryName, milestoneId) + def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) = + byMilestone(userName, repositoryName, milestoneId) } } diff --git a/src/main/scala/gitbucket/core/model/Priorities.scala b/src/main/scala/gitbucket/core/model/Priorities.scala index eb31740..548b32f 100644 --- a/src/main/scala/gitbucket/core/model/Priorities.scala +++ b/src/main/scala/gitbucket/core/model/Priorities.scala @@ -12,14 +12,16 @@ val ordering = column[Int]("ORDERING") val isDefault = column[Boolean]("IS_DEFAULT") val color = column[String]("COLOR") - def * = (userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color) <> (Priority.tupled, Priority.unapply) + def * = + (userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color) <> (Priority.tupled, Priority.unapply) def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId) - def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) = byPriority(userName, repositoryName, priorityId) + def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) = + byPriority(userName, repositoryName, priorityId) } } -case class Priority ( +case class Priority( userName: String, repositoryName: String, priorityId: Int = 0, @@ -27,14 +29,15 @@ description: Option[String], isDefault: Boolean, ordering: Int = 0, - color: String){ + color: String +) { val fontColor = { val r = color.substring(0, 2) val g = color.substring(2, 4) val b = color.substring(4, 6) - if(Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408){ + if (Integer.parseInt(r, 16) + Integer.parseInt(g, 16) + Integer.parseInt(b, 16) > 408) { "000000" } else { "ffffff" diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala index f9170a4..f35dec9 100644 --- a/src/main/scala/gitbucket/core/model/Profile.scala +++ b/src/main/scala/gitbucket/core/model/Profile.scala @@ -16,15 +16,15 @@ ) /** - * WebHookBase.Event Column Types - */ + * WebHookBase.Event Column Types + */ implicit val eventColumnType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_)) /** * Extends Column to add conditional condition */ - implicit class RichColumn(c1: Rep[Boolean]){ - def &&(c2: => Rep[Boolean], guard: => Boolean): Rep[Boolean] = if(guard) c1 && c2 else c1 + implicit class RichColumn(c1: Rep[Boolean]) { + def &&(c2: => Rep[Boolean], guard: => Boolean): Rep[Boolean] = if (guard) c1 && c2 else c1 } /** @@ -40,31 +40,33 @@ } -trait CoreProfile extends ProfileProvider with Profile - with AccessTokenComponent - with AccountComponent - with ActivityComponent - with CollaboratorComponent - with CommitCommentComponent - with CommitStatusComponent - with GroupMemberComponent - with IssueComponent - with IssueCommentComponent - with IssueLabelComponent - with LabelComponent - with PriorityComponent - with MilestoneComponent - with PullRequestComponent - with RepositoryComponent - with SshKeyComponent - with RepositoryWebHookComponent - with RepositoryWebHookEventComponent - with AccountWebHookComponent - with AccountWebHookEventComponent - with AccountFederationComponent - with ProtectedBranchComponent - with DeployKeyComponent - with ReleaseTagComponent - with ReleaseAssetComponent +trait CoreProfile + extends ProfileProvider + with Profile + with AccessTokenComponent + with AccountComponent + with ActivityComponent + with CollaboratorComponent + with CommitCommentComponent + with CommitStatusComponent + with GroupMemberComponent + with IssueComponent + with IssueCommentComponent + with IssueLabelComponent + with LabelComponent + with PriorityComponent + with MilestoneComponent + with PullRequestComponent + with RepositoryComponent + with SshKeyComponent + with RepositoryWebHookComponent + with RepositoryWebHookEventComponent + with AccountWebHookComponent + with AccountWebHookEventComponent + with AccountFederationComponent + with ProtectedBranchComponent + with DeployKeyComponent + with ReleaseTagComponent + with ReleaseAssetComponent object Profile extends CoreProfile diff --git a/src/main/scala/gitbucket/core/model/ProtectedBranch.scala b/src/main/scala/gitbucket/core/model/ProtectedBranch.scala index b143b4f..b101081 100644 --- a/src/main/scala/gitbucket/core/model/ProtectedBranch.scala +++ b/src/main/scala/gitbucket/core/model/ProtectedBranch.scala @@ -8,27 +8,22 @@ class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate { val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN") def * = (userName, repositoryName, branch, statusCheckAdmin) <> (ProtectedBranch.tupled, ProtectedBranch.unapply) - def byPrimaryKey(userName: String, repositoryName: String, branch: String) = byBranch(userName, repositoryName, branch) - def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) = byBranch(userName, repositoryName, branch) + def byPrimaryKey(userName: String, repositoryName: String, branch: String) = + byBranch(userName, repositoryName, branch) + def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) = + byBranch(userName, repositoryName, branch) } lazy val ProtectedBranchContexts = TableQuery[ProtectedBranchContexts] - class ProtectedBranchContexts(tag: Tag) extends Table[ProtectedBranchContext](tag, "PROTECTED_BRANCH_REQUIRE_CONTEXT") with BranchTemplate { + class ProtectedBranchContexts(tag: Tag) + extends Table[ProtectedBranchContext](tag, "PROTECTED_BRANCH_REQUIRE_CONTEXT") + with BranchTemplate { val context = column[String]("CONTEXT") - def * = (userName, repositoryName, branch, context) <> (ProtectedBranchContext.tupled, ProtectedBranchContext.unapply) + def * = + (userName, repositoryName, branch, context) <> (ProtectedBranchContext.tupled, ProtectedBranchContext.unapply) } } +case class ProtectedBranch(userName: String, repositoryName: String, branch: String, statusCheckAdmin: Boolean) -case class ProtectedBranch( - userName: String, - repositoryName: String, - branch: String, - statusCheckAdmin: Boolean) - - -case class ProtectedBranchContext( - userName: String, - repositoryName: String, - branch: String, - context: String) +case class ProtectedBranchContext(userName: String, repositoryName: String, branch: String, context: String) diff --git a/src/main/scala/gitbucket/core/model/PullRequest.scala b/src/main/scala/gitbucket/core/model/PullRequest.scala index dba1a3f..5c37691 100644 --- a/src/main/scala/gitbucket/core/model/PullRequest.scala +++ b/src/main/scala/gitbucket/core/model/PullRequest.scala @@ -12,10 +12,23 @@ val requestBranch = column[String]("REQUEST_BRANCH") val commitIdFrom = column[String]("COMMIT_ID_FROM") val commitIdTo = column[String]("COMMIT_ID_TO") - def * = (userName, repositoryName, issueId, branch, requestUserName, requestRepositoryName, requestBranch, commitIdFrom, commitIdTo) <> (PullRequest.tupled, PullRequest.unapply) + def * = + ( + userName, + repositoryName, + issueId, + branch, + requestUserName, + requestRepositoryName, + requestBranch, + commitIdFrom, + commitIdTo + ) <> (PullRequest.tupled, PullRequest.unapply) - def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = byIssue(userName, repositoryName, issueId) - def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], issueId: Rep[Int]) = byIssue(userName, repositoryName, issueId) + def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) = + byIssue(userName, repositoryName, issueId) + def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], issueId: Rep[Int]) = + byIssue(userName, repositoryName, issueId) } } diff --git a/src/main/scala/gitbucket/core/model/ReleaseAsset.scala b/src/main/scala/gitbucket/core/model/ReleaseAsset.scala index 7bd6697..3bab8ce 100644 --- a/src/main/scala/gitbucket/core/model/ReleaseAsset.scala +++ b/src/main/scala/gitbucket/core/model/ReleaseAsset.scala @@ -20,9 +20,12 @@ val registeredDate = column[Date]("REGISTERED_DATE") val updatedDate = column[Date]("UPDATED_DATE") - def * = (userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate) <> (ReleaseAsset.tupled, ReleaseAsset.unapply) - def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) = byTag(owner, repository, tag) && (this.fileName === fileName.bind) - def byTag(owner: String, repository: String, tag: String) = byRepository(owner, repository) && (this.tag === tag.bind) + def * = + (userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate) <> (ReleaseAsset.tupled, ReleaseAsset.unapply) + def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) = + byTag(owner, repository, tag) && (this.fileName === fileName.bind) + def byTag(owner: String, repository: String, tag: String) = + byRepository(owner, repository) && (this.tag === tag.bind) } } diff --git a/src/main/scala/gitbucket/core/model/ReleaseTag.scala b/src/main/scala/gitbucket/core/model/ReleaseTag.scala index d2c92a2..dad2e8b 100644 --- a/src/main/scala/gitbucket/core/model/ReleaseTag.scala +++ b/src/main/scala/gitbucket/core/model/ReleaseTag.scala @@ -16,9 +16,11 @@ val registeredDate = column[java.util.Date]("REGISTERED_DATE") val updatedDate = column[java.util.Date]("UPDATED_DATE") - def * = (userName, repositoryName, name, tag, author, content, registeredDate, updatedDate) <> (ReleaseTag.tupled, ReleaseTag.unapply) + def * = + (userName, repositoryName, name, tag, author, content, registeredDate, updatedDate) <> (ReleaseTag.tupled, ReleaseTag.unapply) def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag) - def byTag(owner: String, repository: String, tag: String) = byRepository(owner, repository) && (this.tag === tag.bind) + def byTag(owner: String, repository: String, tag: String) = + byRepository(owner, repository) && (this.tag === tag.bind) } } diff --git a/src/main/scala/gitbucket/core/model/Repository.scala b/src/main/scala/gitbucket/core/model/Repository.scala index 5a24c35..a62047d 100644 --- a/src/main/scala/gitbucket/core/model/Repository.scala +++ b/src/main/scala/gitbucket/core/model/Repository.scala @@ -7,62 +7,80 @@ lazy val Repositories = TableQuery[Repositories] class Repositories(tag: Tag) extends Table[Repository](tag, "REPOSITORY") with BasicTemplate { - val isPrivate = column[Boolean]("PRIVATE") - val description = column[String]("DESCRIPTION") - val defaultBranch = column[String]("DEFAULT_BRANCH") - val registeredDate = column[java.util.Date]("REGISTERED_DATE") - val updatedDate = column[java.util.Date]("UPDATED_DATE") - val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") - val originUserName = column[String]("ORIGIN_USER_NAME") + val isPrivate = column[Boolean]("PRIVATE") + val description = column[String]("DESCRIPTION") + val defaultBranch = column[String]("DEFAULT_BRANCH") + val registeredDate = column[java.util.Date]("REGISTERED_DATE") + val updatedDate = column[java.util.Date]("UPDATED_DATE") + val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") + val originUserName = column[String]("ORIGIN_USER_NAME") val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME") - val parentUserName = column[String]("PARENT_USER_NAME") + val parentUserName = column[String]("PARENT_USER_NAME") val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME") - val issuesOption = column[String]("ISSUES_OPTION") - val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL") - val wikiOption = column[String]("WIKI_OPTION") - val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL") - val allowFork = column[Boolean]("ALLOW_FORK") - val mergeOptions = column[String]("MERGE_OPTIONS") - val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION") + val issuesOption = column[String]("ISSUES_OPTION") + val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL") + val wikiOption = column[String]("WIKI_OPTION") + val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL") + val allowFork = column[Boolean]("ALLOW_FORK") + val mergeOptions = column[String]("MERGE_OPTIONS") + val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION") - def * = ( - (userName, repositoryName, isPrivate, description.?, defaultBranch, - registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?), - (issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork, mergeOptions, defaultMergeOption) - ).shaped <> ( - { case (repository, options) => - Repository( - repository._1, - repository._2, - repository._3, - repository._4, - repository._5, - repository._6, - repository._7, - repository._8, - repository._9, - repository._10, - repository._11, - repository._12, - RepositoryOptions.tupled.apply(options) - ) + def * = + ( + ( + userName, + repositoryName, + isPrivate, + description.?, + defaultBranch, + registeredDate, + updatedDate, + lastActivityDate, + originUserName.?, + originRepositoryName.?, + parentUserName.?, + parentRepositoryName.? + ), + (issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork, mergeOptions, defaultMergeOption) + ).shaped <> ({ + case (repository, options) => + Repository( + repository._1, + repository._2, + repository._3, + repository._4, + repository._5, + repository._6, + repository._7, + repository._8, + repository._9, + repository._10, + repository._11, + repository._12, + RepositoryOptions.tupled.apply(options) + ) }, { (r: Repository) => - Some((( - r.userName, - r.repositoryName, - r.isPrivate, - r.description, - r.defaultBranch, - r.registeredDate, - r.updatedDate, - r.lastActivityDate, - r.originUserName, - r.originRepositoryName, - r.parentUserName, - r.parentRepositoryName - ),( - RepositoryOptions.unapply(r.options).get - ))) + Some( + ( + ( + r.userName, + r.repositoryName, + r.isPrivate, + r.description, + r.defaultBranch, + r.registeredDate, + r.updatedDate, + r.lastActivityDate, + r.originUserName, + r.originRepositoryName, + r.parentUserName, + r.parentRepositoryName + ), + ( + RepositoryOptions.unapply(r.options).get + ) + ) + ) }) def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) diff --git a/src/main/scala/gitbucket/core/model/RepositoryWebHook.scala b/src/main/scala/gitbucket/core/model/RepositoryWebHook.scala index 967d067..15ea535 100644 --- a/src/main/scala/gitbucket/core/model/RepositoryWebHook.scala +++ b/src/main/scala/gitbucket/core/model/RepositoryWebHook.scala @@ -3,7 +3,8 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile => import profile.api._ - implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code)) + implicit val whContentTypeColumnType = + MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code)) lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks] @@ -11,13 +12,14 @@ val url = column[String]("URL") val token = column[Option[String]]("TOKEN") val ctype = column[WebHookContentType]("CTYPE") - def * = (userName, repositoryName, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply) + def * = + (userName, repositoryName, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply) - def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) + def byPrimaryKey(owner: String, repository: String, url: String) = + byRepository(owner, repository) && (this.url === url.bind) } } - case class RepositoryWebHook( userName: String, repositoryName: String, diff --git a/src/main/scala/gitbucket/core/model/RepositoryWebHookEvent.scala b/src/main/scala/gitbucket/core/model/RepositoryWebHookEvent.scala index 83cbea5..79ca9a9 100644 --- a/src/main/scala/gitbucket/core/model/RepositoryWebHookEvent.scala +++ b/src/main/scala/gitbucket/core/model/RepositoryWebHookEvent.scala @@ -6,17 +6,22 @@ lazy val RepositoryWebHookEvents = TableQuery[RepositoryWebHookEvents] - class RepositoryWebHookEvents(tag: Tag) extends Table[RepositoryWebHookEvent](tag, "WEB_HOOK_EVENT") with BasicTemplate { + class RepositoryWebHookEvents(tag: Tag) + extends Table[RepositoryWebHookEvent](tag, "WEB_HOOK_EVENT") + with BasicTemplate { val url = column[String]("URL") val event = column[WebHook.Event]("EVENT") - def * = (userName, repositoryName, url, event) <> ((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply) + def * = + (userName, repositoryName, url, event) <> ((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply) - def byRepositoryWebHook(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind) + def byRepositoryWebHook(owner: String, repository: String, url: String) = + byRepository(owner, repository) && (this.url === url.bind) def byRepositoryWebHook(owner: Rep[String], repository: Rep[String], url: Rep[String]) = byRepository(userName, repositoryName) && (this.url === url) def byRepositoryWebHook(webhook: RepositoryWebHooks) = byRepository(webhook.userName, webhook.repositoryName) && (this.url === webhook.url) - def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = byRepositoryWebHook(owner, repository, url) && (this.event === event.bind) + def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = + byRepositoryWebHook(owner, repository, url) && (this.event === event.bind) } } diff --git a/src/main/scala/gitbucket/core/model/SshKey.scala b/src/main/scala/gitbucket/core/model/SshKey.scala index a5a41f0..d6379ed 100644 --- a/src/main/scala/gitbucket/core/model/SshKey.scala +++ b/src/main/scala/gitbucket/core/model/SshKey.scala @@ -12,7 +12,8 @@ val publicKey = column[String]("PUBLIC_KEY") def * = (userName, sshKeyId, title, publicKey) <> (SshKey.tupled, SshKey.unapply) - def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind) + def byPrimaryKey(userName: String, sshKeyId: Int) = + (this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind) } } diff --git a/src/main/scala/gitbucket/core/model/WebHook.scala b/src/main/scala/gitbucket/core/model/WebHook.scala index 3643dfb..f575cf7 100644 --- a/src/main/scala/gitbucket/core/model/WebHook.scala +++ b/src/main/scala/gitbucket/core/model/WebHook.scala @@ -16,7 +16,7 @@ def valueOpt(code: String): Option[WebHookContentType] = map.get(code) } -trait WebHook{ +trait WebHook { val url: String val ctype: WebHookContentType val token: Option[String] @@ -45,7 +45,7 @@ case object TeamAdd extends Event("team_add") case object Watch extends Event("watch") - object Event{ + object Event { val values = List( CommitComment, Create, @@ -68,7 +68,7 @@ Watch ) - private val map: Map[String,Event] = values.map(e => e.name -> e).toMap + private val map: Map[String, Event] = values.map(e => e.name -> e).toMap def valueOf(name: String): Event = map(name) def valueOpt(name: String): Option[Event] = map.get(name) } diff --git a/src/main/scala/gitbucket/core/plugin/GitRepositoryRouting.scala b/src/main/scala/gitbucket/core/plugin/GitRepositoryRouting.scala index 61089f0..917beb4 100644 --- a/src/main/scala/gitbucket/core/plugin/GitRepositoryRouting.scala +++ b/src/main/scala/gitbucket/core/plugin/GitRepositoryRouting.scala @@ -10,13 +10,18 @@ * @param localPath the string to assemble local file path of repository (e.g. "gist/$1/$2") * @param filter the filter for request to the Git repository which is defined by this routing */ -case class GitRepositoryRouting(urlPattern: String, localPath: String, filter: GitRepositoryFilter){ +case class GitRepositoryRouting(urlPattern: String, localPath: String, filter: GitRepositoryFilter) { def this(urlPattern: String, localPath: String) = { - this(urlPattern, localPath, new GitRepositoryFilter(){ - def filter(repositoryName: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean) - (implicit session: Session): Boolean = true - }) + this( + urlPattern, + localPath, + new GitRepositoryFilter() { + def filter(repositoryName: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)( + implicit session: Session + ): Boolean = true + } + ) } } @@ -36,7 +41,8 @@ * @param session the database session * @return true if allow accessing to repository, otherwise false. */ - def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean) - (implicit session: Session): Boolean + def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)( + implicit session: Session + ): Boolean -} \ No newline at end of file +} diff --git a/src/main/scala/gitbucket/core/plugin/IssueHook.scala b/src/main/scala/gitbucket/core/plugin/IssueHook.scala index 56e7294..12d050a 100644 --- a/src/main/scala/gitbucket/core/plugin/IssueHook.scala +++ b/src/main/scala/gitbucket/core/plugin/IssueHook.scala @@ -9,7 +9,10 @@ trait IssueHook { def created(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () - def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () + def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)( + implicit session: Session, + context: Context + ): Unit = () def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () def reopened(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () diff --git a/src/main/scala/gitbucket/core/plugin/Plugin.scala b/src/main/scala/gitbucket/core/plugin/Plugin.scala index 3cb5a48..92a51ea 100644 --- a/src/main/scala/gitbucket/core/plugin/Plugin.scala +++ b/src/main/scala/gitbucket/core/plugin/Plugin.scala @@ -30,7 +30,8 @@ /** * Override to declare this plug-in provides images. */ - def images(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Array[Byte])] = Nil + def images(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Array[Byte])] = + Nil /** * Override to declare this plug-in provides controllers. @@ -40,7 +41,11 @@ /** * Override to declare this plug-in provides controllers. */ - def controllers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, ControllerBase)] = Nil + def controllers( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(String, ControllerBase)] = Nil /** * Override to declare this plug-in provides JavaScript. @@ -50,7 +55,8 @@ /** * Override to declare this plug-in provides JavaScript. */ - def javaScripts(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = Nil + def javaScripts(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = + Nil /** * Override to declare this plug-in provides renderers. @@ -60,7 +66,8 @@ /** * Override to declare this plug-in provides renderers. */ - def renderers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Renderer)] = Nil + def renderers(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, Renderer)] = + Nil /** * Override to add git repository routings. @@ -70,7 +77,11 @@ /** * Override to add git repository routings. */ - def repositoryRoutings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[GitRepositoryRouting] = Nil + def repositoryRoutings( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[GitRepositoryRouting] = Nil /** * Override to add account hooks. @@ -100,7 +111,11 @@ /** * Override to add repository hooks. */ - def repositoryHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[RepositoryHook] = Nil + def repositoryHooks( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[RepositoryHook] = Nil /** * Override to add issue hooks. @@ -120,7 +135,11 @@ /** * Override to add pull request hooks. */ - def pullRequestHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PullRequestHook] = Nil + def pullRequestHooks( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[PullRequestHook] = Nil /** * Override to add repository headers. @@ -130,7 +149,11 @@ /** * Override to add repository headers. */ - def repositoryHeaders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Html]] = Nil + def repositoryHeaders( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(RepositoryInfo, Context) => Option[Html]] = Nil /** * Override to add global menus. @@ -140,7 +163,11 @@ /** * Override to add global menus. */ - def globalMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil + def globalMenus( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(Context) => Option[Link]] = Nil /** * Override to add repository menus. @@ -150,7 +177,11 @@ /** * Override to add repository menus. */ - def repositoryMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil + def repositoryMenus( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil /** * Override to add repository setting tabs. @@ -160,7 +191,11 @@ /** * Override to add repository setting tabs. */ - def repositorySettingTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil + def repositorySettingTabs( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil /** * Override to add profile tabs. @@ -170,7 +205,11 @@ /** * Override to add profile tabs. */ - def profileTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Account, Context) => Option[Link]] = Nil + def profileTabs( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(Account, Context) => Option[Link]] = Nil /** * Override to add system setting menus. @@ -180,7 +219,11 @@ /** * Override to add system setting menus. */ - def systemSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil + def systemSettingMenus( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(Context) => Option[Link]] = Nil /** * Override to add account setting menus. @@ -190,7 +233,11 @@ /** * Override to add account setting menus. */ - def accountSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil + def accountSettingMenus( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(Context) => Option[Link]] = Nil /** * Override to add dashboard tabs. @@ -200,7 +247,11 @@ /** * Override to add dashboard tabs. */ - def dashboardTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil + def dashboardTabs( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(Context) => Option[Link]] = Nil /** * Override to add issue sidebars. @@ -210,7 +261,11 @@ /** * Override to add issue sidebars. */ - def issueSidebars(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = Nil + def issueSidebars( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = Nil /** * Override to add assets mappings. @@ -220,7 +275,11 @@ /** * Override to add assets mappings. */ - def assetsMappings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = Nil + def assetsMappings( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[(String, String)] = Nil /** * Override to add text decorators. @@ -230,7 +289,8 @@ /** * Override to add text decorators. */ - def textDecorators(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[TextDecorator] = Nil + def textDecorators(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[TextDecorator] = + Nil /** * Override to add suggestion provider. @@ -240,7 +300,11 @@ /** * Override to add suggestion provider. */ - def suggestionProviders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[SuggestionProvider] = Nil + def suggestionProviders( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[SuggestionProvider] = Nil /** * Override to add ssh command providers. @@ -250,25 +314,32 @@ /** * Override to add ssh command providers. */ - def sshCommandProviders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PartialFunction[String, Command]] = Nil - + def sshCommandProviders( + registry: PluginRegistry, + context: ServletContext, + settings: SystemSettings + ): Seq[PartialFunction[String, Command]] = Nil /** * This method is invoked in initialization of plugin system. * Register plugin functionality to PluginRegistry. */ def initialize(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = { - (images ++ images(registry, context, settings)).foreach { case (id, in) => - registry.addImage(id, in) + (images ++ images(registry, context, settings)).foreach { + case (id, in) => + registry.addImage(id, in) } - (controllers ++ controllers(registry, context, settings)).foreach { case (path, controller) => - registry.addController(path, controller) + (controllers ++ controllers(registry, context, settings)).foreach { + case (path, controller) => + registry.addController(path, controller) } - (javaScripts ++ javaScripts(registry, context, settings)).foreach { case (path, script) => - registry.addJavaScript(path, script) + (javaScripts ++ javaScripts(registry, context, settings)).foreach { + case (path, script) => + registry.addJavaScript(path, script) } - (renderers ++ renderers(registry, context, settings)).foreach { case (extension, renderer) => - registry.addRenderer(extension, renderer) + (renderers ++ renderers(registry, context, settings)).foreach { + case (extension, renderer) => + registry.addRenderer(extension, renderer) } (repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing => registry.addRepositoryRouting(routing) @@ -345,7 +416,7 @@ * Helper method to get a resource from classpath. */ protected def fromClassPath(path: String): Array[Byte] = - using(getClass.getClassLoader.getResourceAsStream(path)){ in => + using(getClass.getClassLoader.getResourceAsStream(path)) { in => val bytes = new Array[Byte](in.available) in.read(bytes) bytes diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala index 79b85c1..287a5bc 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala @@ -70,7 +70,7 @@ @deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0") def addImage(id: String, in: InputStream): Unit = { - val bytes = using(in){ in => + val bytes = using(in) { in => val bytes = new Array[Byte](in.available) in.read(bytes) bytes @@ -87,9 +87,11 @@ def getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq - def addJavaScript(path: String, script: String): Unit = javaScripts.add((path, script)) //javaScripts += ((path, script)) + def addJavaScript(path: String, script: String): Unit = + javaScripts.add((path, script)) //javaScripts += ((path, script)) - def getJavaScript(currentPath: String): List[String] = javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2) + def getJavaScript(currentPath: String): List[String] = + javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2) def addRenderer(extension: String, renderer: Renderer): Unit = renderers.put(extension, renderer) @@ -129,7 +131,8 @@ def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.asScala.toSeq - def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders.add(repositoryHeader) + def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = + repositoryHeaders.add(repositoryHeader) def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.asScala.toSeq @@ -137,11 +140,13 @@ def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.asScala.toSeq - def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus.add(repositoryMenu) + def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = + repositoryMenus.add(repositoryMenu) def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.asScala.toSeq - def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs.add(repositorySettingTab) + def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = + repositorySettingTabs.add(repositorySettingTab) def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.asScala.toSeq @@ -149,11 +154,13 @@ def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.asScala.toSeq - def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus.add(systemSettingMenu) + def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = + systemSettingMenus.add(systemSettingMenu) def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.asScala.toSeq - def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus.add(accountSettingMenu) + def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = + accountSettingMenus.add(accountSettingMenu) def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.asScala.toSeq @@ -161,7 +168,8 @@ def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.asScala.toSeq - def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars.add(issueSidebar) + def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = + issueSidebars.add(issueSidebar) def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.asScala.toSeq @@ -177,7 +185,8 @@ def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq - def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit = sshCommandProviders.add(sshCommandProvider) + def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit = + sshCommandProviders.add(sshCommandProvider) def getSshCommandProviders: Seq[PartialFunction[String, Command]] = sshCommandProviders.asScala.toSeq } @@ -212,37 +221,44 @@ /** * Uninstall a specified plugin. */ - def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { - instance.getPlugins() - .collect { case plugin if plugin.pluginId == pluginId => plugin } - .foreach { plugin => + def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = + synchronized { + instance + .getPlugins() + .collect { case plugin if plugin.pluginId == pluginId => plugin } + .foreach { plugin => // try { // plugin.pluginClass.uninstall(instance, context, settings) // } catch { // case e: Exception => // logger.error(s"Error during uninstalling plugin: ${plugin.pluginJar.getName}", e) // } - shutdown(context, settings) - plugin.pluginJar.delete() - instance = new PluginRegistry() - initialize(context, settings, conn) - } - } + shutdown(context, settings) + plugin.pluginJar.delete() + instance = new PluginRegistry() + initialize(context, settings, conn) + } + } /** * Install a plugin from a specified jar file. */ - def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { - shutdown(context, settings) - FileUtils.copyFile(file, new File(PluginHome, file.getName)) - instance = new PluginRegistry() - initialize(context, settings, conn) - } + def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = + synchronized { + shutdown(context, settings) + FileUtils.copyFile(file, new File(PluginHome, file.getName)) + instance = new PluginRegistry() + initialize(context, settings, conn) + } private def listPluginJars(dir: File): Seq[File] = { - dir.listFiles(new FilenameFilter { - override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") - }).toSeq.sortBy(_.getName).reverse + dir + .listFiles(new FilenameFilter { + override def accept(dir: File, name: String): Boolean = name.endsWith(".jar") + }) + .toSeq + .sortBy(_.getName) + .reverse } lazy val extraPluginDir: Option[String] = Option(System.getProperty("gitbucket.pluginDir")) @@ -256,13 +272,17 @@ // Clean installed directory val installedDir = new File(PluginHome, ".installed") - if(installedDir.exists){ + if (installedDir.exists) { FileUtils.deleteDirectory(installedDir) } installedDir.mkdirs() val pluginJars = listPluginJars(pluginDir) - val extraJars = extraPluginDir.map { extraDir => listPluginJars(new File(extraDir)) }.getOrElse(Nil) + val extraJars = extraPluginDir + .map { extraDir => + listPluginJars(new File(extraDir)) + } + .getOrElse(Nil) (extraJars ++ pluginJars).foreach { pluginJar => val installedJar = new File(installedDir, pluginJar.getName) @@ -283,27 +303,32 @@ case None => { // Migration val solidbase = new Solidbase() - solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*)) + solidbase + .migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*)) conn.commit() // Check database version val databaseVersion = manager.getCurrentVersion(plugin.pluginId) val pluginVersion = plugin.versions.last.getVersion if (databaseVersion != pluginVersion) { - throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}") + throw new IllegalStateException( + s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}" + ) } // Initialize plugin.initialize(instance, context, settings) - instance.addPlugin(PluginInfo( - pluginId = plugin.pluginId, - pluginName = plugin.pluginName, - pluginVersion = plugin.versions.last.getVersion, - description = plugin.description, - pluginClass = plugin, - pluginJar = pluginJar, - classLoader = classLoader - )) + instance.addPlugin( + PluginInfo( + pluginId = plugin.pluginId, + pluginName = plugin.pluginName, + pluginVersion = plugin.versions.last.getVersion, + description = plugin.description, + pluginClass = plugin, + pluginJar = pluginJar, + classLoader = classLoader + ) + ) } } } catch { @@ -311,13 +336,13 @@ } } - if(watcher == null){ + if (watcher == null) { watcher = new PluginWatchThread(context, PluginHome) watcher.start() } extraPluginDir.foreach { extraDir => - if(extraWatcher == null){ + if (extraWatcher == null) { extraWatcher = new PluginWatchThread(context, extraDir) extraWatcher.start() } @@ -328,11 +353,11 @@ instance.getPlugins().foreach { plugin => try { plugin.pluginClass.shutdown(instance, context, settings) - if(watcher != null){ + if (watcher != null) { watcher.interrupt() watcher = null } - if(extraWatcher != null){ + if (extraWatcher != null) { extraWatcher.interrupt() extraWatcher = null } @@ -380,17 +405,19 @@ override def run(): Unit = { val path = Paths.get(dir) - if(!Files.exists(path)){ + if (!Files.exists(path)) { Files.createDirectories(path) } val fs = path.getFileSystem val watcher = fs.newWatchService - val watchKey = path.register(watcher, + val watchKey = path.register( + watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE, - StandardWatchEventKinds.OVERFLOW) + StandardWatchEventKinds.OVERFLOW + ) logger.info("Start PluginWatchThread: " + path) @@ -400,13 +427,13 @@ val events = detectedWatchKey.pollEvents.asScala.filter { e => e.context.toString != ".installed" && !e.context.toString.endsWith(".bak") } - if(events.nonEmpty){ + if (events.nonEmpty) { events.foreach { event => logger.info(event.kind + ": " + event.context) } new Thread { override def run(): Unit = { - gitbucket.core.servlet.Database() withTransaction { session => + gitbucket.core.servlet.Database() withTransaction { session => logger.info("Reloading plugins...") PluginRegistry.reload(context, loadSystemSettings(), session.conn) logger.info("Reloading finished.") diff --git a/src/main/scala/gitbucket/core/plugin/PluginRepository.scala b/src/main/scala/gitbucket/core/plugin/PluginRepository.scala index c4cd051..e3d9d1c 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRepository.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRepository.scala @@ -15,7 +15,7 @@ lazy val LocalRepositoryIndexFile = new java.io.File(LocalRepositoryDir, "plugins.json") def getPlugins(): Seq[PluginMetadata] = { - if(LocalRepositoryIndexFile.exists){ + if (LocalRepositoryIndexFile.exists) { parsePluginJson(FileUtils.readFileToString(LocalRepositoryIndexFile, "UTF-8")) } else Nil } @@ -29,7 +29,7 @@ description: String, versions: Seq[VersionDef], default: Boolean = false -){ +) { lazy val latestVersion: VersionDef = versions.last } @@ -37,7 +37,6 @@ version: String, url: String, range: String -){ +) { lazy val file = url.substring(url.lastIndexOf("/") + 1) } - diff --git a/src/main/scala/gitbucket/core/plugin/ReceiveHook.scala b/src/main/scala/gitbucket/core/plugin/ReceiveHook.scala index 7b76ecc..8452d29 100644 --- a/src/main/scala/gitbucket/core/plugin/ReceiveHook.scala +++ b/src/main/scala/gitbucket/core/plugin/ReceiveHook.scala @@ -6,10 +6,12 @@ trait ReceiveHook { - def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String) - (implicit session: Session): Option[String] = None + def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)( + implicit session: Session + ): Option[String] = None - def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String) - (implicit session: Session): Unit = () + def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)( + implicit session: Session + ): Unit = () } diff --git a/src/main/scala/gitbucket/core/plugin/Renderer.scala b/src/main/scala/gitbucket/core/plugin/Renderer.scala index b4c333e..6c4037b 100644 --- a/src/main/scala/gitbucket/core/plugin/Renderer.scala +++ b/src/main/scala/gitbucket/core/plugin/Renderer.scala @@ -21,14 +21,16 @@ object MarkdownRenderer extends Renderer { override def render(request: RenderRequest): Html = { import request._ - Html(Markdown.toHtml( - markdown = fileContent, - repository = repository, - enableWikiLink = enableWikiLink, - enableRefsLink = enableRefsLink, - enableAnchor = enableAnchor, - enableLineBreaks = false - )(context)) + Html( + Markdown.toHtml( + markdown = fileContent, + repository = repository, + enableWikiLink = enableWikiLink, + enableRefsLink = enableRefsLink, + enableAnchor = enableAnchor, + enableLineBreaks = false + )(context) + ) } } diff --git a/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala b/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala index e7d49df..32f5bff 100644 --- a/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala +++ b/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala @@ -73,7 +73,9 @@ * * Each element can be accessed as `option` in `template()` or `replace()` method. */ - def options(repository: RepositoryInfo): Seq[(String, String)] = values(repository).map { value => (value, value) } + def options(repository: RepositoryInfo): Seq[(String, String)] = values(repository).map { value => + (value, value) + } /** * JavaScript fragment to generate a label of completion proposal. The default is: `option.label`. diff --git a/src/main/scala/gitbucket/core/service/AccessTokenService.scala b/src/main/scala/gitbucket/core/service/AccessTokenService.scala index 8c123c5..102240d 100644 --- a/src/main/scala/gitbucket/core/service/AccessTokenService.scala +++ b/src/main/scala/gitbucket/core/service/AccessTokenService.scala @@ -7,7 +7,6 @@ import scala.util.Random - trait AccessTokenService { def makeAccessTokenString: String = { @@ -27,13 +26,10 @@ do { token = makeAccessTokenString - hash = tokenToHash(token) + hash = tokenToHash(token) } while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run) - val newToken = AccessToken( - userName = userName, - note = note, - tokenHash = hash) + val newToken = AccessToken(userName = userName, note = note, tokenHash = hash) val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) insert newToken (tokenId, token) } @@ -41,8 +37,11 @@ def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] = Accounts .join(AccessTokens) - .filter { case (ac, t) => (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) } - .map { case (ac, t) => ac } + .filter { + case (ac, t) => + (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) + } + .map { case (ac, t) => ac } .firstOption def getAccessTokens(userName: String)(implicit s: Session): List[AccessToken] = diff --git a/src/main/scala/gitbucket/core/service/AccountFederationService.scala b/src/main/scala/gitbucket/core/service/AccountFederationService.scala index a560127..6a3d388 100644 --- a/src/main/scala/gitbucket/core/service/AccountFederationService.scala +++ b/src/main/scala/gitbucket/core/service/AccountFederationService.scala @@ -12,20 +12,22 @@ private val logger = LoggerFactory.getLogger(classOf[AccountFederationService]) /** - * Get or create a user account federated with OIDC or SAML IdP. - * - * @param issuer Issuer - * @param subject Subject - * @param mailAddress Mail address - * @param preferredUserName Username (if this is none, username will be generated from the mail address) - * @param fullName Fullname (defaults to username) - * @return Account - */ - def getOrCreateFederatedUser(issuer: String, - subject: String, - mailAddress: String, - preferredUserName: Option[String], - fullName: Option[String])(implicit s: Session): Option[Account] = + * Get or create a user account federated with OIDC or SAML IdP. + * + * @param issuer Issuer + * @param subject Subject + * @param mailAddress Mail address + * @param preferredUserName Username (if this is none, username will be generated from the mail address) + * @param fullName Fullname (defaults to username) + * @return Account + */ + def getOrCreateFederatedUser( + issuer: String, + subject: String, + mailAddress: String, + preferredUserName: Option[String], + fullName: Option[String] + )(implicit s: Session): Option[Account] = getAccountByFederation(issuer, subject) match { case Some(account) if !account.isRemoved => Some(account) @@ -43,19 +45,25 @@ private def extractSafeStringForUserName(s: String) = """^[a-zA-Z0-9][a-zA-Z0-9\-_.]*""".r.findPrefixOf(s) /** - * Find an available username from the preferred username or mail address. - * - * @param mailAddress Mail address - * @param preferredUserName Username - * @return Available username - */ - def findAvailableUserName(preferredUserName: Option[String], mailAddress: String)(implicit s: Session): Option[String] = { - preferredUserName.flatMap(n => extractSafeStringForUserName(n)).orElse(extractSafeStringForUserName(mailAddress)) match { + * Find an available username from the preferred username or mail address. + * + * @param mailAddress Mail address + * @param preferredUserName Username + * @return Available username + */ + def findAvailableUserName(preferredUserName: Option[String], mailAddress: String)( + implicit s: Session + ): Option[String] = { + preferredUserName + .flatMap(n => extractSafeStringForUserName(n)) + .orElse(extractSafeStringForUserName(mailAddress)) match { case Some(safeUserName) => getAccountByUserName(safeUserName, includeRemoved = true) match { case None => Some(safeUserName) case Some(_) => - logger.info(s"User ($safeUserName) already exists. preferredUserName=$preferredUserName, mailAddress=$mailAddress") + logger.info( + s"User ($safeUserName) already exists. preferredUserName=$preferredUserName, mailAddress=$mailAddress" + ) None } case None => @@ -65,8 +73,10 @@ } def getAccountByFederation(issuer: String, subject: String)(implicit s: Session): Option[Account] = - AccountFederations.filter(_.byPrimaryKey(issuer, subject)) - .join(Accounts).on { case af ~ ac => af.userName === ac.userName } + AccountFederations + .filter(_.byPrimaryKey(issuer, subject)) + .join(Accounts) + .on { case af ~ ac => af.userName === ac.userName } .map { case _ ~ ac => ac } .firstOption diff --git a/src/main/scala/gitbucket/core/service/AccountService.scala b/src/main/scala/gitbucket/core/service/AccountService.scala index e502236..543f743 100644 --- a/src/main/scala/gitbucket/core/service/AccountService.scala +++ b/src/main/scala/gitbucket/core/service/AccountService.scala @@ -13,14 +13,16 @@ private val logger = LoggerFactory.getLogger(classOf[AccountService]) - def authenticate(settings: SystemSettings, userName: String, password: String)(implicit s: Session): Option[Account] = { + def authenticate(settings: SystemSettings, userName: String, password: String)( + implicit s: Session + ): Option[Account] = { val account = if (settings.ldapAuthentication) { ldapAuthentication(settings, userName, password) } else { defaultAuthentication(userName, password) } - if(account.isEmpty){ + if (account.isEmpty) { logger.info(s"Failed to authenticate: $userName") } @@ -32,45 +34,55 @@ */ private def defaultAuthentication(userName: String, password: String)(implicit s: Session) = { getAccountByUserName(userName).collect { - case account if(!account.isGroupAccount && account.password == sha1(password)) => Some(account) + case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account) } getOrElse None } /** * Authenticate by LDAP. */ - private def ldapAuthentication(settings: SystemSettings, userName: String, password: String) - (implicit s: Session): Option[Account] = { + private def ldapAuthentication(settings: SystemSettings, userName: String, password: String)( + implicit s: Session + ): Option[Account] = { LDAPUtil.authenticate(settings.ldap.get, userName, password) match { case Right(ldapUserInfo) => { // Create or update account by LDAP information getAccountByUserName(ldapUserInfo.userName, true) match { - case Some(x) if(!x.isRemoved) => { - if(settings.ldap.get.mailAttribute.getOrElse("").isEmpty) { + case Some(x) if (!x.isRemoved) => { + if (settings.ldap.get.mailAttribute.getOrElse("").isEmpty) { updateAccount(x.copy(fullName = ldapUserInfo.fullName)) } else { updateAccount(x.copy(mailAddress = ldapUserInfo.mailAddress, fullName = ldapUserInfo.fullName)) } getAccountByUserName(ldapUserInfo.userName) } - case Some(x) if(x.isRemoved) => { + case Some(x) if (x.isRemoved) => { logger.info("LDAP Authentication Failed: Account is already registered but disabled.") defaultAuthentication(userName, password) } - case None => getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { - case Some(x) if(!x.isRemoved) => { - updateAccount(x.copy(fullName = ldapUserInfo.fullName)) - getAccountByUserName(ldapUserInfo.userName) + case None => + getAccountByMailAddress(ldapUserInfo.mailAddress, true) match { + case Some(x) if (!x.isRemoved) => { + updateAccount(x.copy(fullName = ldapUserInfo.fullName)) + getAccountByUserName(ldapUserInfo.userName) + } + case Some(x) if (x.isRemoved) => { + logger.info("LDAP Authentication Failed: Account is already registered but disabled.") + defaultAuthentication(userName, password) + } + case None => { + createAccount( + ldapUserInfo.userName, + "", + ldapUserInfo.fullName, + ldapUserInfo.mailAddress, + false, + None, + None + ) + getAccountByUserName(ldapUserInfo.userName) + } } - case Some(x) if(x.isRemoved) => { - logger.info("LDAP Authentication Failed: Account is already registered but disabled.") - defaultAuthentication(userName, password) - } - case None => { - createAccount(ldapUserInfo.userName, "", ldapUserInfo.fullName, ldapUserInfo.mailAddress, false, None, None) - getAccountByUserName(ldapUserInfo.userName) - } - } } } case Left(errorMessage) => { @@ -81,58 +93,90 @@ } def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = - Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption + Accounts filter (t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption - def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false)(implicit s: Session): Map[String, Account] = { + def getAccountsByUserNames(userNames: Set[String], knowns: Set[Account], includeRemoved: Boolean = false)( + implicit s: Session + ): Map[String, Account] = { val map = knowns.map(a => a.userName -> a).toMap val needs = userNames -- map.keySet - if(needs.isEmpty){ + if (needs.isEmpty) { map - }else{ - map ++ Accounts.filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved)).list.map(a => a.userName -> a).toMap + } else { + map ++ Accounts + .filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved)) + .list + .map(a => a.userName -> a) + .toMap } } - def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] = - Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption + def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)( + implicit s: Session + ): Option[Account] = + Accounts filter ( + t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved) + ) firstOption - def getAllUsers(includeRemoved: Boolean = true, includeGroups: Boolean = true)(implicit s: Session): List[Account] = - { + def getAllUsers(includeRemoved: Boolean = true, includeGroups: Boolean = true)(implicit s: Session): List[Account] = { Accounts filter { t => (1.bind === 1.bind) && - (t.groupAccount === false.bind, !includeGroups) && - (t.removed === false.bind, !includeRemoved) - } sortBy(_.userName) list + (t.groupAccount === false.bind, !includeGroups) && + (t.removed === false.bind, !includeRemoved) + } sortBy (_.userName) list } def isLastAdministrator(account: Account)(implicit s: Session): Boolean = { - if(account.isAdmin){ + if (account.isAdmin) { (Accounts filter (_.removed === false.bind) filter (_.isAdmin === true.bind) map (_.userName.length)).first == 1 } else false } - def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, description: Option[String], url: Option[String]) - (implicit s: Session): Unit = + def createAccount( + userName: String, + password: String, + fullName: String, + mailAddress: String, + isAdmin: Boolean, + description: Option[String], + url: Option[String] + )(implicit s: Session): Unit = Accounts insert Account( - userName = userName, - password = password, - fullName = fullName, - mailAddress = mailAddress, - isAdmin = isAdmin, - url = url, + userName = userName, + password = password, + fullName = fullName, + mailAddress = mailAddress, + isAdmin = isAdmin, + url = url, registeredDate = currentDate, - updatedDate = currentDate, - lastLoginDate = None, - image = None, + updatedDate = currentDate, + lastLoginDate = None, + image = None, isGroupAccount = false, - isRemoved = false, - description = description) + isRemoved = false, + description = description + ) def updateAccount(account: Account)(implicit s: Session): Unit = Accounts - .filter { a => a.userName === account.userName.bind } - .map { a => (a.password, a.fullName, a.mailAddress, a.isAdmin, a.url.?, a.registeredDate, a.updatedDate, a.lastLoginDate.?, a.removed, a.description.?) } - .update ( + .filter { a => + a.userName === account.userName.bind + } + .map { a => + ( + a.password, + a.fullName, + a.mailAddress, + a.isAdmin, + a.url.?, + a.registeredDate, + a.updatedDate, + a.lastLoginDate.?, + a.removed, + a.description.? + ) + } + .update( account.password, account.fullName, account.mailAddress, @@ -142,7 +186,8 @@ currentDate, account.lastLoginDate, account.isRemoved, - account.description) + account.description + ) def updateAvatarImage(userName: String, image: Option[String])(implicit s: Session): Unit = Accounts.filter(_.userName === userName.bind).map(_.image.?).update(image) @@ -152,29 +197,34 @@ def createGroup(groupName: String, description: Option[String], url: Option[String])(implicit s: Session): Unit = Accounts insert Account( - userName = groupName, - password = "", - fullName = groupName, - mailAddress = groupName + "@devnull", - isAdmin = false, - url = url, + userName = groupName, + password = "", + fullName = groupName, + mailAddress = groupName + "@devnull", + isAdmin = false, + url = url, registeredDate = currentDate, - updatedDate = currentDate, - lastLoginDate = None, - image = None, + updatedDate = currentDate, + lastLoginDate = None, + image = None, isGroupAccount = true, - isRemoved = false, - description = description) + isRemoved = false, + description = description + ) - def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)(implicit s: Session): Unit = - Accounts.filter(_.userName === groupName.bind) + def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)( + implicit s: Session + ): Unit = + Accounts + .filter(_.userName === groupName.bind) .map(t => (t.url.?, t.description.?, t.updatedDate, t.removed)) .update(url, description, currentDate, removed) def updateGroupMembers(groupName: String, members: List[(String, Boolean)])(implicit s: Session): Unit = { GroupMembers.filter(_.groupName === groupName.bind).delete - members.foreach { case (userName, isManager) => - GroupMembers insert GroupMember (groupName, userName, isManager) + members.foreach { + case (userName, isManager) => + GroupMembers insert GroupMember(groupName, userName, isManager) } } diff --git a/src/main/scala/gitbucket/core/service/ActivityService.scala b/src/main/scala/gitbucket/core/service/ActivityService.scala index 54d9d5a..ef83188 100644 --- a/src/main/scala/gitbucket/core/service/ActivityService.scala +++ b/src/main/scala/gitbucket/core/service/ActivityService.scala @@ -15,188 +15,356 @@ def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] = Activities - .join(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { case (t1, t2) => - if(isPublic){ - (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind) - } else { - (t1.activityUserName === activityUserName.bind) - } + .join(Repositories) + .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) + .filter { + case (t1, t2) => + if (isPublic) { + (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind) + } else { + (t1.activityUserName === activityUserName.bind) + } } .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } + .map { case (t1, t2) => t1 } .take(30) .list def getRecentActivities()(implicit s: Session): List[Activity] = Activities - .join(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) - .filter { case (t1, t2) => t2.isPrivate === false.bind } + .join(Repositories) + .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) + .filter { case (t1, t2) => t2.isPrivate === false.bind } .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } + .map { case (t1, t2) => t1 } .take(30) .list - def getRecentActivitiesByOwners(owners : Set[String])(implicit s: Session): List[Activity] = + def getRecentActivitiesByOwners(owners: Set[String])(implicit s: Session): List[Activity] = Activities - .join(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) + .join(Repositories) + .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) .filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) } .sortBy { case (t1, t2) => t1.activityId desc } - .map { case (t1, t2) => t1 } + .map { case (t1, t2) => t1 } .take(30) .list - def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)( + implicit s: Session + ): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "create_repository", s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", None, - currentDate) + currentDate + ) - def recordCreateIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCreateIssueActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + title: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "open_issue", s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", Some(title), - currentDate) + currentDate + ) - def recordCloseIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCloseIssueActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + title: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "close_issue", s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", Some(title), - currentDate) + currentDate + ) - def recordClosePullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordClosePullRequestActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + title: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "close_issue", s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(title), - currentDate) + currentDate + ) - def recordReopenIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordReopenIssueActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + title: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "reopen_issue", s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", Some(title), - currentDate) + currentDate + ) - def recordCommentIssueActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCommentIssueActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + comment: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "comment_issue", s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", Some(cut(comment, 200)), - currentDate) + currentDate + ) - def recordCommentPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, comment: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCommentPullRequestActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + comment: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "comment_issue", s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(cut(comment, 200)), - currentDate) + currentDate + ) - def recordCommentCommitActivity(userName: String, repositoryName: String, activityUserName: String, commitId: String, comment: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCommentCommitActivity( + userName: String, + repositoryName: String, + activityUserName: String, + commitId: String, + comment: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "comment_commit", s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", Some(cut(comment, 200)), currentDate ) - def recordCreateWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCreateWikiPageActivity( + userName: String, + repositoryName: String, + activityUserName: String, + pageName: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "create_wiki", s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", Some(pageName), - currentDate) + currentDate + ) - def recordEditWikiPageActivity(userName: String, repositoryName: String, activityUserName: String, pageName: String, commitId: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordEditWikiPageActivity( + userName: String, + repositoryName: String, + activityUserName: String, + pageName: String, + commitId: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "edit_wiki", s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", Some(pageName + ":" + commitId), - currentDate) + currentDate + ) - def recordPushActivity(userName: String, repositoryName: String, activityUserName: String, - branchName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordPushActivity( + userName: String, + repositoryName: String, + activityUserName: String, + branchName: String, + commits: List[JGitUtil.CommitInfo] + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "push", s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", - Some(commits.take(5).map { commit => commit.id + ":" + commit.shortMessage }.mkString("\n")), - currentDate) + Some( + commits + .take(5) + .map { commit => + commit.id + ":" + commit.shortMessage + } + .mkString("\n") + ), + currentDate + ) - def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String, - tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCreateTagActivity( + userName: String, + repositoryName: String, + activityUserName: String, + tagName: String, + commits: List[JGitUtil.CommitInfo] + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "create_tag", s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", None, - currentDate) + currentDate + ) - def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String, - tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordDeleteTagActivity( + userName: String, + repositoryName: String, + activityUserName: String, + tagName: String, + commits: List[JGitUtil.CommitInfo] + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "delete_tag", s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", None, - currentDate) + currentDate + ) - def recordCreateBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordCreateBranchActivity( + userName: String, + repositoryName: String, + activityUserName: String, + branchName: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "create_branch", s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", None, - currentDate) + currentDate + ) - def recordDeleteBranchActivity(userName: String, repositoryName: String, activityUserName: String, branchName: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordDeleteBranchActivity( + userName: String, + repositoryName: String, + activityUserName: String, + branchName: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "delete_branch", s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", None, - currentDate) + currentDate + ) - def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)( + implicit s: Session + ): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "fork", s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]", None, - currentDate) + currentDate + ) - def recordPullRequestActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, title: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordPullRequestActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + title: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "open_pullreq", s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(title), - currentDate) + currentDate + ) - def recordMergeActivity(userName: String, repositoryName: String, activityUserName: String, issueId: Int, message: String) - (implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordMergeActivity( + userName: String, + repositoryName: String, + activityUserName: String, + issueId: Int, + message: String + )(implicit s: Session): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "merge_pullreq", s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(message), - currentDate) + currentDate + ) - def recordReleaseActivity(userName: String, repositoryName: String, activityUserName: String, name: String)(implicit s: Session): Unit = - Activities insert Activity(userName, repositoryName, activityUserName, + def recordReleaseActivity(userName: String, repositoryName: String, activityUserName: String, name: String)( + implicit s: Session + ): Unit = + Activities insert Activity( + userName, + repositoryName, + activityUserName, "release", s"[user:${activityUserName}] released ${name} at [repo:${userName}/${repositoryName}]", None, - currentDate) + currentDate + ) private def cut(value: String, length: Int): String = - if(value.length > length) value.substring(0, length) + "..." else value + if (value.length > length) value.substring(0, length) + "..." else value } diff --git a/src/main/scala/gitbucket/core/service/CommitStatusService.scala b/src/main/scala/gitbucket/core/service/CommitStatusService.scala index ca24ff9..25a9b6b 100644 --- a/src/main/scala/gitbucket/core/service/CommitStatusService.scala +++ b/src/main/scala/gitbucket/core/service/CommitStatusService.scala @@ -6,47 +6,77 @@ import gitbucket.core.model.{CommitState, CommitStatus, Account} trait CommitStatusService { - /** insert or update */ - def createCommitStatus(userName: String, repositoryName: String, sha: String, context: String, state: CommitState, - targetUrl: Option[String], description: Option[String], now: java.util.Date, creator: Account)(implicit s: Session): Int = - CommitStatuses - .filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind ) - .map(_.commitStatusId).firstOption match { - case Some(id: Int) => { - CommitStatuses.filter(_.byPrimaryKey(id)).map { t => - (t.state , t.targetUrl , t.updatedDate , t.creator, t.description) - }.update((state, targetUrl, now, creator.userName, description)) - id - } - case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) insert CommitStatus( - userName = userName, - repositoryName = repositoryName, - commitId = sha, - context = context, - state = state, - targetUrl = targetUrl, - description = description, - creator = creator.userName, - registeredDate = now, - updatedDate = now) - } - def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] = + /** insert or update */ + def createCommitStatus( + userName: String, + repositoryName: String, + sha: String, + context: String, + state: CommitState, + targetUrl: Option[String], + description: Option[String], + now: java.util.Date, + creator: Account + )(implicit s: Session): Int = + CommitStatuses + .filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind) + .map(_.commitStatusId) + .firstOption match { + case Some(id: Int) => { + CommitStatuses + .filter(_.byPrimaryKey(id)) + .map { t => + (t.state, t.targetUrl, t.updatedDate, t.creator, t.description) + } + .update((state, targetUrl, now, creator.userName, description)) + id + } + case None => + (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) insert CommitStatus( + userName = userName, + repositoryName = repositoryName, + commitId = sha, + context = context, + state = state, + targetUrl = targetUrl, + description = description, + creator = creator.userName, + registeredDate = now, + updatedDate = now + ) + } + + def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session): Option[CommitStatus] = CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption - def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)(implicit s: Session) :Option[CommitStatus] = + def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)( + implicit s: Session + ): Option[CommitStatus] = CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind).firstOption - def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[CommitStatus] = + def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session): List[CommitStatus] = byCommitStatues(userName, repositoryName, sha).list - def getRecentStatuesContexts(userName: String, repositoryName: String, time: java.util.Date)(implicit s: Session) :List[String] = - CommitStatuses.filter(t => t.byRepository(userName, repositoryName)).filter(t => t.updatedDate > time.bind).groupBy(_.context).map(_._1).list + def getRecentStatuesContexts(userName: String, repositoryName: String, time: java.util.Date)( + implicit s: Session + ): List[String] = + CommitStatuses + .filter(t => t.byRepository(userName, repositoryName)) + .filter(t => t.updatedDate > time.bind) + .groupBy(_.context) + .map(_._1) + .list - def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[(CommitStatus, Account)] = - byCommitStatues(userName, repositoryName, sha).join(Accounts).filter { case (t, a) => t.creator === a.userName }.list + def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)( + implicit s: Session + ): List[(CommitStatus, Account)] = + byCommitStatues(userName, repositoryName, sha) + .join(Accounts) + .filter { case (t, a) => t.creator === a.userName } + .list - protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) = + protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) = CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha)).sortBy(_.updatedDate desc) } diff --git a/src/main/scala/gitbucket/core/service/CommitsService.scala b/src/main/scala/gitbucket/core/service/CommitsService.scala index 3e28f45..c3142c7 100644 --- a/src/main/scala/gitbucket/core/service/CommitsService.scala +++ b/src/main/scala/gitbucket/core/service/CommitsService.scala @@ -7,9 +7,11 @@ trait CommitsService { - def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(implicit s: Session) = - CommitComments filter { - t => t.byCommit(owner, repository, commitId) && (t.issueId.isEmpty || includePullRequest) + def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)( + implicit s: Session + ) = + CommitComments filter { t => + t.byCommit(owner, repository, commitId) && (t.issueId.isEmpty || includePullRequest) } list def getCommitComment(owner: String, repository: String, commentId: String)(implicit s: Session) = @@ -20,33 +22,48 @@ else None - def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String, - content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int], - issueId: Option[Int])(implicit s: Session): Int = + def createCommitComment( + owner: String, + repository: String, + commitId: String, + loginUser: String, + content: String, + fileName: Option[String], + oldLine: Option[Int], + newLine: Option[Int], + issueId: Option[Int] + )(implicit s: Session): Int = CommitComments returning CommitComments.map(_.commentId) insert CommitComment( - userName = owner, - repositoryName = repository, - commitId = commitId, + userName = owner, + repositoryName = repository, + commitId = commitId, commentedUserName = loginUser, - content = content, - fileName = fileName, - oldLine = oldLine, - newLine = newLine, - registeredDate = currentDate, - updatedDate = currentDate, - issueId = issueId) + content = content, + fileName = fileName, + oldLine = oldLine, + newLine = newLine, + registeredDate = currentDate, + updatedDate = currentDate, + issueId = issueId + ) - def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])(implicit s: Session): Unit = - CommitComments.filter(_.byPrimaryKey(commentId)) + def updateCommitCommentPosition(commentId: Int, commitId: String, oldLine: Option[Int], newLine: Option[Int])( + implicit s: Session + ): Unit = + CommitComments + .filter(_.byPrimaryKey(commentId)) .map { t => (t.commitId, t.oldLine, t.newLine) - }.update(commitId, oldLine, newLine) + } + .update(commitId, oldLine, newLine) def updateCommitComment(commentId: Int, content: String)(implicit s: Session) = { CommitComments - .filter (_.byPrimaryKey(commentId)) - .map { t => (t.content, t.updatedDate) } - .update (content, currentDate) + .filter(_.byPrimaryKey(commentId)) + .map { t => + (t.content, t.updatedDate) + } + .update(content, currentDate) } def deleteCommitComment(commentId: Int)(implicit s: Session) = diff --git a/src/main/scala/gitbucket/core/service/DeployKeyService.scala b/src/main/scala/gitbucket/core/service/DeployKeyService.scala index 7313bc6..13885a6 100644 --- a/src/main/scala/gitbucket/core/service/DeployKeyService.scala +++ b/src/main/scala/gitbucket/core/service/DeployKeyService.scala @@ -6,20 +6,24 @@ trait DeployKeyService { - def addDeployKey(userName: String, repositoryName: String, title: String, publicKey: String, allowWrite: Boolean) - (implicit s: Session): Unit = - DeployKeys.insert(DeployKey( - userName = userName, - repositoryName = repositoryName, - title = title, - publicKey = publicKey, - allowWrite = allowWrite - )) + def addDeployKey(userName: String, repositoryName: String, title: String, publicKey: String, allowWrite: Boolean)( + implicit s: Session + ): Unit = + DeployKeys.insert( + DeployKey( + userName = userName, + repositoryName = repositoryName, + title = title, + publicKey = publicKey, + allowWrite = allowWrite + ) + ) def getDeployKeys(userName: String, repositoryName: String)(implicit s: Session): List[DeployKey] = DeployKeys .filter(x => (x.userName === userName.bind) && (x.repositoryName === repositoryName.bind)) - .sortBy(_.deployKeyId).list + .sortBy(_.deployKeyId) + .list def getAllDeployKeys()(implicit s: Session): List[DeployKey] = DeployKeys.filter(_.publicKey.trim =!= "").list @@ -27,5 +31,4 @@ def deleteDeployKey(userName: String, repositoryName: String, deployKeyId: Int)(implicit s: Session): Unit = DeployKeys.filter(_.byPrimaryKey(userName, repositoryName, deployKeyId)).delete - } diff --git a/src/main/scala/gitbucket/core/service/HandleCommentService.scala b/src/main/scala/gitbucket/core/service/HandleCommentService.scala index 3ef52e6..edd968f 100644 --- a/src/main/scala/gitbucket/core/service/HandleCommentService.scala +++ b/src/main/scala/gitbucket/core/service/HandleCommentService.scala @@ -8,85 +8,111 @@ import gitbucket.core.util.Implicits._ trait HandleCommentService { - self: RepositoryService with IssuesService with ActivityService - with WebHookService with WebHookIssueCommentService with WebHookPullRequestService => + self: RepositoryService + with IssuesService + with ActivityService + with WebHookService + with WebHookIssueCommentService + with WebHookPullRequestService => /** * @see [[https://github.com/gitbucket/gitbucket/wiki/CommentAction]] */ - def handleComment(issue: Issue, content: Option[String], repository: RepositoryService.RepositoryInfo, actionOpt: Option[String]) - (implicit context: Context, s: Session) = { + def handleComment( + issue: Issue, + content: Option[String], + repository: RepositoryService.RepositoryInfo, + actionOpt: Option[String] + )(implicit context: Context, s: Session) = { context.loginAccount.flatMap { loginAccount => - defining(repository.owner, repository.name){ case (owner, name) => - val userName = loginAccount.userName + defining(repository.owner, repository.name) { + case (owner, name) => + val userName = loginAccount.userName - val (action, actionActivity) = actionOpt - .collect { - case "close" if(!issue.closed) => true -> - (Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _)) - case "reopen" if(issue.closed) => false -> - (Some("reopen") -> Some(recordReopenIssueActivity _)) - } - .map { case (closed, t) => - updateClosed(owner, name, issue.issueId, closed) - t - } - .getOrElse(None -> None) - - val commentId = (content, action) match { - case (None, None) => None - case (None, Some(action)) => - Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action)) - case (Some(content), _) => - val id = Some(createComment(owner, name, userName, issue.issueId, content, action.map(_+ "_comment").getOrElse("comment"))) - - // record comment activity - if(issue.isPullRequest) recordCommentPullRequestActivity(owner, name, userName, issue.issueId, content) - else recordCommentIssueActivity(owner, name, userName, issue.issueId, content) - - // extract references and create refer comment - createReferComment(owner, name, issue, content, loginAccount) - - id - } - - actionActivity.foreach { f => f(owner, name, userName, issue.issueId, issue.title) } - - // call web hooks - action match { - case None => commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount)) - case Some(act) => - val webHookAction = act match { - case "close" => "closed" - case "reopen" => "reopened" + val (action, actionActivity) = actionOpt + .collect { + case "close" if (!issue.closed) => + true -> + (Some("close") -> Some( + if (issue.isPullRequest) recordClosePullRequestActivity _ + else recordCloseIssueActivity _ + )) + case "reopen" if (issue.closed) => + false -> + (Some("reopen") -> Some(recordReopenIssueActivity _)) } - if(issue.isPullRequest) - callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, loginAccount) - else - callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, loginAccount) - } + .map { + case (closed, t) => + updateClosed(owner, name, issue.issueId, closed) + t + } + .getOrElse(None -> None) - // call hooks - content foreach { x => - if(issue.isPullRequest) - PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository)) - else - PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository)) - } - action foreach { - case "close" => - if(issue.isPullRequest) - PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository)) - else - PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository)) - case "reopen" => - if(issue.isPullRequest) - PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository)) - else - PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository)) - } + val commentId = (content, action) match { + case (None, None) => None + case (None, Some(action)) => + Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action)) + case (Some(content), _) => + val id = Some( + createComment( + owner, + name, + userName, + issue.issueId, + content, + action.map(_ + "_comment").getOrElse("comment") + ) + ) - commentId.map( issue -> _ ) + // record comment activity + if (issue.isPullRequest) recordCommentPullRequestActivity(owner, name, userName, issue.issueId, content) + else recordCommentIssueActivity(owner, name, userName, issue.issueId, content) + + // extract references and create refer comment + createReferComment(owner, name, issue, content, loginAccount) + + id + } + + actionActivity.foreach { f => + f(owner, name, userName, issue.issueId, issue.title) + } + + // call web hooks + action match { + case None => commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount)) + case Some(act) => + val webHookAction = act match { + case "close" => "closed" + case "reopen" => "reopened" + } + if (issue.isPullRequest) + callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, loginAccount) + else + callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, loginAccount) + } + + // call hooks + content foreach { x => + if (issue.isPullRequest) + PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository)) + else + PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository)) + } + action foreach { + case "close" => + if (issue.isPullRequest) + PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository)) + else + PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository)) + case "reopen" => + if (issue.isPullRequest) + PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository)) + else + PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository)) + } + + commentId.map(issue -> _) } } } diff --git a/src/main/scala/gitbucket/core/service/IssueCreationService.scala b/src/main/scala/gitbucket/core/service/IssueCreationService.scala index ad67266..a769fb8 100644 --- a/src/main/scala/gitbucket/core/service/IssueCreationService.scala +++ b/src/main/scala/gitbucket/core/service/IssueCreationService.scala @@ -11,20 +11,33 @@ self: RepositoryService with WebHookIssueCommentService with LabelsService with IssuesService with ActivityService => - def createIssue(repository: RepositoryInfo, title:String, body:Option[String], - assignee: Option[String], milestoneId: Option[Int], priorityId: Option[Int], labelNames: Seq[String], - loginAccount: Account)(implicit context: Context, s: Session) : Issue = { + def createIssue( + repository: RepositoryInfo, + title: String, + body: Option[String], + assignee: Option[String], + milestoneId: Option[Int], + priorityId: Option[Int], + labelNames: Seq[String], + loginAccount: Account + )(implicit context: Context, s: Session): Issue = { val owner = repository.owner - val name = repository.name - val userName = loginAccount.userName + val name = repository.name + val userName = loginAccount.userName val manageable = isIssueManageable(repository) // insert issue - val issueId = insertIssue(owner, name, userName, title, body, + val issueId = insertIssue( + owner, + name, + userName, + title, + body, if (manageable) assignee else None, if (manageable) milestoneId else None, - if (manageable) priorityId else None) + if (manageable) priorityId else None + ) val issue: Issue = getIssue(owner, name, issueId.toString).get // insert labels diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index f081d9c..8f665c6 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -5,7 +5,17 @@ import gitbucket.core.util.Implicits._ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.controller.Context -import gitbucket.core.model.{Issue, PullRequest, IssueComment, IssueLabel, Label, Account, Repository, CommitState, Role} +import gitbucket.core.model.{ + Issue, + PullRequest, + IssueComment, + IssueLabel, + Label, + Account, + Repository, + CommitState, + Role +} import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile.blockingApi._ @@ -21,22 +31,31 @@ else None def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) = - IssueComments filter (_.byIssue(owner, repository, issueId)) sortBy(_.commentId asc) list + IssueComments filter (_.byIssue(owner, repository, issueId)) sortBy (_.commentId asc) list /** @return IssueComment and commentedUser and Issue */ - def getCommentsForApi(owner: String, repository: String, issueId: Int)(implicit s: Session): List[(IssueComment, Account, Issue)] = - IssueComments.filter(_.byIssue(owner, repository, issueId)) - .filter(_.action inSetBind Set("comment" , "close_comment", "reopen_comment")) - .join(Accounts).on { case t1 ~ t2 => t1.commentedUserName === t2.userName } - .join(Issues).on { case t1 ~ t2 ~ t3 => t3.byIssue(t1.userName, t1.repositoryName, t1.issueId) } - .map { case t1 ~ t2 ~ t3 => (t1, t2, t3) } - .list + def getCommentsForApi(owner: String, repository: String, issueId: Int)( + implicit s: Session + ): List[(IssueComment, Account, Issue)] = + IssueComments + .filter(_.byIssue(owner, repository, issueId)) + .filter(_.action inSetBind Set("comment", "close_comment", "reopen_comment")) + .join(Accounts) + .on { case t1 ~ t2 => t1.commentedUserName === t2.userName } + .join(Issues) + .on { case t1 ~ t2 ~ t3 => t3.byIssue(t1.userName, t1.repositoryName, t1.issueId) } + .map { case t1 ~ t2 ~ t3 => (t1, t2, t3) } + .list - def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = { - IssueComments.filter(_.byIssue(owner, repository, issueId)) + def getMergedComment(owner: String, repository: String, issueId: Int)( + implicit s: Session + ): Option[(IssueComment, Account)] = { + IssueComments + .filter(_.byIssue(owner, repository, issueId)) .filter(_.action === "merge".bind) - .join(Accounts).on { case t1 ~ t2 => t1.commentedUserName === t2.userName } - .map { case t1 ~ t2 => (t1, t2)} + .join(Accounts) + .on { case t1 ~ t2 => t1.commentedUserName === t2.userName } + .map { case t1 ~ t2 => (t1, t2) } .firstOption } @@ -50,15 +69,19 @@ def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session): List[Label] = { IssueLabels - .join(Labels).on { case t1 ~ t2 => - t1.byLabel(t2.userName, t2.repositoryName, t2.labelId) + .join(Labels) + .on { + case t1 ~ t2 => + t1.byLabel(t2.userName, t2.repositoryName, t2.labelId) } .filter { case t1 ~ t2 => t1.byIssue(owner, repository, issueId) } .map { case t1 ~ t2 => t2 } .list } - def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)(implicit s: Session): Option[IssueLabel] = { + def getIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int)( + implicit s: Session + ): Option[IssueLabel] = { IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) firstOption } @@ -70,7 +93,9 @@ * @param repos Tuple of the repository owner and the repository name * @return the count of the search result */ - def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*)(implicit s: Session): Int = { + def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*)( + implicit s: Session + ): Int = { Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first } @@ -82,69 +107,95 @@ * @param condition the search condition * @return the Map which contains issue count for each labels (key is label name, value is issue count) */ - def countIssueGroupByLabels(owner: String, repository: String, condition: IssueSearchCondition, - filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = { + def countIssueGroupByLabels( + owner: String, + repository: String, + condition: IssueSearchCondition, + filterUser: Map[String, String] + )(implicit s: Session): Map[String, Int] = { searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false) - .join(IssueLabels).on { case t1 ~ t2 => - t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) + .join(IssueLabels) + .on { + case t1 ~ t2 => + t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } - .join(Labels).on { case t1 ~ t2 ~ t3 => - t2.byLabel(t3.userName, t3.repositoryName, t3.labelId) + .join(Labels) + .on { + case t1 ~ t2 ~ t3 => + t2.byLabel(t3.userName, t3.repositoryName, t3.labelId) } - .groupBy { case t1 ~ t2 ~ t3 => - t3.labelName + .groupBy { + case t1 ~ t2 ~ t3 => + t3.labelName } - .map { case labelName ~ t => - labelName -> t.length + .map { + case labelName ~ t => + labelName -> t.length } - .list.toMap + .list + .toMap } /** - * Returns the Map which contains issue count for each priority. - * - * @param owner the repository owner - * @param repository the repository name - * @param condition the search condition - * @return the Map which contains issue count for each priority (key is priority name, value is issue count) - */ - def countIssueGroupByPriorities(owner: String, repository: String, condition: IssueSearchCondition, - filterUser: Map[String, String])(implicit s: Session): Map[String, Int] = { + * Returns the Map which contains issue count for each priority. + * + * @param owner the repository owner + * @param repository the repository name + * @param condition the search condition + * @return the Map which contains issue count for each priority (key is priority name, value is issue count) + */ + def countIssueGroupByPriorities( + owner: String, + repository: String, + condition: IssueSearchCondition, + filterUser: Map[String, String] + )(implicit s: Session): Map[String, Int] = { searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false) - .join(Priorities).on { case t1 ~ t2 => - t1.byPriority(t2.userName, t2.repositoryName, t2.priorityId) + .join(Priorities) + .on { + case t1 ~ t2 => + t1.byPriority(t2.userName, t2.repositoryName, t2.priorityId) } - .groupBy { case t1 ~ t2 => - t2.priorityName + .groupBy { + case t1 ~ t2 => + t2.priorityName } - .map { case priorityName ~ t => - priorityName -> t.length + .map { + case priorityName ~ t => + priorityName -> t.length } - .list.toMap + .list + .toMap } - def getCommitStatues(userName: String, repositoryName: String, issueId: Int)(implicit s: Session): Option[CommitStatusInfo] = { + def getCommitStatues(userName: String, repositoryName: String, issueId: Int)( + implicit s: Session + ): Option[CommitStatusInfo] = { val status = PullRequests .filter { pr => pr.userName === userName.bind && pr.repositoryName === repositoryName.bind && pr.issueId === issueId.bind } - .join(CommitStatuses).on { case pr ~ cs => - pr.userName === cs.userName && pr.repositoryName === cs.repositoryName && pr.commitIdTo === cs.commitId + .join(CommitStatuses) + .on { + case pr ~ cs => + pr.userName === cs.userName && pr.repositoryName === cs.repositoryName && pr.commitIdTo === cs.commitId } .list - if(status.nonEmpty){ + if (status.nonEmpty) { val (_, cs) = status.head - Some(CommitStatusInfo( - count = status.length, - successCount = status.count(_._2.state == CommitState.SUCCESS), - context = (if(status.length == 1) Some(cs.context) else None), - state = (if(status.length == 1) Some(cs.state) else None), - targetUrl = (if(status.length == 1) cs.targetUrl else None), - description = (if(status.length == 1) cs.description else None) - )) + Some( + CommitStatusInfo( + count = status.length, + successCount = status.count(_._2.state == CommitState.SUCCESS), + context = (if (status.length == 1) Some(cs.context) else None), + state = (if (status.length == 1) Some(cs.state) else None), + targetUrl = (if (status.length == 1) cs.targetUrl else None), + description = (if (status.length == 1) cs.description else None) + ) + ) } else { None } @@ -160,104 +211,154 @@ * @param repos Tuple of the repository owner and the repository name * @return the search result (list of tuples which contain issue, labels and comment count) */ - def searchIssue(condition: IssueSearchCondition, pullRequest: Boolean, offset: Int, limit: Int, repos: (String, String)*) - (implicit s: Session): List[IssueInfo] = { + def searchIssue( + condition: IssueSearchCondition, + pullRequest: Boolean, + offset: Int, + limit: Int, + repos: (String, String)* + )(implicit s: Session): List[IssueInfo] = { // get issues and comment count and labels val result = searchIssueQueryBase(condition, pullRequest, offset, limit, repos) - .joinLeft (IssueLabels) .on { case t1 ~ t2 ~ i ~ t3 => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } - .joinLeft (Labels) .on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t3.map(_.byLabel(t4.userName, t4.repositoryName, t4.labelId)) } - .joinLeft (Milestones) .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) } - .joinLeft (Priorities) .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t1.byPriority(t6.userName, t6.repositoryName, t6.priorityId) } - .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => i asc } - .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => - (t1, t2.commentCount, t4.map(_.labelId), t4.map(_.labelName), t4.map(_.color), t5.map(_.title), t6.map(_.priorityName)) - } - .list - .splitWith { (c1, c2) => c1._1.userName == c2._1.userName && c1._1.repositoryName == c2._1.repositoryName && c1._1.issueId == c2._1.issueId } + .joinLeft(IssueLabels) + .on { case t1 ~ t2 ~ i ~ t3 => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } + .joinLeft(Labels) + .on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t3.map(_.byLabel(t4.userName, t4.repositoryName, t4.labelId)) } + .joinLeft(Milestones) + .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) } + .joinLeft(Priorities) + .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t1.byPriority(t6.userName, t6.repositoryName, t6.priorityId) } + .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => i asc } + .map { + case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => + ( + t1, + t2.commentCount, + t4.map(_.labelId), + t4.map(_.labelName), + t4.map(_.color), + t5.map(_.title), + t6.map(_.priorityName) + ) + } + .list + .splitWith { (c1, c2) => + c1._1.userName == c2._1.userName && c1._1.repositoryName == c2._1.repositoryName && c1._1.issueId == c2._1.issueId + } - result.map { issues => issues.head match { - case (issue, commentCount, _, _, _, milestone, priority) => - IssueInfo(issue, - issues.flatMap { t => t._3.map (Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get))} toList, - milestone, - priority, - commentCount, - getCommitStatues(issue.userName, issue.repositoryName, issue.issueId)) - }} toList + result.map { issues => + issues.head match { + case (issue, commentCount, _, _, _, milestone, priority) => + IssueInfo( + issue, + issues.flatMap { t => + t._3.map(Label(issue.userName, issue.repositoryName, _, t._4.get, t._5.get)) + } toList, + milestone, + priority, + commentCount, + getCommitStatues(issue.userName, issue.repositoryName, issue.issueId) + ) + } + } toList } /** for api * @return (issue, issueUser, commentCount) */ - def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*) - (implicit s: Session): List[(Issue, Account)] = { + def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)( + implicit s: Session + ): List[(Issue, Account)] = { // get issues and comment count and labels searchIssueQueryBase(condition, false, offset, limit, repos) - .join(Accounts).on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName } + .join(Accounts) + .on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName } .sortBy { case t1 ~ t2 ~ i ~ t3 => i asc } - .map { case t1 ~ t2 ~ i ~ t3 => (t1, t3) } + .map { case t1 ~ t2 ~ i ~ t3 => (t1, t3) } .list } /** for api * @return (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) */ - def searchPullRequestByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*) - (implicit s: Session): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = { + def searchPullRequestByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)( + implicit s: Session + ): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = { // get issues and comment count and labels searchIssueQueryBase(condition, true, offset, limit, repos) - .join (PullRequests).on { case t1 ~ t2 ~ i ~ t3 => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) } - .join (Repositories).on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.byRepository(t1.userName, t1.repositoryName) } - .join (Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t1.openedUserName } - .join (Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t6.userName === t4.userName } - .joinLeft(Accounts ).on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t7.userName === t1.assignedUserName} + .join(PullRequests) + .on { case t1 ~ t2 ~ i ~ t3 => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) } + .join(Repositories) + .on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.byRepository(t1.userName, t1.repositoryName) } + .join(Accounts) + .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t5.userName === t1.openedUserName } + .join(Accounts) + .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t6.userName === t4.userName } + .joinLeft(Accounts) + .on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t7.userName === t1.assignedUserName } .sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => i asc } - .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => (t1, t5, t2.commentCount, t3, t4, t6, t7) } + .map { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => (t1, t5, t2.commentCount, t3, t4, t6, t7) } .list } - private def searchIssueQueryBase(condition: IssueSearchCondition, pullRequest: Boolean, offset: Int, limit: Int, repos: Seq[(String, String)]) - (implicit s: Session) = + private def searchIssueQueryBase( + condition: IssueSearchCondition, + pullRequest: Boolean, + offset: Int, + limit: Int, + repos: Seq[(String, String)] + )(implicit s: Session) = searchIssueQuery(repos, condition, pullRequest) - .join(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } - .sortBy { case (t1, t2) => t1.issueId desc } - .sortBy { case (t1, t2) => - condition.sort match { - case "created" => condition.direction match { - case "asc" => t1.registeredDate asc - case "desc" => t1.registeredDate desc - } - case "comments" => condition.direction match { - case "asc" => t2.commentCount asc - case "desc" => t2.commentCount desc - } - case "updated" => condition.direction match { - case "asc" => t1.updatedDate asc - case "desc" => t1.updatedDate desc - } - case "priority" => condition.direction match { - case "asc" => t2.priority asc - case "desc" => t2.priority desc - } - } + .join(IssueOutline) + .on { (t1, t2) => + t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } - .drop(offset).take(limit).zipWithIndex - + .sortBy { case (t1, t2) => t1.issueId desc } + .sortBy { + case (t1, t2) => + condition.sort match { + case "created" => + condition.direction match { + case "asc" => t1.registeredDate asc + case "desc" => t1.registeredDate desc + } + case "comments" => + condition.direction match { + case "asc" => t2.commentCount asc + case "desc" => t2.commentCount desc + } + case "updated" => + condition.direction match { + case "asc" => t1.updatedDate asc + case "desc" => t1.updatedDate desc + } + case "priority" => + condition.direction match { + case "asc" => t2.priority asc + case "desc" => t2.priority desc + } + } + } + .drop(offset) + .take(limit) + .zipWithIndex /** * Assembles query for conditional issue searching. */ - private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)(implicit s: Session) = + private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)( + implicit s: Session + ) = Issues filter { t1 => repos .map { case (owner, repository) => t1.byRepository(owner, repository) } - .foldLeft[Rep[Boolean]](false) ( _ || _ ) && - (t1.closed === (condition.state == "closed").bind) && - (t1.milestoneId.? isEmpty, condition.milestone == Some(None)) && - (t1.priorityId.? isEmpty, condition.priority == Some(None)) && - (t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) && - (t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && - (t1.pullRequest === pullRequest.bind) && + .foldLeft[Rep[Boolean]](false)(_ || _) && + (t1.closed === (condition.state == "closed").bind) && + (t1.milestoneId.? isEmpty, condition.milestone == Some(None)) && + (t1.priorityId.? isEmpty, condition.priority == Some(None)) && + (t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) && + (t1.openedUserName === condition.author.get.bind, condition.author.isDefined) && + (t1.pullRequest === pullRequest.bind) && // Milestone filter (Milestones filter { t2 => (t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) && @@ -266,7 +367,7 @@ // Priority filter (Priorities filter { t2 => (t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.priorityId)) && - (t2.priorityName === condition.priority.get.get.bind) + (t2.priorityName === condition.priority.get.get.bind) } exists, condition.priority.flatten.isDefined) && // Assignee filter (t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined) && @@ -277,7 +378,7 @@ (Labels filter { t3 => (t3.byRepository(t1.userName, t1.repositoryName)) && (t3.labelName inSetBind condition.labels) - } map(_.labelId))) + } map (_.labelId))) } exists, condition.labels.nonEmpty) && // Visibility filter (Repositories filter { t2 => @@ -288,41 +389,55 @@ (t1.userName inSetBind condition.groups, condition.groups.nonEmpty) && // Mentioned filter ((t1.openedUserName === condition.mentioned.get.bind) || t1.assignedUserName === condition.mentioned.get.bind || - (IssueComments filter { t2 => - (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind) - } exists), condition.mentioned.isDefined) + (IssueComments filter { t2 => + (t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) && (t2.commentedUserName === condition.mentioned.get.bind) + } exists), condition.mentioned.isDefined) } - def insertIssue(owner: String, repository: String, loginUser: String, title: String, content: Option[String], - assignedUserName: Option[String], milestoneId: Option[Int], priorityId: Option[Int], - isPullRequest: Boolean = false)(implicit s: Session): Int = { + def insertIssue( + owner: String, + repository: String, + loginUser: String, + title: String, + content: Option[String], + assignedUserName: Option[String], + milestoneId: Option[Int], + priorityId: Option[Int], + isPullRequest: Boolean = false + )(implicit s: Session): Int = { // next id number - sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE".as[Int] - .firstOption.filter { id => - Issues insert Issue( - owner, - repository, - id, - loginUser, - milestoneId, - priorityId, - assignedUserName, - title, - content, - false, - currentDate, - currentDate, - isPullRequest) + sql"SELECT ISSUE_ID + 1 FROM ISSUE_ID WHERE USER_NAME = $owner AND REPOSITORY_NAME = $repository FOR UPDATE" + .as[Int] + .firstOption + .filter { id => + Issues insert Issue( + owner, + repository, + id, + loginUser, + milestoneId, + priorityId, + assignedUserName, + title, + content, + false, + currentDate, + currentDate, + isPullRequest + ) - // increment issue id - IssueId - .filter(_.byPrimaryKey(owner, repository)) - .map(_.issueId) - .update(id) > 0 + // increment issue id + IssueId + .filter(_.byPrimaryKey(owner, repository)) + .map(_.issueId) + .update(id) > 0 } get } - def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)(implicit context: Context, s: Session): Int = { + def registerIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)( + implicit context: Context, + s: Session + ): Int = { if (insertComment) { IssueComments insert IssueComment( userName = owner, @@ -338,7 +453,10 @@ IssueLabels insert IssueLabel(owner, repository, issueId, labelId) } - def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)(implicit context: Context, s: Session): Int = { + def deleteIssueLabel(owner: String, repository: String, issueId: Int, labelId: Int, insertComment: Boolean = false)( + implicit context: Context, + s: Session + ): Int = { if (insertComment) { IssueComments insert IssueComment( userName = owner, @@ -351,31 +469,48 @@ updatedDate = currentDate ) } - IssueLabels filter(_.byPrimaryKey(owner, repository, issueId, labelId)) delete + IssueLabels filter (_.byPrimaryKey(owner, repository, issueId, labelId)) delete } - def createComment(owner: String, repository: String, loginUser: String, - issueId: Int, content: String, action: String)(implicit s: Session): Int = { + def createComment( + owner: String, + repository: String, + loginUser: String, + issueId: Int, + content: String, + action: String + )(implicit s: Session): Int = { Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate) IssueComments returning IssueComments.map(_.commentId) insert IssueComment( - userName = owner, - repositoryName = repository, - issueId = issueId, - action = action, + userName = owner, + repositoryName = repository, + issueId = issueId, + action = action, commentedUserName = loginUser, - content = content, - registeredDate = currentDate, - updatedDate = currentDate) + content = content, + registeredDate = currentDate, + updatedDate = currentDate + ) } - def updateIssue(owner: String, repository: String, issueId: Int, title: String, content: Option[String])(implicit s: Session): Int = { + def updateIssue(owner: String, repository: String, issueId: Int, title: String, content: Option[String])( + implicit s: Session + ): Int = { Issues - .filter (_.byPrimaryKey(owner, repository, issueId)) - .map { t => (t.title, t.content.?, t.updatedDate) } + .filter(_.byPrimaryKey(owner, repository, issueId)) + .map { t => + (t.title, t.content.?, t.updatedDate) + } .update(title, content, currentDate) } - def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String], insertComment: Boolean = false)(implicit context: Context, s: Session): Int = { + def updateAssignedUserName( + owner: String, + repository: String, + issueId: Int, + assignedUserName: Option[String], + insertComment: Boolean = false + )(implicit context: Context, s: Session): Int = { if (insertComment) { val oldAssigned = getIssue(owner, repository, s"${issueId}").get.assignedUserName.getOrElse("Not assigned") val assigned = assignedUserName.getOrElse("Not assigned") @@ -390,13 +525,26 @@ updatedDate = currentDate ) } - Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.assignedUserName?, t.updatedDate)).update(assignedUserName, currentDate) + Issues + .filter(_.byPrimaryKey(owner, repository, issueId)) + .map(t => (t.assignedUserName ?, t.updatedDate)) + .update(assignedUserName, currentDate) } - def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int], insertComment: Boolean = false)(implicit context: Context, s: Session): Int = { + def updateMilestoneId( + owner: String, + repository: String, + issueId: Int, + milestoneId: Option[Int], + insertComment: Boolean = false + )(implicit context: Context, s: Session): Int = { if (insertComment) { - val oldMilestoneName = getIssue(owner, repository, s"${issueId}").get.milestoneId.map(getMilestone(owner, repository, _).map(_.title).getOrElse("Unknown milestone")).getOrElse("No milestone") - val milestoneName = milestoneId.map(getMilestone(owner, repository, _).map(_.title).getOrElse("Unknown milestone")).getOrElse("No milestone") + val oldMilestoneName = getIssue(owner, repository, s"${issueId}").get.milestoneId + .map(getMilestone(owner, repository, _).map(_.title).getOrElse("Unknown milestone")) + .getOrElse("No milestone") + val milestoneName = milestoneId + .map(getMilestone(owner, repository, _).map(_.title).getOrElse("Unknown milestone")) + .getOrElse("No milestone") IssueComments insert IssueComment( userName = owner, repositoryName = repository, @@ -408,13 +556,26 @@ updatedDate = currentDate ) } - Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.milestoneId?, t.updatedDate)).update(milestoneId, currentDate) + Issues + .filter(_.byPrimaryKey(owner, repository, issueId)) + .map(t => (t.milestoneId ?, t.updatedDate)) + .update(milestoneId, currentDate) } - def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int], insertComment: Boolean = false)(implicit context: Context, s: Session): Int = { + def updatePriorityId( + owner: String, + repository: String, + issueId: Int, + priorityId: Option[Int], + insertComment: Boolean = false + )(implicit context: Context, s: Session): Int = { if (insertComment) { - val oldPriorityName = getIssue(owner, repository, s"${issueId}").get.priorityId.map(getPriority(owner, repository, _).map(_.priorityName).getOrElse("Unknown priority")).getOrElse("No priority") - val priorityName = priorityId.map(getPriority(owner, repository, _).map(_.priorityName).getOrElse("Unknown priority")).getOrElse("No priority") + val oldPriorityName = getIssue(owner, repository, s"${issueId}").get.priorityId + .map(getPriority(owner, repository, _).map(_.priorityName).getOrElse("Unknown priority")) + .getOrElse("No priority") + val priorityName = priorityId + .map(getPriority(owner, repository, _).map(_.priorityName).getOrElse("Unknown priority")) + .getOrElse("No priority") IssueComments insert IssueComment( userName = owner, repositoryName = repository, @@ -426,7 +587,10 @@ updatedDate = currentDate ) } - Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.priorityId?, t.updatedDate)).update(priorityId, currentDate) + Issues + .filter(_.byPrimaryKey(owner, repository, issueId)) + .map(t => (t.priorityId ?, t.updatedDate)) + .update(priorityId, currentDate) } def updateComment(issueId: Int, commentId: Int, content: String)(implicit s: Session): Int = { @@ -447,7 +611,10 @@ } def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session): Int = { - Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.closed, t.updatedDate)).update(closed, currentDate) + Issues + .filter(_.byPrimaryKey(owner, repository, issueId)) + .map(t => (t.closed, t.updatedDate)) + .update(closed, currentDate) } /** @@ -458,69 +625,100 @@ * @param query the keywords separated by whitespace. * @return issues with comment count and matched content of issue or comment */ - def searchIssuesByKeyword(owner: String, repository: String, query: String)(implicit s: Session): List[(Issue, Int, String)] = { + def searchIssuesByKeyword(owner: String, repository: String, query: String)( + implicit s: Session + ): List[(Issue, Int, String)] = { //import slick.driver.JdbcDriver.likeEncode val keywords = splitWords(query.toLowerCase) // Search Issue val issues = Issues .filter(_.byRepository(owner, repository)) - .join(IssueOutline).on { case (t1, t2) => - t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) + .join(IssueOutline) + .on { + case (t1, t2) => + t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - keywords.map { keyword => - (t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) || - (t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) - } .reduceLeft(_ && _) + .filter { + case (t1, t2) => + keywords + .map { keyword => + (t1.title.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) || + (t1.content.toLowerCase like (s"%${likeEncode(keyword)}%", '^')) + } + .reduceLeft(_ && _) } - .map { case (t1, t2) => - (t1, 0, t1.content.?, t2.commentCount) + .map { + case (t1, t2) => + (t1, 0, t1.content.?, t2.commentCount) } // Search IssueComment val comments = IssueComments .filter(_.byRepository(owner, repository)) - .join(Issues).on { case (t1, t2) => - t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) + .join(Issues) + .on { + case (t1, t2) => + t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } - .join(IssueOutline).on { case ((t1, t2), t3) => - t2.byIssue(t3.userName, t3.repositoryName, t3.issueId) + .join(IssueOutline) + .on { + case ((t1, t2), t3) => + t2.byIssue(t3.userName, t3.repositoryName, t3.issueId) } - .filter { case ((t1, t2), t3) => - keywords.map { query => - t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^') - }.reduceLeft(_ && _) + .filter { + case ((t1, t2), t3) => + keywords + .map { query => + t1.content.toLowerCase like (s"%${likeEncode(query)}%", '^') + } + .reduceLeft(_ && _) } - .map { case ((t1, t2), t3) => - (t2, t1.commentId, t1.content.?, t3.commentCount) + .map { + case ((t1, t2), t3) => + (t2, t1.commentId, t1.content.?, t3.commentCount) } - issues.union(comments).sortBy { case (issue, commentId, _, _) => - issue.issueId -> commentId - }.list.splitWith { case ((issue1, _, _, _), (issue2, _, _, _)) => - issue1.issueId == issue2.issueId - }.map { _.head match { - case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse("")) + issues + .union(comments) + .sortBy { + case (issue, commentId, _, _) => + issue.issueId -> commentId } - }.toList + .list + .splitWith { + case ((issue1, _, _, _), (issue2, _, _, _)) => + issue1.issueId == issue2.issueId + } + .map { + _.head match { + case (issue, _, content, commentCount) => (issue, commentCount, content.getOrElse("")) + } + } + .toList } - def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)(implicit s: Session): Unit = { + def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String)( + implicit s: Session + ): Unit = { extractCloseId(message).foreach { issueId => - for(issue <- getIssue(owner, repository, issueId) if !issue.closed){ + for (issue <- getIssue(owner, repository, issueId) if !issue.closed) { createComment(owner, repository, userName, issue.issueId, "Close", "close") updateClosed(owner, repository, issue.issueId, true) } } } - def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(implicit s: Session): Unit = { + def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)( + implicit s: Session + ): Unit = { extractIssueId(message).foreach { issueId => val content = fromIssue.issueId + ":" + fromIssue.title - if(getIssue(owner, repository, issueId).isDefined){ + if (getIssue(owner, repository, issueId).isDefined) { // Not add if refer comment already exist. - if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) { + if (!getComments(owner, repository, issueId.toInt).exists { x => + x.action == "refer" && x.content == content + }) { createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer") } } @@ -529,8 +727,9 @@ def createIssueComment(owner: String, repository: String, commit: CommitInfo)(implicit s: Session): Unit = { extractIssueId(commit.fullMessage).foreach { issueId => - if(getIssue(owner, repository, issueId).isDefined){ - val userName = getAccountByMailAddress(commit.committerEmailAddress).map(_.userName).getOrElse(commit.committerName) + if (getIssue(owner, repository, issueId).isDefined) { + val userName = + getAccountByMailAddress(commit.committerEmailAddress).map(_.userName).getOrElse(commit.committerName) createComment(owner, repository, userName, issueId.toInt, commit.fullMessage + " " + commit.id, "commit") } } @@ -539,13 +738,13 @@ def getAssignableUserNames(owner: String, repository: String)(implicit s: Session): List[String] = { (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)) ::: (getAccountByUserName(owner) match { - case Some(x) if x.isGroupAccount => - getGroupMembers(owner).map(_.userName) - case Some(_) => - List(owner) - case None => - Nil - })).distinct.sorted + case Some(x) if x.isGroupAccount => + getGroupMembers(owner).map(_.userName) + case Some(_) => + List(owner) + case None => + Nil + })).distinct.sorted } } @@ -556,61 +755,63 @@ val IssueLimit = 25 case class IssueSearchCondition( - labels: Set[String] = Set.empty, - milestone: Option[Option[String]] = None, - priority: Option[Option[String]] = None, - author: Option[String] = None, - assigned: Option[Option[String]] = None, - mentioned: Option[String] = None, - state: String = "open", - sort: String = "created", - direction: String = "desc", - visibility: Option[String] = None, - groups: Set[String] = Set.empty){ + labels: Set[String] = Set.empty, + milestone: Option[Option[String]] = None, + priority: Option[Option[String]] = None, + author: Option[String] = None, + assigned: Option[Option[String]] = None, + mentioned: Option[String] = None, + state: String = "open", + sort: String = "created", + direction: String = "desc", + visibility: Option[String] = None, + groups: Set[String] = Set.empty + ) { def isEmpty: Boolean = { labels.isEmpty && milestone.isEmpty && author.isEmpty && assigned.isEmpty && - state == "open" && sort == "created" && direction == "desc" && visibility.isEmpty + state == "open" && sort == "created" && direction == "desc" && visibility.isEmpty } def nonEmpty: Boolean = !isEmpty - def toFilterString: String = ( - List( - Some(s"is:${state}"), - author.map(author => s"author:${author}"), - assigned.map(assignee => s"assignee:${assignee}"), - mentioned.map(mentioned => s"mentions:${mentioned}") - ).flatten ++ - labels.map(label => s"label:${label}") ++ - List( - milestone.map { - case Some(x) => s"milestone:${x}" - case None => "no:milestone" - }, - priority.map { - case Some(x) => s"priority:${x}" - case None => "no:priority" - }, - (sort, direction) match { - case ("created" , "desc") => None - case ("created" , "asc" ) => Some("sort:created-asc") - case ("comments", "desc") => Some("sort:comments-desc") - case ("comments", "asc" ) => Some("sort:comments-asc") - case ("updated" , "desc") => Some("sort:updated-desc") - case ("updated" , "asc" ) => Some("sort:updated-asc") - case ("priority", "desc") => Some("sort:priority-desc") - case ("priority", "asc" ) => Some("sort:priority-asc") - case x => throw new MatchError(x) - }, - visibility.map(visibility => s"visibility:${visibility}") - ).flatten ++ - groups.map(group => s"group:${group}") - ).mkString(" ") + def toFilterString: String = + ( + List( + Some(s"is:${state}"), + author.map(author => s"author:${author}"), + assigned.map(assignee => s"assignee:${assignee}"), + mentioned.map(mentioned => s"mentions:${mentioned}") + ).flatten ++ + labels.map(label => s"label:${label}") ++ + List( + milestone.map { + case Some(x) => s"milestone:${x}" + case None => "no:milestone" + }, + priority.map { + case Some(x) => s"priority:${x}" + case None => "no:priority" + }, + (sort, direction) match { + case ("created", "desc") => None + case ("created", "asc") => Some("sort:created-asc") + case ("comments", "desc") => Some("sort:comments-desc") + case ("comments", "asc") => Some("sort:comments-asc") + case ("updated", "desc") => Some("sort:updated-desc") + case ("updated", "asc") => Some("sort:updated-asc") + case ("priority", "desc") => Some("sort:priority-desc") + case ("priority", "asc") => Some("sort:priority-asc") + case x => throw new MatchError(x) + }, + visibility.map(visibility => s"visibility:${visibility}") + ).flatten ++ + groups.map(group => s"group:${group}") + ).mkString(" ") def toURL: String = "?" + List( - if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))), + if (labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))), milestone.map { case Some(x) => "milestone=" + urlEncode(x) case None => "milestone=none" @@ -619,17 +820,17 @@ case Some(x) => "priority=" + urlEncode(x) case None => "priority=none" }, - author .map(x => "author=" + urlEncode(x)), + author.map(x => "author=" + urlEncode(x)), assigned.map { - case Some(x) => "assigned=" + urlEncode(x) + case Some(x) => "assigned=" + urlEncode(x) case None => "assigned=none" }, mentioned.map(x => "mentioned=" + urlEncode(x)), - Some("state=" + urlEncode(state)), - Some("sort=" + urlEncode(sort)), + Some("state=" + urlEncode(state)), + Some("sort=" + urlEncode(sort)), Some("direction=" + urlEncode(direction)), visibility.map(x => "visibility=" + urlEncode(x)), - if(groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(","))) + if (groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(","))) ).flatten.mkString("&") } @@ -638,7 +839,7 @@ private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = { val value = request.getParameter(name) - if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value) + if (value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value) } /** @@ -661,23 +862,38 @@ case x => Some(x) }, param(request, "mentioned"), - param(request, "state", Seq("open", "closed")).getOrElse("open"), - param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"), + param(request, "state", Seq("open", "closed")).getOrElse("open"), + param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"), param(request, "direction", Seq("asc", "desc")).getOrElse("desc"), param(request, "visibility"), param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty) ) - def page(request: HttpServletRequest) = try { - val i = param(request, "page").getOrElse("1").toInt - if(i <= 0) 1 else i - } catch { - case e: NumberFormatException => 1 - } + def page(request: HttpServletRequest) = + try { + val i = param(request, "page").getOrElse("1").toInt + if (i <= 0) 1 else i + } catch { + case e: NumberFormatException => 1 + } } - case class CommitStatusInfo(count: Int, successCount: Int, context: Option[String], state: Option[CommitState], targetUrl: Option[String], description: Option[String]) + case class CommitStatusInfo( + count: Int, + successCount: Int, + context: Option[String], + state: Option[CommitState], + targetUrl: Option[String], + description: Option[String] + ) - case class IssueInfo(issue: Issue, labels: List[Label], milestone: Option[String], priority: Option[String], commentCount: Int, status:Option[CommitStatusInfo]) + case class IssueInfo( + issue: Issue, + labels: List[Label], + milestone: Option[String], + priority: Option[String], + commentCount: Int, + status: Option[CommitStatusInfo] + ) } diff --git a/src/main/scala/gitbucket/core/service/LabelsService.scala b/src/main/scala/gitbucket/core/service/LabelsService.scala index e95fa06..4bbd5d8 100644 --- a/src/main/scala/gitbucket/core/service/LabelsService.scala +++ b/src/main/scala/gitbucket/core/service/LabelsService.scala @@ -17,18 +17,20 @@ def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int = { Labels returning Labels.map(_.labelId) insert Label( - userName = owner, + userName = owner, repositoryName = repository, - labelName = labelName, - color = color + labelName = labelName, + color = color ) } - def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String) - (implicit s: Session): Unit = - Labels.filter(_.byPrimaryKey(owner, repository, labelId)) - .map(t => t.labelName -> t.color) - .update(labelName, color) + def updateLabel(owner: String, repository: String, labelId: Int, labelName: String, color: String)( + implicit s: Session + ): Unit = + Labels + .filter(_.byPrimaryKey(owner, repository, labelId)) + .map(t => t.labelName -> t.color) + .update(labelName, color) def deleteLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Unit = { IssueLabels.filter(_.byLabel(owner, repository, labelId)).delete diff --git a/src/main/scala/gitbucket/core/service/MergeService.scala b/src/main/scala/gitbucket/core/service/MergeService.scala index ed2b4b4..0ea90f7 100644 --- a/src/main/scala/gitbucket/core/service/MergeService.scala +++ b/src/main/scala/gitbucket/core/service/MergeService.scala @@ -31,7 +31,12 @@ * Returns Some(true) if conflict will be caused. * Returns None if cache has not created yet. */ - def checkConflictCache(userName: String, repositoryName: String, branch: String, issueId: Int): Option[Option[String]] = { + def checkConflictCache( + userName: String, + repositoryName: String, + branch: String, + issueId: Int + ): Option[Option[String]] = { using(Git.open(getRepositoryDir(userName, repositoryName))) { git => new MergeCacheInfo(git, branch, issueId).checkConflictCache() } @@ -43,7 +48,13 @@ } /** rebase to the head of the pull request branch */ - def rebasePullRequest(git: Git, branch: String, issueId: Int, commits: Seq[RevCommit], committer: PersonIdent): Unit = { + def rebasePullRequest( + git: Git, + branch: String, + issueId: Int, + commits: Seq[RevCommit], + committer: PersonIdent + ): Unit = { new MergeCacheInfo(git, branch, issueId).rebase(committer, commits) } @@ -53,8 +64,15 @@ } /** fetch remote branch to my repository refs/pull/{issueId}/head */ - def fetchAsPullRequest(userName: String, repositoryName: String, requestUserName: String, requestRepositoryName: String, requestBranch:String, issueId:Int){ - using(Git.open(getRepositoryDir(userName, repositoryName))){ git => + def fetchAsPullRequest( + userName: String, + repositoryName: String, + requestUserName: String, + requestRepositoryName: String, + requestBranch: String, + issueId: Int + ) { + using(Git.open(getRepositoryDir(userName, repositoryName))) { git => git.fetch .setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString) .setRefSpecs(new RefSpec(s"refs/heads/${requestBranch}:refs/pull/${issueId}/head")) @@ -65,8 +83,14 @@ /** * Checks whether conflict will be caused in merging. Returns true if conflict will be caused. */ - def tryMergeRemote(localUserName: String, localRepositoryName: String, localBranch: String, - remoteUserName: String, remoteRepositoryName: String, remoteBranch: String): Either[String, (ObjectId, ObjectId, ObjectId)] = { + def tryMergeRemote( + localUserName: String, + localRepositoryName: String, + localBranch: String, + remoteUserName: String, + remoteRepositoryName: String, + remoteBranch: String + ): Either[String, (ObjectId, ObjectId, ObjectId)] = { using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git => val remoteRefName = s"refs/heads/${remoteBranch}" val tmpRefName = s"refs/remote-temp/${remoteUserName}/${remoteRepositoryName}/${remoteBranch}" @@ -74,15 +98,15 @@ try { // fetch objects from origin repository branch git.fetch - .setRemote(getRepositoryDir(remoteUserName, remoteRepositoryName).toURI.toString) - .setRefSpecs(refSpec) - .call + .setRemote(getRepositoryDir(remoteUserName, remoteRepositoryName).toURI.toString) + .setRefSpecs(refSpec) + .call // merge conflict check val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true) val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${localBranch}") val mergeTip = git.getRepository.resolve(tmpRefName) try { - if(merger.merge(mergeBaseTip, mergeTip)){ + if (merger.merge(mergeBaseTip, mergeTip)) { Right((merger.getResultTreeId, mergeBaseTip, mergeTip)) } else { Left(createConflictMessage(mergeTip, mergeBaseTip, merger)) @@ -101,45 +125,73 @@ /** * Checks whether conflict will be caused in merging. Returns `Some(errorMessage)` if conflict will be caused. */ - def checkConflict(userName: String, repositoryName: String, branch: String, - requestUserName: String, requestRepositoryName: String, requestBranch: String): Option[String] = + def checkConflict( + userName: String, + repositoryName: String, + branch: String, + requestUserName: String, + requestRepositoryName: String, + requestBranch: String + ): Option[String] = tryMergeRemote(userName, repositoryName, branch, requestUserName, requestRepositoryName, requestBranch).left.toOption - def pullRemote(localUserName: String, localRepositoryName: String, localBranch: String, - remoteUserName: String, remoteRepositoryName: String, remoteBranch: String, - loginAccount: Account, message: String): Option[ObjectId] = { - tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map { case (newTreeId, oldBaseId, oldHeadId) => - using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git => - val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) - val newCommit = Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId)) - Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge")) - } - oldBaseId + def pullRemote( + localUserName: String, + localRepositoryName: String, + localBranch: String, + remoteUserName: String, + remoteRepositoryName: String, + remoteBranch: String, + loginAccount: Account, + message: String + ): Option[ObjectId] = { + tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map { + case (newTreeId, oldBaseId, oldHeadId) => + using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git => + val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress) + val newCommit = + Util.createMergeCommit(git.getRepository, newTreeId, committer, message, Seq(oldBaseId, oldHeadId)) + Util.updateRefs(git.getRepository, s"refs/heads/${localBranch}", newCommit, false, committer, Some("merge")) + } + oldBaseId }.toOption } } -object MergeService{ +object MergeService { - object Util{ + object Util { // return merge commit id - def createMergeCommit(repository: Repository, treeId: ObjectId, committer: PersonIdent, message: String, parents: Seq[ObjectId]): ObjectId = { + def createMergeCommit( + repository: Repository, + treeId: ObjectId, + committer: PersonIdent, + message: String, + parents: Seq[ObjectId] + ): ObjectId = { val mergeCommit = new CommitBuilder() mergeCommit.setTreeId(treeId) - mergeCommit.setParentIds(parents:_*) + mergeCommit.setParentIds(parents: _*) mergeCommit.setAuthor(committer) mergeCommit.setCommitter(committer) mergeCommit.setMessage(message) // insertObject and got mergeCommit Object Id - using(repository.newObjectInserter){ inserter => + using(repository.newObjectInserter) { inserter => val mergeCommitId = inserter.insert(mergeCommit) inserter.flush() mergeCommitId } } - def updateRefs(repository: Repository, ref: String, newObjectId: ObjectId, force: Boolean, committer: PersonIdent, refLogMessage: Option[String] = None): Unit = { + def updateRefs( + repository: Repository, + ref: String, + newObjectId: ObjectId, + force: Boolean, + committer: PersonIdent, + refLogMessage: Option[String] = None + ): Unit = { val refUpdate = repository.updateRef(ref) refUpdate.setNewObjectId(newObjectId) refUpdate.setForceUpdate(force) @@ -149,56 +201,58 @@ } } - class MergeCacheInfo(git: Git, branch: String, issueId: Int){ + class MergeCacheInfo(git: Git, branch: String, issueId: Int) { private val repository = git.getRepository - private val mergedBranchName = s"refs/pull/${issueId}/merge" + private val mergedBranchName = s"refs/pull/${issueId}/merge" private val conflictedBranchName = s"refs/pull/${issueId}/conflict" lazy val mergeBaseTip = repository.resolve(s"refs/heads/${branch}") - lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head") + lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head") def checkConflictCache(): Option[Option[String]] = { - Option(repository.resolve(mergedBranchName)).flatMap { merged => - if(parseCommit(merged).getParents().toSet == Set( mergeBaseTip, mergeTip )){ + Option(repository.resolve(mergedBranchName)) + .flatMap { merged => + if (parseCommit(merged).getParents().toSet == Set(mergeBaseTip, mergeTip)) { // merged branch exists Some(None) } else { None } - }.orElse(Option(repository.resolve(conflictedBranchName)).flatMap{ conflicted => - val commit = parseCommit(conflicted) - if(commit.getParents().toSet == Set( mergeBaseTip, mergeTip )){ - // conflict branch exists - Some(Some(commit.getFullMessage)) - } else { - None } - }) + .orElse(Option(repository.resolve(conflictedBranchName)).flatMap { conflicted => + val commit = parseCommit(conflicted) + if (commit.getParents().toSet == Set(mergeBaseTip, mergeTip)) { + // conflict branch exists + Some(Some(commit.getFullMessage)) + } else { + None + } + }) } - def checkConflict(): Option[String] ={ + def checkConflict(): Option[String] = { checkConflictCache.getOrElse(checkConflictForce) } - def checkConflictForce(): Option[String] ={ + def checkConflictForce(): Option[String] = { val merger = MergeStrategy.RECURSIVE.newMerger(repository, true) val conflicted = try { !merger.merge(mergeBaseTip, mergeTip) } catch { case e: NoMergeBaseException => true } - val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip )) + val mergeTipCommit = using(new RevWalk(repository))(_.parseCommit(mergeTip)) val committer = mergeTipCommit.getCommitterIdent - def _updateBranch(treeId: ObjectId, message: String, branchName: String){ + def _updateBranch(treeId: ObjectId, message: String, branchName: String) { // creates merge commit val mergeCommitId = createMergeCommit(treeId, committer, message) Util.updateRefs(repository, branchName, mergeCommitId, true, committer) } - if(!conflicted){ + if (!conflicted) { _updateBranch(merger.getResultTreeId, s"Merge ${mergeTip.name} into ${mergeBaseTip.name}", mergedBranchName) git.branchDelete().setForce(true).setBranchNames(conflictedBranchName).call() None @@ -212,7 +266,7 @@ // update branch from cache def merge(message: String, committer: PersonIdent) = { - if(checkConflict().isDefined){ + if (checkConflict().isDefined) { throw new RuntimeException("This pull request can't merge automatically.") } val mergeResultCommit = parseCommit(Option(repository.resolve(mergedBranchName)).getOrElse { @@ -225,7 +279,7 @@ } def rebase(committer: PersonIdent, commits: Seq[RevCommit]): Unit = { - if(checkConflict().isDefined){ + if (checkConflict().isDefined) { throw new RuntimeException("This pull request can't merge automatically.") } @@ -242,10 +296,10 @@ newCommit } - val mergeBaseTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeBaseTip )) + val mergeBaseTipCommit = using(new RevWalk(repository))(_.parseCommit(mergeBaseTip)) var previousId = mergeBaseTipCommit.getId - using(repository.newObjectInserter){ inserter => + using(repository.newObjectInserter) { inserter => commits.foreach { commit => val nextCommit = _cloneCommit(commit, previousId, mergeBaseTipCommit.getId) previousId = inserter.insert(nextCommit) @@ -257,12 +311,12 @@ } def squash(message: String, committer: PersonIdent): Unit = { - if(checkConflict().isDefined){ + if (checkConflict().isDefined) { throw new RuntimeException("This pull request can't merge automatically.") } - val mergeBaseTipCommit = using(new RevWalk( repository ))(_.parseCommit(mergeBaseTip)) - val mergeBranchHeadCommit = using(new RevWalk( repository ))(_.parseCommit(repository.resolve(mergedBranchName))) + val mergeBaseTipCommit = using(new RevWalk(repository))(_.parseCommit(mergeBaseTip)) + val mergeBranchHeadCommit = using(new RevWalk(repository))(_.parseCommit(repository.resolve(mergedBranchName))) // Create squash commit val mergeCommit = new CommitBuilder() @@ -273,7 +327,7 @@ mergeCommit.setMessage(message) // insertObject and got squash commit Object Id - val newCommitId = using(repository.newObjectInserter){ inserter => + val newCommitId = using(repository.newObjectInserter) { inserter => val newCommitId = inserter.insert(mergeCommit) inserter.flush() newCommitId @@ -282,14 +336,21 @@ Util.updateRefs(repository, mergedBranchName, newCommitId, true, committer) // rebase to squash commit - Util.updateRefs(repository, s"refs/heads/${branch}", repository.resolve(mergedBranchName), false, committer, Some("squashed")) + Util.updateRefs( + repository, + s"refs/heads/${branch}", + repository.resolve(mergedBranchName), + false, + committer, + Some("squashed") + ) } // return treeId private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) = Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip)) - private def parseCommit(id: ObjectId) = using(new RevWalk( repository ))(_.parseCommit(id)) + private def parseCommit(id: ObjectId) = using(new RevWalk(repository))(_.parseCommit(id)) } diff --git a/src/main/scala/gitbucket/core/service/MilestonesService.scala b/src/main/scala/gitbucket/core/service/MilestonesService.scala index 276aa7c..ae1ce1e 100644 --- a/src/main/scala/gitbucket/core/service/MilestonesService.scala +++ b/src/main/scala/gitbucket/core/service/MilestonesService.scala @@ -7,22 +7,27 @@ trait MilestonesService { - def createMilestone(owner: String, repository: String, title: String, description: Option[String], - dueDate: Option[java.util.Date])(implicit s: Session): Unit = + def createMilestone( + owner: String, + repository: String, + title: String, + description: Option[String], + dueDate: Option[java.util.Date] + )(implicit s: Session): Unit = Milestones insert Milestone( - userName = owner, + userName = owner, repositoryName = repository, - title = title, - description = description, - dueDate = dueDate, - closedDate = None + title = title, + description = description, + dueDate = dueDate, + closedDate = None ) def updateMilestone(milestone: Milestone)(implicit s: Session): Unit = Milestones - .filter (t => t.byPrimaryKey(milestone.userName, milestone.repositoryName, milestone.milestoneId)) - .map (t => (t.title, t.description, t.dueDate, t.closedDate)) - .update (milestone.title, milestone.description, milestone.dueDate, milestone.closedDate) + .filter(t => t.byPrimaryKey(milestone.userName, milestone.repositoryName, milestone.milestoneId)) + .map(t => (t.title, t.description, t.dueDate, t.closedDate)) + .update(milestone.title, milestone.description, milestone.dueDate, milestone.closedDate) def openMilestone(milestone: Milestone)(implicit s: Session): Unit = updateMilestone(milestone.copy(closedDate = None)) @@ -38,20 +43,33 @@ def getMilestone(owner: String, repository: String, milestoneId: Int)(implicit s: Session): Option[Milestone] = Milestones.filter(_.byPrimaryKey(owner, repository, milestoneId)).firstOption - def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = { + def getMilestonesWithIssueCount(owner: String, repository: String)( + implicit s: Session + ): List[(Milestone, Int, Int)] = { val counts = Issues - .filter { t => t.byRepository(owner, repository) && (t.milestoneId.? isDefined) } - .groupBy { t => t.milestoneId -> t.closed } - .map { case (t1, t2) => t1._1 -> t1._2 -> t2.length } + .filter { t => + t.byRepository(owner, repository) && (t.milestoneId.? isDefined) + } + .groupBy { t => + t.milestoneId -> t.closed + } + .map { case (t1, t2) => t1._1 -> t1._2 -> t2.length } .list .toMap getMilestones(owner, repository).map { milestone => - (milestone, counts.getOrElse((milestone.milestoneId, false), 0), counts.getOrElse((milestone.milestoneId, true), 0)) + ( + milestone, + counts.getOrElse((milestone.milestoneId, false), 0), + counts.getOrElse((milestone.milestoneId, true), 0) + ) } } def getMilestones(owner: String, repository: String)(implicit s: Session): List[Milestone] = - Milestones.filter(_.byRepository(owner, repository)).sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)).list + Milestones + .filter(_.byRepository(owner, repository)) + .sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)) + .list } diff --git a/src/main/scala/gitbucket/core/service/OpenIDConnectService.scala b/src/main/scala/gitbucket/core/service/OpenIDConnectService.scala index c767111..2b60dce 100644 --- a/src/main/scala/gitbucket/core/service/OpenIDConnectService.scala +++ b/src/main/scala/gitbucket/core/service/OpenIDConnectService.scala @@ -20,8 +20,8 @@ import scala.collection.JavaConverters.{asScalaSet, mapAsJavaMap} /** - * Service class for the OpenID Connect authentication. - */ + * Service class for the OpenID Connect authentication. + */ trait OpenIDConnectService { self: AccountFederationService => @@ -29,22 +29,17 @@ private val JWK_REQUEST_TIMEOUT = 5000 - private val OIDC_SCOPE = new Scope( - OIDCScopeValue.OPENID, - OIDCScopeValue.EMAIL, - OIDCScopeValue.PROFILE) + private val OIDC_SCOPE = new Scope(OIDCScopeValue.OPENID, OIDCScopeValue.EMAIL, OIDCScopeValue.PROFILE) /** - * Obtain the OIDC metadata from discovery and create an authentication request. - * - * @param issuer Issuer, used to construct the discovery endpoint URL, e.g. https://accounts.google.com - * @param clientID Client ID (given by the issuer) - * @param redirectURI Redirect URI - * @return Authentication request - */ - def createOIDCAuthenticationRequest(issuer: Issuer, - clientID: ClientID, - redirectURI: URI): AuthenticationRequest = { + * Obtain the OIDC metadata from discovery and create an authentication request. + * + * @param issuer Issuer, used to construct the discovery endpoint URL, e.g. https://accounts.google.com + * @param clientID Client ID (given by the issuer) + * @param redirectURI Redirect URI + * @return Authentication request + */ + def createOIDCAuthenticationRequest(issuer: Issuer, clientID: ClientID, redirectURI: URI): AuthenticationRequest = { val metadata = OIDCProviderMetadata.resolve(issuer) new AuthenticationRequest( metadata.getAuthorizationEndpointURI, @@ -53,29 +48,38 @@ clientID, redirectURI, new State(), - new Nonce()) + new Nonce() + ) } /** - * Proceed the OpenID Connect authentication. - * - * @param params Query parameters of the authentication response - * @param redirectURI Redirect URI - * @param state State saved in the session - * @param nonce Nonce saved in the session - * @param oidc OIDC settings - * @return ID token - */ - def authenticate(params: Map[String, String], - redirectURI: URI, - state: State, - nonce: Nonce, - oidc: SystemSettingsService.OIDC)(implicit s: Session): Option[Account] = + * Proceed the OpenID Connect authentication. + * + * @param params Query parameters of the authentication response + * @param redirectURI Redirect URI + * @param state State saved in the session + * @param nonce Nonce saved in the session + * @param oidc OIDC settings + * @return ID token + */ + def authenticate( + params: Map[String, String], + redirectURI: URI, + state: State, + nonce: Nonce, + oidc: SystemSettingsService.OIDC + )(implicit s: Session): Option[Account] = validateOIDCAuthenticationResponse(params, state, redirectURI) flatMap { authenticationResponse => obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap { claims => Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match { case Seq(Some(email), preferredUsername, name) => - getOrCreateFederatedUser(claims.getIssuer.getValue, claims.getSubject.getValue, email, preferredUsername, name) + getOrCreateFederatedUser( + claims.getIssuer.getValue, + claims.getSubject.getValue, + email, + preferredUsername, + name + ) case _ => logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}") None @@ -84,14 +88,18 @@ } /** - * Validate the authentication response. - * - * @param params Query parameters of the authentication response - * @param state State saved in the session - * @param redirectURI Redirect URI - * @return Authentication response - */ - def validateOIDCAuthenticationResponse(params: Map[String, String], state: State, redirectURI: URI): Option[AuthenticationSuccessResponse] = + * Validate the authentication response. + * + * @param params Query parameters of the authentication response + * @param state State saved in the session + * @param redirectURI Redirect URI + * @return Authentication response + */ + def validateOIDCAuthenticationResponse( + params: Map[String, String], + state: State, + redirectURI: URI + ): Option[AuthenticationSuccessResponse] = try { AuthenticationResponseParser.parse(redirectURI, mapAsJavaMap(params)) match { case response: AuthenticationSuccessResponse => @@ -112,23 +120,27 @@ } /** - * Obtain the ID token from the OpenID Provider. - * - * @param authorizationCode Authorization code in the query string - * @param nonce Nonce - * @param redirectURI Redirect URI - * @param oidc OIDC settings - * @return Token response - */ - def obtainOIDCToken(authorizationCode: AuthorizationCode, - nonce: Nonce, - redirectURI: URI, - oidc: SystemSettingsService.OIDC): Option[IDTokenClaimsSet] = { + * Obtain the ID token from the OpenID Provider. + * + * @param authorizationCode Authorization code in the query string + * @param nonce Nonce + * @param redirectURI Redirect URI + * @param oidc OIDC settings + * @return Token response + */ + def obtainOIDCToken( + authorizationCode: AuthorizationCode, + nonce: Nonce, + redirectURI: URI, + oidc: SystemSettingsService.OIDC + ): Option[IDTokenClaimsSet] = { val metadata = OIDCProviderMetadata.resolve(oidc.issuer) - val tokenRequest = new TokenRequest(metadata.getTokenEndpointURI, + val tokenRequest = new TokenRequest( + metadata.getTokenEndpointURI, new ClientSecretBasic(oidc.clientID, oidc.clientSecret), new AuthorizationCodeGrant(authorizationCode, redirectURI), - OIDC_SCOPE) + OIDC_SCOPE + ) val httpResponse = tokenRequest.toHTTPRequest.send() try { OIDCTokenResponseParser.parse(httpResponse) match { @@ -146,29 +158,36 @@ } /** - * Validate the token response. - * - * @param response Token response - * @param metadata OpenID Provider metadata - * @param nonce Nonce - * @return Claims - */ - def validateOIDCTokenResponse(response: OIDCTokenResponse, - metadata: OIDCProviderMetadata, - nonce: Nonce, - oidc: SystemSettingsService.OIDC): Option[IDTokenClaimsSet] = + * Validate the token response. + * + * @param response Token response + * @param metadata OpenID Provider metadata + * @param nonce Nonce + * @return Claims + */ + def validateOIDCTokenResponse( + response: OIDCTokenResponse, + metadata: OIDCProviderMetadata, + nonce: Nonce, + oidc: SystemSettingsService.OIDC + ): Option[IDTokenClaimsSet] = Option(response.getOIDCTokens.getIDToken) match { case Some(jwt) => val validator = oidc.jwsAlgorithm map { jwsAlgorithm => - new IDTokenValidator(metadata.getIssuer, oidc.clientID, jwsAlgorithm, metadata.getJWKSetURI.toURL, - new DefaultResourceRetriever(JWK_REQUEST_TIMEOUT, JWK_REQUEST_TIMEOUT)) + new IDTokenValidator( + metadata.getIssuer, + oidc.clientID, + jwsAlgorithm, + metadata.getJWKSetURI.toURL, + new DefaultResourceRetriever(JWK_REQUEST_TIMEOUT, JWK_REQUEST_TIMEOUT) + ) } getOrElse { new IDTokenValidator(metadata.getIssuer, oidc.clientID) } try { Some(validator.validate(jwt, nonce)) } catch { - case e@(_: BadJOSEException | _: JOSEException) => + case e @ (_: BadJOSEException | _: JOSEException) => logger.info(s"OIDC ID token has error: $e") None } @@ -179,9 +198,10 @@ } object OpenIDConnectService { + /** - * All signature algorithms. - */ + * All signature algorithms. + */ val JWS_ALGORITHMS: Map[String, Set[JWSAlgorithm]] = Seq( "HMAC" -> Family.HMAC_SHA, "RSA" -> Family.RSA, diff --git a/src/main/scala/gitbucket/core/service/PrioritiesService.scala b/src/main/scala/gitbucket/core/service/PrioritiesService.scala index cafff4b..22037e7 100644 --- a/src/main/scala/gitbucket/core/service/PrioritiesService.scala +++ b/src/main/scala/gitbucket/core/service/PrioritiesService.scala @@ -16,8 +16,15 @@ def getPriority(owner: String, repository: String, priorityName: String)(implicit s: Session): Option[Priority] = Priorities.filter(_.byPriority(owner, repository, priorityName)).firstOption - def createPriority(owner: String, repository: String, priorityName: String, description: Option[String], color: String)(implicit s: Session): Int = { - val ordering = Priorities.filter(_.byRepository(owner, repository)) + def createPriority( + owner: String, + repository: String, + priorityName: String, + description: Option[String], + color: String + )(implicit s: Session): Int = { + val ordering = Priorities + .filter(_.byRepository(owner, repository)) .list .map(p => p.ordering) .reduceOption(_ max _) @@ -25,37 +32,48 @@ .getOrElse(0) Priorities returning Priorities.map(_.priorityId) insert Priority( - userName = owner, + userName = owner, repositoryName = repository, - priorityName = priorityName, - description = description, - isDefault = false, - ordering = ordering, - color = color + priorityName = priorityName, + description = description, + isDefault = false, + ordering = ordering, + color = color ) } - def updatePriority(owner: String, repository: String, priorityId: Int, priorityName: String, description: Option[String], color: String) - (implicit s: Session): Unit = - Priorities.filter(_.byPrimaryKey(owner, repository, priorityId)) - .map(t => (t.priorityName, t.description.?, t.color)) - .update(priorityName, description, color) + def updatePriority( + owner: String, + repository: String, + priorityId: Int, + priorityName: String, + description: Option[String], + color: String + )(implicit s: Session): Unit = + Priorities + .filter(_.byPrimaryKey(owner, repository, priorityId)) + .map(t => (t.priorityName, t.description.?, t.color)) + .update(priorityName, description, color) - def reorderPriorities(owner: String, repository: String, order: Map[Int, Int]) - (implicit s: Session): Unit = { + def reorderPriorities(owner: String, repository: String, order: Map[Int, Int])(implicit s: Session): Unit = { - Priorities.filter(_.byRepository(owner, repository)) + Priorities + .filter(_.byRepository(owner, repository)) .list - .foreach(p => Priorities - .filter(_.byPrimaryKey(owner, repository, p.priorityId)) - .map(_.ordering) - .update(order.get(p.priorityId).get)) + .foreach( + p => + Priorities + .filter(_.byPrimaryKey(owner, repository, p.priorityId)) + .map(_.ordering) + .update(order.get(p.priorityId).get) + ) } def deletePriority(owner: String, repository: String, priorityId: Int)(implicit s: Session): Unit = { - Issues.filter(_.byRepository(owner, repository)) + Issues + .filter(_.byRepository(owner, repository)) .filter(_.priorityId === priorityId) - .map(_.priorityId?) + .map(_.priorityId ?) .update(None) Priorities.filter(_.byPrimaryKey(owner, repository, priorityId)).delete @@ -76,9 +94,12 @@ .map(_.isDefault) .update(false) - priorityId.foreach(id => Priorities - .filter(_.byPrimaryKey(owner, repository, id)) - .map(_.isDefault) - .update(true)) + priorityId.foreach( + id => + Priorities + .filter(_.byPrimaryKey(owner, repository, id)) + .map(_.isDefault) + .update(true) + ) } } diff --git a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala index 65d0aaa..8befb2f 100644 --- a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala +++ b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala @@ -6,55 +6,81 @@ import gitbucket.core.model.Profile.profile.blockingApi._ import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack} - trait ProtectedBranchService { import ProtectedBranchService._ - private def getProtectedBranchInfoOpt(owner: String, repository: String, branch: String)(implicit session: Session): Option[ProtectedBranchInfo] = + private def getProtectedBranchInfoOpt(owner: String, repository: String, branch: String)( + implicit session: Session + ): Option[ProtectedBranchInfo] = ProtectedBranches .joinLeft(ProtectedBranchContexts) - .on { case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) } + .on { case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) } .map { case (pb, c) => pb -> c.map(_.context) } .filter(_._1.byPrimaryKey(owner, repository, branch)) .list .groupBy(_._1) .headOption - .map { p => p._1 -> p._2.flatMap(_._2) } - .map { case (t1, contexts) => - new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin) + .map { p => + p._1 -> p._2.flatMap(_._2) + } + .map { + case (t1, contexts) => + new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin) } - def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit session: Session): ProtectedBranchInfo = + def getProtectedBranchInfo(owner: String, repository: String, branch: String)( + implicit session: Session + ): ProtectedBranchInfo = getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(ProtectedBranchInfo.disabled(owner, repository)) def getProtectedBranchList(owner: String, repository: String)(implicit session: Session): List[String] = ProtectedBranches.filter(_.byRepository(owner, repository)).map(_.branch).list - def enableBranchProtection(owner: String, repository: String, branch:String, includeAdministrators: Boolean, contexts: Seq[String]) - (implicit session: Session): Unit = { + def enableBranchProtection( + owner: String, + repository: String, + branch: String, + includeAdministrators: Boolean, + contexts: Seq[String] + )(implicit session: Session): Unit = { disableBranchProtection(owner, repository, branch) ProtectedBranches.insert(new ProtectedBranch(owner, repository, branch, includeAdministrators && contexts.nonEmpty)) - contexts.map{ context => + contexts.map { context => ProtectedBranchContexts.insert(new ProtectedBranchContext(owner, repository, branch, context)) } } - def disableBranchProtection(owner: String, repository: String, branch:String)(implicit session: Session): Unit = + def disableBranchProtection(owner: String, repository: String, branch: String)(implicit session: Session): Unit = ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete } object ProtectedBranchService { - class ProtectedBranchReceiveHook extends ReceiveHook with ProtectedBranchService with RepositoryService with AccountService { - override def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String) - (implicit session: Session): Option[String] = { + class ProtectedBranchReceiveHook + extends ReceiveHook + with ProtectedBranchService + with RepositoryService + with AccountService { + override def preReceive( + owner: String, + repository: String, + receivePack: ReceivePack, + command: ReceiveCommand, + pusher: String + )(implicit session: Session): Option[String] = { val branch = command.getRefName.stripPrefix("refs/heads/") - if(branch != command.getRefName){ + if (branch != command.getRefName) { val repositoryInfo = getRepository(owner, repository) - if(command.getType == ReceiveCommand.Type.DELETE && repositoryInfo.exists(_.repository.defaultBranch == branch)){ + if (command.getType == ReceiveCommand.Type.DELETE && repositoryInfo.exists( + _.repository.defaultBranch == branch + )) { Some(s"refusing to delete the branch: ${command.getRefName}.") } else { - getProtectedBranchInfo(owner, repository, branch).getStopReason(receivePack.isAllowNonFastForwards, command, pusher) + getProtectedBranchInfo(owner, repository, branch).getStopReason( + receivePack.isAllowNonFastForwards, + command, + pusher + ) } } else { None @@ -62,7 +88,6 @@ } } - case class ProtectedBranchInfo( owner: String, repository: String, @@ -78,18 +103,22 @@ * Include administrators * Enforce required status checks for repository administrators. */ - includeAdministrators: Boolean) extends AccountService with RepositoryService with CommitStatusService { + includeAdministrators: Boolean + ) extends AccountService + with RepositoryService + with CommitStatusService { def isAdministrator(pusher: String)(implicit session: Session): Boolean = pusher == owner || getGroupMembers(owner).exists(gm => gm.userName == pusher && gm.isManager) || - getCollaborators(owner, repository).exists { case (collaborator, isGroup) => - if(collaborator.role == Role.ADMIN.name){ - if(isGroup){ - getGroupMembers(collaborator.collaboratorName).exists(gm => gm.userName == pusher) - } else { - collaborator.collaboratorName == pusher - } - } else false + getCollaborators(owner, repository).exists { + case (collaborator, isGroup) => + if (collaborator.role == Role.ADMIN.name) { + if (isGroup) { + getGroupMembers(collaborator.collaboratorName).exists(gm => gm.userName == pusher) + } else { + collaborator.collaboratorName == pusher + } + } else false } /** @@ -97,16 +126,18 @@ * Can't be deleted * Can't have changes merged into them until required status checks pass */ - def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)(implicit session: Session): Option[String] = { - if(enabled){ + def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)( + implicit session: Session + ): Option[String] = { + if (enabled) { command.getType() match { case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards => Some("Cannot force-push to a protected branch") - case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) => + case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) => unSuccessedContexts(command.getNewId.name) match { case s if s.size == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""") case s if s.size >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected") - case _ => None + case _ => None } case ReceiveCommand.Type.DELETE => Some("Cannot delete a protected branch") @@ -116,11 +147,15 @@ None } } - def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = if(contexts.isEmpty){ - Set.empty - } else { - contexts.toSet -- getCommitStatues(owner, repository, sha1).filter(_.state == CommitState.SUCCESS).map(_.context).toSet - } + def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = + if (contexts.isEmpty) { + Set.empty + } else { + contexts.toSet -- getCommitStatues(owner, repository, sha1) + .filter(_.state == CommitState.SUCCESS) + .map(_.context) + .toSet + } def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match { case _ if !enabled => false case _ if contexts.isEmpty => false @@ -129,7 +164,8 @@ case _ => true } } - object ProtectedBranchInfo{ - def disabled(owner: String, repository: String): ProtectedBranchInfo = ProtectedBranchInfo(owner, repository, false, Nil, false) + object ProtectedBranchInfo { + def disabled(owner: String, repository: String): ProtectedBranchInfo = + ProtectedBranchInfo(owner, repository, false, Nil, false) } } diff --git a/src/main/scala/gitbucket/core/service/PullRequestService.scala b/src/main/scala/gitbucket/core/service/PullRequestService.scala index b6c009a..ddcb345 100644 --- a/src/main/scala/gitbucket/core/service/PullRequestService.scala +++ b/src/main/scala/gitbucket/core/service/PullRequestService.scala @@ -14,38 +14,47 @@ import org.eclipse.jgit.api.Git import scala.collection.JavaConverters._ - trait PullRequestService { self: IssuesService with CommitsService => import PullRequestService._ - def getPullRequest(owner: String, repository: String, issueId: Int) - (implicit s: Session): Option[(Issue, PullRequest)] = - getIssue(owner, repository, issueId.toString).flatMap{ issue => - PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)).firstOption.map{ - pullreq => (issue, pullreq) + def getPullRequest(owner: String, repository: String, issueId: Int)( + implicit s: Session + ): Option[(Issue, PullRequest)] = + getIssue(owner, repository, issueId.toString).flatMap { issue => + PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)).firstOption.map { pullreq => + (issue, pullreq) } } - def updateCommitId(owner: String, repository: String, issueId: Int, commitIdTo: String, commitIdFrom: String) - (implicit s: Session): Unit = - PullRequests.filter(_.byPrimaryKey(owner, repository, issueId)) - .map(pr => pr.commitIdTo -> pr.commitIdFrom) - .update((commitIdTo, commitIdFrom)) - - def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String]) - (implicit s: Session): List[PullRequestCount] = + def updateCommitId(owner: String, repository: String, issueId: Int, commitIdTo: String, commitIdFrom: String)( + implicit s: Session + ): Unit = PullRequests - .join(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - (t2.closed === closed.bind) && - (t1.userName === owner.get.bind, owner.isDefined) && - (t1.repositoryName === repository.get.bind, repository.isDefined) + .filter(_.byPrimaryKey(owner, repository, issueId)) + .map(pr => pr.commitIdTo -> pr.commitIdFrom) + .update((commitIdTo, commitIdFrom)) + + def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])( + implicit s: Session + ): List[PullRequestCount] = + PullRequests + .join(Issues) + .on { (t1, t2) => + t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) + } + .filter { + case (t1, t2) => + (t2.closed === closed.bind) && + (t1.userName === owner.get.bind, owner.isDefined) && + (t1.repositoryName === repository.get.bind, repository.isDefined) } .groupBy { case (t1, t2) => t2.openedUserName } .map { case (userName, t) => userName -> t.length } .sortBy(_._2 desc) .list - .map { x => PullRequestCount(x._1, x._2) } + .map { x => + PullRequestCount(x._1, x._2) + } // def getAllPullRequestCountGroupByUser(closed: Boolean, userName: String)(implicit s: Session): List[PullRequestCount] = // PullRequests @@ -65,9 +74,17 @@ // .list // .map { x => PullRequestCount(x._1, x._2) } - def createPullRequest(originUserName: String, originRepositoryName: String, issueId: Int, - originBranch: String, requestUserName: String, requestRepositoryName: String, requestBranch: String, - commitIdFrom: String, commitIdTo: String)(implicit s: Session): Unit = + def createPullRequest( + originUserName: String, + originRepositoryName: String, + issueId: Int, + originBranch: String, + requestUserName: String, + requestRepositoryName: String, + requestBranch: String, + commitIdFrom: String, + commitIdTo: String + )(implicit s: Session): Unit = PullRequests insert PullRequest( originUserName, originRepositoryName, @@ -77,17 +94,23 @@ requestRepositoryName, requestBranch, commitIdFrom, - commitIdTo) + commitIdTo + ) - def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Option[Boolean]) - (implicit s: Session): List[PullRequest] = + def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Option[Boolean])( + implicit s: Session + ): List[PullRequest] = PullRequests - .join(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - (t1.requestUserName === userName.bind) && - (t1.requestRepositoryName === repositoryName.bind) && - (t1.requestBranch === branch.bind) && - (t2.closed === closed.get.bind, closed.isDefined) + .join(Issues) + .on { (t1, t2) => + t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) + } + .filter { + case (t1, t2) => + (t1.requestUserName === userName.bind) && + (t1.requestRepositoryName === repositoryName.bind) && + (t1.requestBranch === branch.bind) && + (t2.closed === closed.get.bind, closed.isDefined) } .map { case (t1, t2) => t1 } .list @@ -99,19 +122,24 @@ * 2. return if exists pull request to other branch * 2. return None */ - def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String) - (implicit s: Session): Option[(PullRequest, Issue)] = + def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String)( + implicit s: Session + ): Option[(PullRequest, Issue)] = PullRequests - .join(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - (t1.requestUserName === userName.bind) && - (t1.requestRepositoryName === repositoryName.bind) && - (t1.requestBranch === branch.bind) && - (t1.userName === userName.bind) && - (t1.repositoryName === repositoryName.bind) && - (t2.closed === false.bind) + .join(Issues) + .on { (t1, t2) => + t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .sortBy{ case (t1, t2) => t1.branch =!= defaultBranch.bind } + .filter { + case (t1, t2) => + (t1.requestUserName === userName.bind) && + (t1.requestRepositoryName === repositoryName.bind) && + (t1.requestBranch === branch.bind) && + (t1.userName === userName.bind) && + (t1.repositoryName === repositoryName.bind) && + (t2.closed === false.bind) + } + .sortBy { case (t1, t2) => t1.branch =!= defaultBranch.bind } .firstOption /** @@ -119,116 +147,172 @@ */ def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit = getPullRequestsByRequest(owner, repository, branch, Some(false)).foreach { pullreq => - if(Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run){ + if (Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run) { // Update the git repository val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest( - pullreq.userName, pullreq.repositoryName, pullreq.branch, pullreq.issueId, - pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch) + pullreq.userName, + pullreq.repositoryName, + pullreq.branch, + pullreq.issueId, + pullreq.requestUserName, + pullreq.requestRepositoryName, + pullreq.requestBranch + ) // Collect comment positions val positions = getCommitComments(pullreq.userName, pullreq.repositoryName, pullreq.commitIdTo, true) .collect { - case CommitComment(_, _, _, commentId, _, _, Some(file), None, Some(newLine), _, _, _) => (file, commentId, Right(newLine)) - case CommitComment(_, _, _, commentId, _, _, Some(file), Some(oldLine), None, _, _, _) => (file, commentId, Left(oldLine)) + case CommitComment(_, _, _, commentId, _, _, Some(file), None, Some(newLine), _, _, _) => + (file, commentId, Right(newLine)) + case CommitComment(_, _, _, commentId, _, _, Some(file), Some(oldLine), None, _, _, _) => + (file, commentId, Left(oldLine)) } .groupBy { case (file, _, _) => file } - .map { case (file, comments) => file -> - comments.map { case (_, commentId, lineNumber) => (commentId, lineNumber) } + .map { + case (file, comments) => + file -> + comments.map { case (_, commentId, lineNumber) => (commentId, lineNumber) } } // Update comments position - updatePullRequestCommentPositions(positions, pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo, commitIdTo) + updatePullRequestCommentPositions( + positions, + pullreq.requestUserName, + pullreq.requestRepositoryName, + pullreq.commitIdTo, + commitIdTo + ) // Update commit id in the PULL_REQUEST table updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom) } } - def getPullRequestByRequestCommit(userName: String, repositoryName: String, toBranch:String, fromBranch: String, commitId: String) - (implicit s: Session): Option[(PullRequest, Issue)] = { - if(toBranch == fromBranch){ + def getPullRequestByRequestCommit( + userName: String, + repositoryName: String, + toBranch: String, + fromBranch: String, + commitId: String + )(implicit s: Session): Option[(PullRequest, Issue)] = { + if (toBranch == fromBranch) { None } else { PullRequests - .join(Issues).on { (t1, t2) => t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) } - .filter { case (t1, t2) => - (t1.userName === userName.bind) && - (t1.repositoryName === repositoryName.bind) && - (t1.branch === toBranch.bind) && - (t1.requestUserName === userName.bind) && - (t1.requestRepositoryName === repositoryName.bind) && - (t1.requestBranch === fromBranch.bind) && - (t1.commitIdTo === commitId.bind) + .join(Issues) + .on { (t1, t2) => + t1.byPrimaryKey(t2.userName, t2.repositoryName, t2.issueId) + } + .filter { + case (t1, t2) => + (t1.userName === userName.bind) && + (t1.repositoryName === repositoryName.bind) && + (t1.branch === toBranch.bind) && + (t1.requestUserName === userName.bind) && + (t1.requestRepositoryName === repositoryName.bind) && + (t1.requestBranch === fromBranch.bind) && + (t1.commitIdTo === commitId.bind) } .firstOption } } - private def updatePullRequestCommentPositions(positions: Map[String, Seq[(Int, Either[Int, Int])]], userName: String, repositoryName: String, - oldCommitId: String, newCommitId: String)(implicit s: Session): Unit = { + private def updatePullRequestCommentPositions( + positions: Map[String, Seq[(Int, Either[Int, Int])]], + userName: String, + repositoryName: String, + oldCommitId: String, + newCommitId: String + )(implicit s: Session): Unit = { val (_, diffs) = getRequestCompareInfo(userName, repositoryName, oldCommitId, userName, repositoryName, newCommitId) - val patchs = positions.map { case (file, _) => - diffs.find(x => x.oldPath == file).map { diff => - (diff.oldContent, diff.newContent) match { - case (Some(oldContent), Some(newContent)) => { - val oldLines = oldContent.replace("\r\n", "\n").split("\n") - val newLines = newContent.replace("\r\n", "\n").split("\n") - file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava)) + val patchs = positions.map { + case (file, _) => + diffs + .find(x => x.oldPath == file) + .map { diff => + (diff.oldContent, diff.newContent) match { + case (Some(oldContent), Some(newContent)) => { + val oldLines = oldContent.replace("\r\n", "\n").split("\n") + val newLines = newContent.replace("\r\n", "\n").split("\n") + file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava)) + } + case _ => + file -> None + } } - case _ => + .getOrElse { file -> None - } - }.getOrElse { - file -> None - } + } } - positions.foreach { case (file, comments) => - patchs(file) match { - case Some(patch) => file -> comments.foreach { case (commentId, lineNumber) => lineNumber match { - case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None) - case Right(newLine) => - var counter = newLine - patch.getDeltas.asScala.filter(_.getOriginal.getPosition < newLine).foreach { delta => - delta.getType match { - case Delta.TYPE.CHANGE => - if(delta.getOriginal.getPosition <= newLine - 1 && newLine <= delta.getOriginal.getPosition + delta.getRevised.getLines.size){ - counter = -1 - } else { - counter = counter + (delta.getRevised.getLines.size - delta.getOriginal.getLines.size) - } - case Delta.TYPE.INSERT => counter = counter + delta.getRevised.getLines.size - case Delta.TYPE.DELETE => counter = counter - delta.getOriginal.getLines.size - } + positions.foreach { + case (file, comments) => + patchs(file) match { + case Some(patch) => + file -> comments.foreach { + case (commentId, lineNumber) => + lineNumber match { + case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None) + case Right(newLine) => + var counter = newLine + patch.getDeltas.asScala.filter(_.getOriginal.getPosition < newLine).foreach { delta => + delta.getType match { + case Delta.TYPE.CHANGE => + if (delta.getOriginal.getPosition <= newLine - 1 && newLine <= delta.getOriginal.getPosition + delta.getRevised.getLines.size) { + counter = -1 + } else { + counter = counter + (delta.getRevised.getLines.size - delta.getOriginal.getLines.size) + } + case Delta.TYPE.INSERT => counter = counter + delta.getRevised.getLines.size + case Delta.TYPE.DELETE => counter = counter - delta.getOriginal.getLines.size + } + } + if (counter >= 0) { + updateCommitCommentPosition(commentId, newCommitId, None, Some(counter)) + } + } } - if(counter >= 0){ - updateCommitCommentPosition(commentId, newCommitId, None, Some(counter)) + case _ => + comments.foreach { + case (commentId, lineNumber) => + lineNumber match { + case Right(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None) + case Left(newLine) => updateCommitCommentPosition(commentId, newCommitId, None, Some(newLine)) + } } - }} - case _ => comments.foreach { case (commentId, lineNumber) => lineNumber match { - case Right(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None) - case Left(newLine) => updateCommitCommentPosition(commentId, newCommitId, None, Some(newLine)) - }} - } + } } } - def getRequestCompareInfo(userName: String, repositoryName: String, branch: String, - requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = + def getRequestCompareInfo( + userName: String, + repositoryName: String, + branch: String, + requestUserName: String, + requestRepositoryName: String, + requestCommitId: String + ): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = using( Git.open(getRepositoryDir(userName, repositoryName)), Git.open(getRepositoryDir(requestUserName, requestRepositoryName)) - ){ (oldGit, newGit) => + ) { (oldGit, newGit) => val oldId = oldGit.getRepository.resolve(branch) val newId = newGit.getRepository.resolve(requestCommitId) - val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit => - new CommitInfo(revCommit) - }.toList.splitWith { (commit1, commit2) => - helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) - } + val commits = newGit.log + .addRange(oldId, newId) + .call + .iterator + .asScala + .map { revCommit => + new CommitInfo(revCommit) + } + .toList + .splitWith { (commit1, commit2) => + helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) + } val diffs = JGitUtil.getDiffs(newGit, Some(oldId.getName), newId.getName, true, false) @@ -251,22 +335,30 @@ hasUpdatePermission: Boolean, needStatusCheck: Boolean, hasMergePermission: Boolean, - commitIdTo: String){ + commitIdTo: String + ) { val hasConflict = conflictMessage.isDefined val statuses: List[CommitStatus] = - commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _)) - val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS)) - val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS) - val canUpdate = branchIsOutOfDate && !hasConflict - val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem - lazy val commitStateSummary:(CommitState, String) = { + commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet) + .map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _)) + val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists( + context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS) + ) + val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine( + statuses.map(_.state).toSet + ) != CommitState.SUCCESS) + val canUpdate = branchIsOutOfDate && !hasConflict + val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem + lazy val commitStateSummary: (CommitState, String) = { val stateMap = statuses.groupBy(_.state) val state = CommitState.combine(stateMap.keySet) - val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ") + val summary = stateMap.map { case (keyState, states) => states.size + " " + keyState.name }.mkString(", ") state -> summary } - lazy val statusesAndRequired:List[(CommitStatus, Boolean)] = statuses.map{ s => s -> branchProtection.contexts.contains(s.context) } - lazy val isAllSuccess = commitStateSummary._1==CommitState.SUCCESS + lazy val statusesAndRequired: List[(CommitStatus, Boolean)] = statuses.map { s => + s -> branchProtection.contexts.contains(s.context) + } + lazy val isAllSuccess = commitStateSummary._1 == CommitState.SUCCESS } } diff --git a/src/main/scala/gitbucket/core/service/ReleaseService.scala b/src/main/scala/gitbucket/core/service/ReleaseService.scala index 26941d3..0a8df2f 100644 --- a/src/main/scala/gitbucket/core/service/ReleaseService.scala +++ b/src/main/scala/gitbucket/core/service/ReleaseService.scala @@ -9,17 +9,25 @@ trait ReleaseService { self: AccountService with RepositoryService => - def createReleaseAsset(owner: String, repository: String, tag: String, fileName: String, label: String, size: Long, loginAccount: Account)(implicit s: Session): Unit = { + def createReleaseAsset( + owner: String, + repository: String, + tag: String, + fileName: String, + label: String, + size: Long, + loginAccount: Account + )(implicit s: Session): Unit = { ReleaseAssets insert ReleaseAsset( - userName = owner, + userName = owner, repositoryName = repository, - tag = tag, - fileName = fileName, - label = label, - size = size, - uploader = loginAccount.userName, + tag = tag, + fileName = fileName, + label = label, + size = size, + uploader = loginAccount.userName, registeredDate = currentDate, - updatedDate = currentDate + updatedDate = currentDate ) } @@ -27,12 +35,16 @@ ReleaseAssets.filter(x => x.byTag(owner, repository, tag)).list } - def getReleaseAssetsMap(owner: String, repository: String)(implicit s: Session): Map[ReleaseTag, Seq[ReleaseAsset]] = { + def getReleaseAssetsMap(owner: String, repository: String)( + implicit s: Session + ): Map[ReleaseTag, Seq[ReleaseAsset]] = { val releases = getReleases(owner, repository) releases.map(rel => (rel -> getReleaseAssets(owner, repository, rel.tag))).toMap } - def getReleaseAsset(owner: String, repository: String, tag: String, fileId: String)(implicit s: Session): Option[ReleaseAsset] = { + def getReleaseAsset(owner: String, repository: String, tag: String, fileId: String)( + implicit s: Session + ): Option[ReleaseAsset] = { ReleaseAssets.filter(x => x.byPrimaryKey(owner, repository, tag, fileId)) firstOption } @@ -40,17 +52,23 @@ ReleaseAssets.filter(x => x.byTag(owner, repository, tag)) delete } - def createRelease(owner: String, repository: String, name: String, content: Option[String], tag: String, - loginAccount: Account)(implicit context: Context, s: Session): Int = { + def createRelease( + owner: String, + repository: String, + name: String, + content: Option[String], + tag: String, + loginAccount: Account + )(implicit context: Context, s: Session): Int = { ReleaseTags insert ReleaseTag( - userName = owner, + userName = owner, repositoryName = repository, - name = name, - tag = tag, - author = loginAccount.userName, - content = content, + name = name, + tag = tag, + author = loginAccount.userName, + content = content, registeredDate = currentDate, - updatedDate = currentDate + updatedDate = currentDate ) } @@ -73,11 +91,15 @@ // else None // } - def updateRelease(owner: String, repository: String, tag: String, title: String, content: Option[String])(implicit s: Session): Int = { + def updateRelease(owner: String, repository: String, tag: String, title: String, content: Option[String])( + implicit s: Session + ): Int = { ReleaseTags - .filter (_.byPrimaryKey(owner, repository, tag)) - .map { t => (t.name, t.content, t.updatedDate) } - .update (title, content, currentDate) + .filter(_.byPrimaryKey(owner, repository, tag)) + .map { t => + (t.name, t.content, t.updatedDate) + } + .update(title, content, currentDate) } def deleteRelease(owner: String, repository: String, tag: String)(implicit s: Session): Unit = { diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala index 88a777d..8e7a662 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala @@ -33,7 +33,7 @@ def endCreation(owner: String, repository: String, error: Option[String]): Unit = { error match { - case None => Creating.remove(s"${owner}/${repository}") + case None => Creating.remove(s"${owner}/${repository}") case Some(error) => Creating.put(s"${owner}/${repository}", Some(error)) } } @@ -45,15 +45,33 @@ } trait RepositoryCreationService { - self: AccountService with RepositoryService with LabelsService with WikiService with ActivityService with PrioritiesService => + self: AccountService + with RepositoryService + with LabelsService + with WikiService + with ActivityService + with PrioritiesService => - def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], - isPrivate: Boolean, createReadme: Boolean): Future[Unit] = { + def createRepository( + loginAccount: Account, + owner: String, + name: String, + description: Option[String], + isPrivate: Boolean, + createReadme: Boolean + ): Future[Unit] = { createRepository(loginAccount, owner, name, description, isPrivate, if (createReadme) "README" else "EMPTY", None) } - def createRepository(loginAccount: Account, owner: String, name: String, description: Option[String], - isPrivate: Boolean, initOption: String, sourceUrl: Option[String]): Future[Unit] = Future { + def createRepository( + loginAccount: Account, + owner: String, + name: String, + description: Option[String], + isPrivate: Boolean, + initOption: String, + sourceUrl: Option[String] + ): Future[Unit] = Future { RepositoryCreationService.startCreation(owner, name) try { Database() withTransaction { implicit session => @@ -68,7 +86,6 @@ } } else None - // Insert to the database at first insertRepository(name, owner, description, isPrivate) @@ -106,13 +123,26 @@ "===============\n" } - builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE, - inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) + builder.add( + JGitUtil.createDirCacheEntry( + "README.md", + FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")) + ) + ) } builder.finish() - JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit") + JGitUtil.createNewCommit( + git, + inserter, + headId, + builder.getDirCache.writeTree(inserter), + Constants.HEAD, + loginAccount.fullName, + loginAccount.mailAddress, + "Initial commit" + ) } } @@ -165,8 +195,9 @@ // Set default collaborators for the private fork if (repository.repository.isPrivate) { // Copy collaborators from the source repository - getCollaborators(repository.owner, repository.name).foreach { case (collaborator, _) => - addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role) + getCollaborators(repository.owner, repository.name).foreach { + case (collaborator, _) => + addCollaborator(accountName, repository.name, collaborator.collaboratorName, collaborator.role) } // Register an owner of the source repository as a collaborator addCollaborator(accountName, repository.name, repository.owner, Role.ADMIN.name) @@ -180,11 +211,14 @@ // clone repository actually JGitUtil.cloneRepository( getRepositoryDir(repository.owner, repository.name), - FileUtil.deleteIfExists(getRepositoryDir(accountName, repository.name))) + FileUtil.deleteIfExists(getRepositoryDir(accountName, repository.name)) + ) // Create Wiki repository - JGitUtil.cloneRepository(getWikiRepositoryDir(repository.owner, repository.name), - FileUtil.deleteIfExists(getWikiRepositoryDir(accountName, repository.name))) + JGitUtil.cloneRepository( + getWikiRepositoryDir(repository.owner, repository.name), + FileUtil.deleteIfExists(getWikiRepositoryDir(accountName, repository.name)) + ) // Copy LFS files val lfsDir = getLfsDir(repository.owner, repository.name) @@ -216,10 +250,36 @@ } def insertDefaultPriorities(userName: String, repositoryName: String)(implicit s: Session): Unit = { - createPriority(userName, repositoryName, "highest", Some("All defects at this priority must be fixed before any public product is delivered."), "fc2929") - createPriority(userName, repositoryName, "very high", Some("Issues must be addressed before a final product is delivered."), "fc5629") - createPriority(userName, repositoryName, "high", Some("Issues should be addressed before a final product is delivered. If the issue cannot be resolved before delivery, it should be prioritized for the next release."), "fc9629") - createPriority(userName, repositoryName, "important", Some("Issues can be shipped with a final product, but should be reviewed before the next release."), "fccd29") + createPriority( + userName, + repositoryName, + "highest", + Some("All defects at this priority must be fixed before any public product is delivered."), + "fc2929" + ) + createPriority( + userName, + repositoryName, + "very high", + Some("Issues must be addressed before a final product is delivered."), + "fc5629" + ) + createPriority( + userName, + repositoryName, + "high", + Some( + "Issues should be addressed before a final product is delivered. If the issue cannot be resolved before delivery, it should be prioritized for the next release." + ), + "fc9629" + ) + createPriority( + userName, + repositoryName, + "important", + Some("Issues can be shipped with a final product, but should be reviewed before the next release."), + "fccd29" + ) createPriority(userName, repositoryName, "default", Some("Default."), "acacac") setDefaultPriority(userName, repositoryName, getPriority(userName, repositoryName, "default").map(_.priorityId)) diff --git a/src/main/scala/gitbucket/core/service/RepositorySearchService.scala b/src/main/scala/gitbucket/core/service/RepositorySearchService.scala index 11be724..4c53792 100644 --- a/src/main/scala/gitbucket/core/service/RepositorySearchService.scala +++ b/src/main/scala/gitbucket/core/service/RepositorySearchService.scala @@ -17,69 +17,72 @@ def countIssues(owner: String, repository: String, query: String)(implicit session: Session): Int = searchIssuesByKeyword(owner, repository, query).length - def searchIssues(owner: String, repository: String, query: String)(implicit session: Session): List[IssueSearchResult] = - searchIssuesByKeyword(owner, repository, query).map { case (issue, commentCount, content) => - IssueSearchResult( - issue.issueId, - issue.isPullRequest, - issue.title, - issue.openedUserName, - issue.registeredDate, - commentCount, - getHighlightText(content, query)._1) + def searchIssues(owner: String, repository: String, query: String)( + implicit session: Session + ): List[IssueSearchResult] = + searchIssuesByKeyword(owner, repository, query).map { + case (issue, commentCount, content) => + IssueSearchResult( + issue.issueId, + issue.isPullRequest, + issue.title, + issue.openedUserName, + issue.registeredDate, + commentCount, + getHighlightText(content, query)._1 + ) } def countFiles(owner: String, repository: String, query: String): Int = - using(Git.open(getRepositoryDir(owner, repository))){ git => - if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length + using(Git.open(getRepositoryDir(owner, repository))) { git => + if (JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length } def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] = - using(Git.open(getRepositoryDir(owner, repository))){ git => - if(JGitUtil.isEmpty(git)){ + using(Git.open(getRepositoryDir(owner, repository))) { git => + if (JGitUtil.isEmpty(git)) { Nil } else { val files = searchRepositoryFiles(git, query) val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD") - files.map { case (path, text) => - val (highlightText, lineNumber) = getHighlightText(text, query) - FileSearchResult( - path, - commits(path).getCommitterIdent.getWhen, - highlightText, - lineNumber) + files.map { + case (path, text) => + val (highlightText, lineNumber) = getHighlightText(text, query) + FileSearchResult(path, commits(path).getCommitterIdent.getWhen, highlightText, lineNumber) } } } def countWikiPages(owner: String, repository: String, query: String): Int = - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + if (JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length } def searchWikiPages(owner: String, repository: String, query: String): List[FileSearchResult] = - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - if(JGitUtil.isEmpty(git)){ + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + if (JGitUtil.isEmpty(git)) { Nil } else { val files = searchRepositoryFiles(git, query) val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD") - files.map { case (path, text) => - val (highlightText, lineNumber) = getHighlightText(text, query) - FileSearchResult( - path.stripSuffix(".md"), - commits(path).getCommitterIdent.getWhen, - highlightText, - lineNumber) + files.map { + case (path, text) => + val (highlightText, lineNumber) = getHighlightText(text, query) + FileSearchResult( + path.stripSuffix(".md"), + commits(path).getCommitterIdent.getWhen, + highlightText, + lineNumber + ) } } } def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = { - val revWalk = new RevWalk(git.getRepository) - val objectId = git.getRepository.resolve("HEAD") + val revWalk = new RevWalk(git.getRepository) + val objectId = git.getRepository.resolve("HEAD") val revCommit = revWalk.parseCommit(objectId) - val treeWalk = new TreeWalk(git.getRepository) + val treeWalk = new TreeWalk(git.getRepository) treeWalk.setRecursive(true) treeWalk.addTree(revCommit.getTree) @@ -88,13 +91,13 @@ while (treeWalk.next()) { val mode = treeWalk.getFileMode(0) - if(mode == FileMode.REGULAR_FILE || mode == FileMode.EXECUTABLE_FILE){ + if (mode == FileMode.REGULAR_FILE || mode == FileMode.EXECUTABLE_FILE) { JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).foreach { bytes => - if(FileUtil.isText(bytes)){ - val text = StringUtil.convertFromByteArray(bytes) + if (FileUtil.isText(bytes)) { + val text = StringUtil.convertFromByteArray(bytes) val lowerText = text.toLowerCase - val indices = keywords.map(lowerText.indexOf _) - if(!indices.exists(_ < 0)){ + val indices = keywords.map(lowerText.indexOf _) + if (!indices.exists(_ < 0)) { list.append((treeWalk.getPathString, text)) } } @@ -111,28 +114,29 @@ object RepositorySearchService { - val CodeLimit = 10 + val CodeLimit = 10 val IssueLimit = 10 def getHighlightText(content: String, query: String): (String, Int) = { - val keywords = StringUtil.splitWords(query.toLowerCase) + val keywords = StringUtil.splitWords(query.toLowerCase) val lowerText = content.toLowerCase - val indices = keywords.map(lowerText.indexOf _) + val indices = keywords.map(lowerText.indexOf _) - if(!indices.exists(_ < 0)){ + if (!indices.exists(_ < 0)) { val lineNumber = content.substring(0, indices.min).split("\n").size - 1 - val highlightText = StringUtil.escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n")) - .replaceAll("(?i)(" + keywords.map("\\Q" + _ + "\\E").mkString("|") + ")", - "$1") + val highlightText = StringUtil + .escapeHtml(content.split("\n").drop(lineNumber).take(5).mkString("\n")) + .replaceAll( + "(?i)(" + keywords.map("\\Q" + _ + "\\E").mkString("|") + ")", + "$1" + ) (highlightText, lineNumber + 1) } else { (content.split("\n").take(5).mkString("\n"), 1) } } - case class SearchResult( - files : List[(String, String)], - issues: List[(Issue, Int, String)]) + case class SearchResult(files: List[(String, String)], issues: List[(Issue, Int, String)]) case class IssueSearchResult( issueId: Int, @@ -141,12 +145,14 @@ openedUserName: String, registeredDate: java.util.Date, commentCount: Int, - highlightText: String) + highlightText: String + ) case class FileSearchResult( - path: String, - lastModified: java.util.Date, - highlightText: String, - highlightLineNumber: Int) + path: String, + lastModified: java.util.Date, + highlightText: String, + highlightLineNumber: Int + ) } diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index f8ddf17..912af55 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -23,188 +23,276 @@ * @param originRepositoryName specify for the forked repository. (default is None) * @param originUserName specify for the forked repository. (default is None) */ - def insertRepository(repositoryName: String, userName: String, description: Option[String], isPrivate: Boolean, - originRepositoryName: Option[String] = None, originUserName: Option[String] = None, - parentRepositoryName: Option[String] = None, parentUserName: Option[String] = None) - (implicit s: Session): Unit = { + def insertRepository( + repositoryName: String, + userName: String, + description: Option[String], + isPrivate: Boolean, + originRepositoryName: Option[String] = None, + originUserName: Option[String] = None, + parentRepositoryName: Option[String] = None, + parentUserName: Option[String] = None + )(implicit s: Session): Unit = { Repositories insert Repository( - userName = userName, - repositoryName = repositoryName, - isPrivate = isPrivate, - description = description, - defaultBranch = "master", - registeredDate = currentDate, - updatedDate = currentDate, - lastActivityDate = currentDate, - originUserName = originUserName, + userName = userName, + repositoryName = repositoryName, + isPrivate = isPrivate, + description = description, + defaultBranch = "master", + registeredDate = currentDate, + updatedDate = currentDate, + lastActivityDate = currentDate, + originUserName = originUserName, originRepositoryName = originRepositoryName, - parentUserName = parentUserName, + parentUserName = parentUserName, parentRepositoryName = parentRepositoryName, - options = RepositoryOptions( - issuesOption = "PUBLIC", // TODO DISABLE for the forked repository? - externalIssuesUrl = None, - wikiOption = "PUBLIC", // TODO DISABLE for the forked repository? - externalWikiUrl = None, - allowFork = true, - mergeOptions = "merge-commit,squash,rebase", - defaultMergeOption = "merge-commit" + options = RepositoryOptions( + issuesOption = "PUBLIC", // TODO DISABLE for the forked repository? + externalIssuesUrl = None, + wikiOption = "PUBLIC", // TODO DISABLE for the forked repository? + externalWikiUrl = None, + allowFork = true, + mergeOptions = "merge-commit,squash,rebase", + defaultMergeOption = "merge-commit" ) ) IssueId insert (userName, repositoryName, 0) } - def renameRepository(oldUserName: String, oldRepositoryName: String, newUserName: String, newRepositoryName: String) - (implicit s: Session): Unit = { + def renameRepository(oldUserName: String, oldRepositoryName: String, newUserName: String, newRepositoryName: String)( + implicit s: Session + ): Unit = { getAccountByUserName(newUserName).foreach { account => - (Repositories filter { t => t.byRepository(oldUserName, oldRepositoryName) } firstOption).map { repository => + (Repositories filter { t => + t.byRepository(oldUserName, oldRepositoryName) + } firstOption).map { repository => Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName) - val webHooks = RepositoryWebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val webHookEvents = RepositoryWebHookEvents.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val pullRequests = PullRequests .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val labels = Labels .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val priorities = Priorities .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueComments = IssueComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val issueLabels = IssueLabels .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val commitComments = CommitComments .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val commitStatuses = CommitStatuses .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val collaborators = Collaborators .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val protectedBranches = ProtectedBranches .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val protectedBranchContexts = ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list - val deployKeys = DeployKeys .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val releases = ReleaseTags .filter(_.byRepository(oldUserName, oldRepositoryName)).list - val releaseAssets = ReleaseAssets .filter(_.byRepository(oldUserName, oldRepositoryName)).list + val webHooks = RepositoryWebHooks.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val webHookEvents = RepositoryWebHookEvents.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val milestones = Milestones.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issueId = IssueId.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issues = Issues.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val pullRequests = PullRequests.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val labels = Labels.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val priorities = Priorities.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issueComments = IssueComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val issueLabels = IssueLabels.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val commitComments = CommitComments.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val commitStatuses = CommitStatuses.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val collaborators = Collaborators.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val protectedBranches = ProtectedBranches.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val protectedBranchContexts = + ProtectedBranchContexts.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val deployKeys = DeployKeys.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val releases = ReleaseTags.filter(_.byRepository(oldUserName, oldRepositoryName)).list + val releaseAssets = ReleaseAssets.filter(_.byRepository(oldUserName, oldRepositoryName)).list - Repositories.filter { t => - (t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind) - }.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName) + Repositories + .filter { t => + (t.originUserName === oldUserName.bind) && (t.originRepositoryName === oldRepositoryName.bind) + } + .map { t => + t.originUserName -> t.originRepositoryName + } + .update(newUserName, newRepositoryName) - Repositories.filter { t => - (t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind) - }.map { t => t.parentUserName -> t.parentRepositoryName }.update(newUserName, newRepositoryName) + Repositories + .filter { t => + (t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind) + } + .map { t => + t.parentUserName -> t.parentRepositoryName + } + .update(newUserName, newRepositoryName) // Updates activity fk before deleting repository because activity is sorted by activityId // and it can't be changed by deleting-and-inserting record. Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity => - Activities.filter(_.activityId === activity.activityId.bind) - .map(x => (x.userName, x.repositoryName)).update(newUserName, newRepositoryName) + Activities + .filter(_.activityId === activity.activityId.bind) + .map(x => (x.userName, x.repositoryName)) + .update(newUserName, newRepositoryName) } deleteRepository(oldUserName, oldRepositoryName) - RepositoryWebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - RepositoryWebHookEvents.insertAll(webHookEvents .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - Priorities .insertAll(priorities .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*) + RepositoryWebHooks.insertAll( + webHooks.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + RepositoryWebHookEvents.insertAll( + webHookEvents.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + Milestones.insertAll(milestones.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + Priorities.insertAll(priorities.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + IssueId.insertAll(issueId.map(_.copy(_1 = newUserName, _2 = newRepositoryName)): _*) val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list val newPriorities = Priorities.filter(_.byRepository(newUserName, newRepositoryName)).list - Issues.insertAll(issues.map { x => x.copy( - userName = newUserName, - repositoryName = newRepositoryName, - milestoneId = x.milestoneId.map { id => - newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId - }, - priorityId = x.priorityId.map { id => - newPriorities.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName).get.priorityId - } - )} :_*) + Issues.insertAll(issues.map { x => + x.copy( + userName = newUserName, + repositoryName = newRepositoryName, + milestoneId = x.milestoneId.map { id => + newMilestones.find(_.title == milestones.find(_.milestoneId == id).get.title).get.milestoneId + }, + priorityId = x.priorityId.map { id => + newPriorities.find(_.priorityName == priorities.find(_.priorityId == id).get.priorityName).get.priorityId + } + ) + }: _*) - PullRequests .insertAll(pullRequests .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - IssueComments .insertAll(issueComments .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - Labels .insertAll(labels .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - CommitComments .insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - CommitStatuses .insertAll(commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - ProtectedBranches .insertAll(protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - ProtectedBranchContexts.insertAll(protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - DeployKeys .insertAll(deployKeys .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - ReleaseTags .insertAll(releases .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - ReleaseAssets .insertAll(releaseAssets .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) + PullRequests.insertAll(pullRequests.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + IssueComments.insertAll( + issueComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + Labels.insertAll(labels.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + CommitComments.insertAll( + commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + CommitStatuses.insertAll( + commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + ProtectedBranches.insertAll( + protectedBranches.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + ProtectedBranchContexts.insertAll( + protectedBranchContexts.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) + DeployKeys.insertAll(deployKeys.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + ReleaseTags.insertAll(releases.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*) + ReleaseAssets.insertAll( + releaseAssets.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) // Update source repository of pull requests - PullRequests.filter { t => - (t.requestUserName === oldUserName.bind) && (t.requestRepositoryName === oldRepositoryName.bind) - }.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName) + PullRequests + .filter { t => + (t.requestUserName === oldUserName.bind) && (t.requestRepositoryName === oldRepositoryName.bind) + } + .map { t => + t.requestUserName -> t.requestRepositoryName + } + .update(newUserName, newRepositoryName) // Convert labelId val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap - val newLabelMap = Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap - IssueLabels.insertAll(issueLabels.map(x => x.copy( - labelId = newLabelMap(oldLabelMap(x.labelId)), - userName = newUserName, - repositoryName = newRepositoryName - )) :_*) + val newLabelMap = + Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap + IssueLabels.insertAll( + issueLabels.map( + x => + x.copy( + labelId = newLabelMap(oldLabelMap(x.labelId)), + userName = newUserName, + repositoryName = newRepositoryName + ) + ): _* + ) // TODO Drop transferred owner from collaborators? - Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) + Collaborators.insertAll( + collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* + ) // Update activity messages - Activities.filter { t => - (t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}#%") || - (t.message like s"%:${oldUserName}/${oldRepositoryName}@%") - }.map { t => t.activityId -> t.message }.list.foreach { case (activityId, message) => - Activities.filter(_.activityId === activityId.bind).map(_.message).update( - message - .replace(s"[repo:${oldUserName}/${oldRepositoryName}]" ,s"[repo:${newUserName}/${newRepositoryName}]") - .replace(s"[branch:${oldUserName}/${oldRepositoryName}#" ,s"[branch:${newUserName}/${newRepositoryName}#") - .replace(s"[tag:${oldUserName}/${oldRepositoryName}#" ,s"[tag:${newUserName}/${newRepositoryName}#") - .replace(s"[pullreq:${oldUserName}/${oldRepositoryName}#",s"[pullreq:${newUserName}/${newRepositoryName}#") - .replace(s"[issue:${oldUserName}/${oldRepositoryName}#" ,s"[issue:${newUserName}/${newRepositoryName}#") - .replace(s"[commit:${oldUserName}/${oldRepositoryName}@" ,s"[commit:${newUserName}/${newRepositoryName}@") - ) - } + Activities + .filter { t => + (t.message like s"%:${oldUserName}/${oldRepositoryName}]%") || + (t.message like s"%:${oldUserName}/${oldRepositoryName}#%") || + (t.message like s"%:${oldUserName}/${oldRepositoryName}@%") + } + .map { t => + t.activityId -> t.message + } + .list + .foreach { + case (activityId, message) => + Activities + .filter(_.activityId === activityId.bind) + .map(_.message) + .update( + message + .replace( + s"[repo:${oldUserName}/${oldRepositoryName}]", + s"[repo:${newUserName}/${newRepositoryName}]" + ) + .replace( + s"[branch:${oldUserName}/${oldRepositoryName}#", + s"[branch:${newUserName}/${newRepositoryName}#" + ) + .replace(s"[tag:${oldUserName}/${oldRepositoryName}#", s"[tag:${newUserName}/${newRepositoryName}#") + .replace( + s"[pullreq:${oldUserName}/${oldRepositoryName}#", + s"[pullreq:${newUserName}/${newRepositoryName}#" + ) + .replace( + s"[issue:${oldUserName}/${oldRepositoryName}#", + s"[issue:${newUserName}/${newRepositoryName}#" + ) + .replace( + s"[commit:${oldUserName}/${oldRepositoryName}@", + s"[commit:${newUserName}/${newRepositoryName}@" + ) + ) + } } } } def deleteRepository(userName: String, repositoryName: String)(implicit s: Session): Unit = { - Activities .filter(_.byRepository(userName, repositoryName)).delete - Collaborators .filter(_.byRepository(userName, repositoryName)).delete - CommitComments .filter(_.byRepository(userName, repositoryName)).delete - IssueLabels .filter(_.byRepository(userName, repositoryName)).delete - Labels .filter(_.byRepository(userName, repositoryName)).delete - IssueComments .filter(_.byRepository(userName, repositoryName)).delete - PullRequests .filter(_.byRepository(userName, repositoryName)).delete - Issues .filter(_.byRepository(userName, repositoryName)).delete - Priorities .filter(_.byRepository(userName, repositoryName)).delete - IssueId .filter(_.byRepository(userName, repositoryName)).delete - Milestones .filter(_.byRepository(userName, repositoryName)).delete - RepositoryWebHooks .filter(_.byRepository(userName, repositoryName)).delete - RepositoryWebHookEvents .filter(_.byRepository(userName, repositoryName)).delete - DeployKeys .filter(_.byRepository(userName, repositoryName)).delete - ReleaseAssets .filter(_.byRepository(userName, repositoryName)).delete - ReleaseTags .filter(_.byRepository(userName, repositoryName)).delete - Repositories .filter(_.byRepository(userName, repositoryName)).delete + Activities.filter(_.byRepository(userName, repositoryName)).delete + Collaborators.filter(_.byRepository(userName, repositoryName)).delete + CommitComments.filter(_.byRepository(userName, repositoryName)).delete + IssueLabels.filter(_.byRepository(userName, repositoryName)).delete + Labels.filter(_.byRepository(userName, repositoryName)).delete + IssueComments.filter(_.byRepository(userName, repositoryName)).delete + PullRequests.filter(_.byRepository(userName, repositoryName)).delete + Issues.filter(_.byRepository(userName, repositoryName)).delete + Priorities.filter(_.byRepository(userName, repositoryName)).delete + IssueId.filter(_.byRepository(userName, repositoryName)).delete + Milestones.filter(_.byRepository(userName, repositoryName)).delete + RepositoryWebHooks.filter(_.byRepository(userName, repositoryName)).delete + RepositoryWebHookEvents.filter(_.byRepository(userName, repositoryName)).delete + DeployKeys.filter(_.byRepository(userName, repositoryName)).delete + ReleaseAssets.filter(_.byRepository(userName, repositoryName)).delete + ReleaseTags.filter(_.byRepository(userName, repositoryName)).delete + Repositories.filter(_.byRepository(userName, repositoryName)).delete // Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME Repositories - .filter { x => (x.originUserName === userName.bind) && (x.originRepositoryName === repositoryName.bind) } - .map { x => (x.userName, x.repositoryName) } + .filter { x => + (x.originUserName === userName.bind) && (x.originRepositoryName === repositoryName.bind) + } + .map { x => + (x.userName, x.repositoryName) + } .list - .foreach { case (userName, repositoryName) => - Repositories - .filter(_.byRepository(userName, repositoryName)) - .map(x => (x.originUserName?, x.originRepositoryName?)) - .update(None, None) + .foreach { + case (userName, repositoryName) => + Repositories + .filter(_.byRepository(userName, repositoryName)) + .map(x => (x.originUserName ?, x.originRepositoryName ?)) + .update(None, None) } // Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME Repositories - .filter { x => (x.parentUserName === userName.bind) && (x.parentRepositoryName === repositoryName.bind) } - .map { x => (x.userName, x.repositoryName) } + .filter { x => + (x.parentUserName === userName.bind) && (x.parentRepositoryName === repositoryName.bind) + } + .map { x => + (x.userName, x.repositoryName) + } .list - .foreach { case (userName, repositoryName) => - Repositories - .filter(_.byRepository(userName, repositoryName)) - .map(x => (x.parentUserName?, x.parentRepositoryName?)) - .update(None, None) + .foreach { + case (userName, repositoryName) => + Repositories + .filter(_.byRepository(userName, repositoryName)) + .map(x => (x.parentUserName ?, x.parentRepositoryName ?)) + .update(None, None) } } @@ -215,7 +303,7 @@ * @return the list of repository names */ def getRepositoryNamesOfUser(userName: String)(implicit s: Session): List[String] = - Repositories filter(_.userName === userName.bind) map (_.repositoryName) list + Repositories filter (_.userName === userName.bind) map (_.repositoryName) list /** * Returns the specified repository information. @@ -225,11 +313,16 @@ * @return the repository information */ def getRepository(userName: String, repositoryName: String)(implicit s: Session): Option[RepositoryInfo] = { - (Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository => + (Repositories filter { t => + t.byRepository(userName, repositoryName) + } firstOption) map { repository => // for getting issue count and pull request count - val issues = Issues.filter { t => - t.byRepository(repository.userName, repository.repositoryName) && (t.closed === false.bind) - }.map(_.pullRequest).list + val issues = Issues + .filter { t => + t.byRepository(repository.userName, repository.repositoryName) && (t.closed === false.bind) + } + .map(_.pullRequest) + .list new RepositoryInfo( JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName), @@ -240,7 +333,8 @@ repository.originUserName.getOrElse(repository.userName), repository.originRepositoryName.getOrElse(repository.repositoryName) ), - getRepositoryManagers(repository.userName)) + getRepositoryManagers(repository.userName) + ) } } @@ -252,45 +346,66 @@ * @return the repository information list */ def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = { - Repositories.filter { t1 => - (t1.isPrivate === false.bind) || - (t1.userName === userName.bind) || (t1.userName in (GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) || - (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && - ((t2.collaboratorName === userName.bind) || (t2.collaboratorName in GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) - } exists) - }.sortBy(_.lastActivityDate desc).map{ t => - (t.userName, t.repositoryName) - }.list + Repositories + .filter { t1 => + (t1.isPrivate === false.bind) || + (t1.userName === userName.bind) || (t1.userName in (GroupMembers + .filter(_.userName === userName.bind) + .map(_.groupName))) || + (Collaborators.filter { t2 => + t2.byRepository(t1.userName, t1.repositoryName) && + ((t2.collaboratorName === userName.bind) || (t2.collaboratorName in GroupMembers + .filter(_.userName === userName.bind) + .map(_.groupName))) + } exists) + } + .sortBy(_.lastActivityDate desc) + .map { t => + (t.userName, t.repositoryName) + } + .list } - def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)(implicit s: Session): List[RepositoryInfo] = { - Repositories.filter { t1 => - (t1.userName === userName.bind) || (t1.userName in (GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) || - (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && - ((t2.collaboratorName === userName.bind) || (t2.collaboratorName in GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) - } exists) - }.sortBy(_.lastActivityDate desc).list.map{ repository => - new RepositoryInfo( - if(withoutPhysicalInfo){ - new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName) - } else { - JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName) - }, - repository, - if(withoutPhysicalInfo){ - -1 - } else { - getForkedCount( - repository.originUserName.getOrElse(repository.userName), - repository.originRepositoryName.getOrElse(repository.repositoryName) - ) - }, - if(withoutPhysicalInfo){ - Nil - } else { - getRepositoryManagers(repository.userName) - }) - } + def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)( + implicit s: Session + ): List[RepositoryInfo] = { + Repositories + .filter { t1 => + (t1.userName === userName.bind) || (t1.userName in (GroupMembers + .filter(_.userName === userName.bind) + .map(_.groupName))) || + (Collaborators.filter { t2 => + t2.byRepository(t1.userName, t1.repositoryName) && + ((t2.collaboratorName === userName.bind) || (t2.collaboratorName in GroupMembers + .filter(_.userName === userName.bind) + .map(_.groupName))) + } exists) + } + .sortBy(_.lastActivityDate desc) + .list + .map { repository => + new RepositoryInfo( + if (withoutPhysicalInfo) { + new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName) + } else { + JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName) + }, + repository, + if (withoutPhysicalInfo) { + -1 + } else { + getForkedCount( + repository.originUserName.getOrElse(repository.userName), + repository.originRepositoryName.getOrElse(repository.repositoryName) + ) + }, + if (withoutPhysicalInfo) { + Nil + } else { + getRepositoryManagers(repository.userName) + } + ) + } } /** @@ -303,53 +418,63 @@ * branches and tags * @return the repository information which is sorted in descending order of lastActivityDate. */ - def getVisibleRepositories(loginAccount: Option[Account], repositoryUserName: Option[String] = None, - withoutPhysicalInfo: Boolean = false) - (implicit s: Session): List[RepositoryInfo] = { + def getVisibleRepositories( + loginAccount: Option[Account], + repositoryUserName: Option[String] = None, + withoutPhysicalInfo: Boolean = false + )(implicit s: Session): List[RepositoryInfo] = { (loginAccount match { // for Administrators - case Some(x) if(x.isAdmin) => Repositories + case Some(x) if (x.isAdmin) => Repositories // for Normal Users - case Some(x) if(!x.isAdmin) => + case Some(x) if (!x.isAdmin) => Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) || (t.userName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName)) || (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && - ((t2.collaboratorName === x.userName.bind) || (t2.collaboratorName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName))) + ((t2.collaboratorName === x.userName.bind) || (t2.collaboratorName in GroupMembers + .filter(_.userName === x.userName.bind) + .map(_.groupName))) } exists) } // for Guests - case None => Repositories filter(_.isPrivate === false.bind) + case None => Repositories filter (_.isPrivate === false.bind) }).filter { t => - repositoryUserName.map { userName => t.userName === userName.bind } getOrElse LiteralColumn(true) - }.sortBy(_.lastActivityDate desc).list.map { repository => - new RepositoryInfo( - if(withoutPhysicalInfo){ - new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName) - } else { - JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName) - }, - repository, - if(withoutPhysicalInfo){ - -1 - } else { - getForkedCount( - repository.originUserName.getOrElse(repository.userName), - repository.originRepositoryName.getOrElse(repository.repositoryName) - ) - }, - if(withoutPhysicalInfo) { - Nil - } else { - getRepositoryManagers(repository.userName) - }) - } + repositoryUserName.map { userName => + t.userName === userName.bind + } getOrElse LiteralColumn(true) + } + .sortBy(_.lastActivityDate desc) + .list + .map { repository => + new RepositoryInfo( + if (withoutPhysicalInfo) { + new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName) + } else { + JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName) + }, + repository, + if (withoutPhysicalInfo) { + -1 + } else { + getForkedCount( + repository.originUserName.getOrElse(repository.userName), + repository.originRepositoryName.getOrElse(repository.repositoryName) + ) + }, + if (withoutPhysicalInfo) { + Nil + } else { + getRepositoryManagers(repository.userName) + } + ) + } } private def getRepositoryManagers(userName: String)(implicit s: Session): Seq[String] = - if(getAccountByUserName(userName).exists(_.isGroupAccount)){ - getGroupMembers(userName).collect { case x if(x.isManager) => x.userName } + if (getAccountByUserName(userName).exists(_.isGroupAccount)) { + getGroupMembers(userName).collect { case x if (x.isManager) => x.userName } } else { Seq(userName) } @@ -364,24 +489,37 @@ /** * Save repository options. */ - def saveRepositoryOptions(userName: String, repositoryName: String, description: Option[String], isPrivate: Boolean, - issuesOption: String, externalIssuesUrl: Option[String], wikiOption: String, externalWikiUrl: Option[String], - allowFork: Boolean, mergeOptions: Seq[String], defaultMergeOption: String)(implicit s: Session): Unit = { + def saveRepositoryOptions( + userName: String, + repositoryName: String, + description: Option[String], + isPrivate: Boolean, + issuesOption: String, + externalIssuesUrl: Option[String], + wikiOption: String, + externalWikiUrl: Option[String], + allowFork: Boolean, + mergeOptions: Seq[String], + defaultMergeOption: String + )(implicit s: Session): Unit = { - Repositories.filter(_.byRepository(userName, repositoryName)) - .map { r => ( - r.description.?, - r.isPrivate, - r.issuesOption, - r.externalIssuesUrl.?, - r.wikiOption, - r.externalWikiUrl.?, - r.allowFork, - r.mergeOptions, - r.defaultMergeOption, - r.updatedDate - ) } - .update ( + Repositories + .filter(_.byRepository(userName, repositoryName)) + .map { r => + ( + r.description.?, + r.isPrivate, + r.issuesOption, + r.externalIssuesUrl.?, + r.wikiOption, + r.externalWikiUrl.?, + r.allowFork, + r.mergeOptions, + r.defaultMergeOption, + r.updatedDate + ) + } + .update( description, isPrivate, issuesOption, @@ -395,16 +533,22 @@ ) } - def saveRepositoryDefaultBranch(userName: String, repositoryName: String, - defaultBranch: String)(implicit s: Session): Unit = - Repositories.filter(_.byRepository(userName, repositoryName)) - .map { r => r.defaultBranch } - .update (defaultBranch) + def saveRepositoryDefaultBranch(userName: String, repositoryName: String, defaultBranch: String)( + implicit s: Session + ): Unit = + Repositories + .filter(_.byRepository(userName, repositoryName)) + .map { r => + r.defaultBranch + } + .update(defaultBranch) /** * Add collaborator (user or group) to the repository. */ - def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, role: String)(implicit s: Session): Unit = + def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, role: String)( + implicit s: Session + ): Unit = Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role) /** @@ -418,9 +562,10 @@ */ def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[(Collaborator, Boolean)] = Collaborators - .join(Accounts).on(_.collaboratorName === _.userName) + .join(Accounts) + .on(_.collaboratorName === _.userName) .filter { case (t1, t2) => t1.byRepository(userName, repositoryName) } - .map { case (t1, t2) => (t1, t2.groupAccount) } + .map { case (t1, t2) => (t1, t2.groupAccount) } .sortBy { case (t1, t2) => t1.collaboratorName } .list @@ -428,60 +573,79 @@ * Returns the list of all collaborator name and permission which is sorted with ascending order. * If a group is added as a collaborator, this method returns users who are belong to that group. */ - def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[Role] = Nil)(implicit s: Session): List[String] = { + def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[Role] = Nil)( + implicit s: Session + ): List[String] = { val q1 = Collaborators - .join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) } + .join(Accounts) + .on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) } .filter { case (t1, t2) => t1.byRepository(userName, repositoryName) } - .map { case (t1, t2) => (t1.collaboratorName, t1.role) } + .map { case (t1, t2) => (t1.collaboratorName, t1.role) } val q2 = Collaborators - .join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) } - .join(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName } + .join(Accounts) + .on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) } + .join(GroupMembers) + .on { case ((t1, t2), t3) => t2.userName === t3.groupName } .filter { case ((t1, t2), t3) => t1.byRepository(userName, repositoryName) } - .map { case ((t1, t2), t3) => (t3.userName, t1.role) } + .map { case ((t1, t2), t3) => (t3.userName, t1.role) } - q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1) + q1.union(q2) + .list + .filter { x => + filter.isEmpty || filter.exists(_.name == x._2) + } + .map(_._1) } def hasOwnerRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { loginAccount match { - case Some(a) if(a.isAdmin) => true - case Some(a) if(a.userName == owner) => true - case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => true - case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName)) => true - case _ => false + case Some(a) if (a.isAdmin) => true + case Some(a) if (a.userName == owner) => true + case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true + case Some(a) if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName)) => true + case _ => false } } - def hasDeveloperRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { + def hasDeveloperRole(owner: String, repository: String, loginAccount: Option[Account])( + implicit s: Session + ): Boolean = { loginAccount match { - case Some(a) if(a.isAdmin) => true - case Some(a) if(a.userName == owner) => true - case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => true - case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName)) => true + case Some(a) if (a.isAdmin) => true + case Some(a) if (a.userName == owner) => true + case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true + case Some(a) + if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName)) => + true case _ => false } } def hasGuestRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { loginAccount match { - case Some(a) if(a.isAdmin) => true - case Some(a) if(a.userName == owner) => true - case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => true - case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST)).contains(a.userName)) => true + case Some(a) if (a.isAdmin) => true + case Some(a) if (a.userName == owner) => true + case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true + case Some(a) + if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST)) + .contains(a.userName)) => + true case _ => false } } def isReadable(repository: Repository, loginAccount: Option[Account])(implicit s: Session): Boolean = { - if(!repository.isPrivate){ + if (!repository.isPrivate) { true } else { loginAccount match { - case Some(x) if(x.isAdmin) => true - case Some(x) if(repository.userName == x.userName) => true - case Some(x) if(getGroupMembers(repository.userName).exists(_.userName == x.userName)) => true - case Some(x) if(getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) => true + case Some(x) if (x.isAdmin) => true + case Some(x) if (repository.userName == x.userName) => true + case Some(x) if (getGroupMembers(repository.userName).exists(_.userName == x.userName)) => true + case Some(x) + if (getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) => + true case _ => false } } @@ -492,61 +656,81 @@ (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind) }.length).first - def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[Repository] = - Repositories.filter { t => - (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind) - } - .sortBy(_.userName asc).list//.map(t => t.userName -> t.repositoryName).list + Repositories + .filter { t => + (t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind) + } + .sortBy(_.userName asc) + .list //.map(t => t.userName -> t.repositoryName).list private val templateExtensions = Seq("md", "markdown") /** - * Returns content of template set per repository. - * - * @param repository the repository information - * @param fileBaseName the file basename without extension of template - * @return The content of template if the repository has it, otherwise empty string. + * Returns content of template set per repository. + * + * @param repository the repository information + * @param fileBaseName the file basename without extension of template + * @return The content of template if the repository has it, otherwise empty string. */ def getContentTemplate(repository: RepositoryInfo, fileBaseName: String)(implicit s: Session): String = { val withExtFilenames = templateExtensions.map(extension => s"${fileBaseName.toLowerCase()}.${extension}") def choiceTemplate(files: List[FileInfo]): Option[FileInfo] = - files.find { f => - f.name.toLowerCase() == fileBaseName - }.orElse { - files.find(f => withExtFilenames.contains(f.name.toLowerCase())) - } + files + .find { f => + f.name.toLowerCase() == fileBaseName + } + .orElse { + files.find(f => withExtFilenames.contains(f.name.toLowerCase())) + } // Get template file from project root. When didn't find, will lookup default folder. using(Git.open(Directory.getRepositoryDir(repository.owner, repository.name))) { git => - choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".")).orElse { - choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".gitbucket")) - }.map { file => - JGitUtil.getContentFromId(git, file.id, true).collect { - case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes) + choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".")) + .orElse { + choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".gitbucket")) } - } getOrElse None + .map { file => + JGitUtil.getContentFromId(git, file.id, true).collect { + case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes) + } + } getOrElse None } getOrElse "" } } object RepositoryService { - case class RepositoryInfo(owner: String, name: String, repository: Repository, - issueCount: Int, pullCount: Int, forkedCount: Int, - branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]) { + case class RepositoryInfo( + owner: String, + name: String, + repository: Repository, + issueCount: Int, + pullCount: Int, + forkedCount: Int, + branchList: Seq[String], + tags: Seq[JGitUtil.TagInfo], + managers: Seq[String] + ) { /** * Creates instance with issue count and pull request count. */ - def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) = + def this( + repo: JGitUtil.RepositoryInfo, + model: Repository, + issueCount: Int, + pullCount: Int, + forkedCount: Int, + managers: Seq[String] + ) = this(repo.owner, repo.name, model, issueCount, pullCount, forkedCount, repo.branchList, repo.tags, managers) /** * Creates instance without issue and pull request count. */ - def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) = + def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) = this(repo.owner, repo.name, model, 0, 0, forkedCount, repo.branchList, repo.tags, managers) def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name) @@ -554,19 +738,23 @@ def splitPath(path: String): (String, String) = { val id = branchList.collectFirst { - case branch if(path == branch || path.startsWith(branch + "/")) => branch + case branch if (path == branch || path.startsWith(branch + "/")) => branch } orElse tags.collectFirst { - case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name + case tag if (path == tag.name || path.startsWith(tag.name + "/")) => tag.name } getOrElse path.split("/")(0) (id, path.substring(id.length).stripPrefix("/")) } } - def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git" + def httpUrl(owner: String, name: String)(implicit context: Context): String = + s"${context.baseUrl}/git/${owner}/${name}.git" def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] = - if(context.settings.ssh){ - context.settings.sshAddress.map { x => s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" } + if (context.settings.ssh) { + context.settings.sshAddress.map { x => + s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" + } } else None - def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}" + def openRepoUrl(openUrl: String)(implicit context: Context): String = + s"github-${context.platform}://openRepo/${openUrl}" } diff --git a/src/main/scala/gitbucket/core/service/RequestCache.scala b/src/main/scala/gitbucket/core/service/RequestCache.scala index 5e2e754..d1b12de 100644 --- a/src/main/scala/gitbucket/core/service/RequestCache.scala +++ b/src/main/scala/gitbucket/core/service/RequestCache.scala @@ -11,26 +11,32 @@ * It may be called many times in one request, so each method stores * its result into the cache which available during a request. */ -trait RequestCache extends SystemSettingsService with AccountService with IssuesService with RepositoryService - with LabelsService with MilestonesService with PrioritiesService { +trait RequestCache + extends SystemSettingsService + with AccountService + with IssuesService + with RepositoryService + with LabelsService + with MilestonesService + with PrioritiesService { private implicit def context2Session(implicit context: Context): Session = request2Session(context.request) def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = { - context.cache(s"issue.${userName}/${repositoryName}#${issueId}"){ + context.cache(s"issue.${userName}/${repositoryName}#${issueId}") { super.getIssue(userName, repositoryName, issueId) } } def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = { - context.cache(s"account.${userName}"){ + context.cache(s"account.${userName}") { super.getAccountByUserName(userName) } } def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = { - context.cache(s"account.${mailAddress}"){ + context.cache(s"account.${mailAddress}") { super.getAccountByMailAddress(mailAddress) } } diff --git a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala index 2ad682b..76eb761 100644 --- a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala +++ b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala @@ -15,7 +15,7 @@ def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request) def saveSystemSettings(settings: SystemSettings): Unit = { - defining(new java.util.Properties()){ props => + defining(new java.util.Properties()) { props => settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", ""))) settings.information.foreach(x => props.setProperty(Information, x)) props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString) @@ -28,7 +28,7 @@ settings.sshHost.foreach(x => props.setProperty(SshHost, x.trim)) settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString)) props.setProperty(UseSMTP, settings.useSMTP.toString) - if(settings.useSMTP) { + if (settings.useSMTP) { settings.smtp.foreach { smtp => props.setProperty(SmtpHost, smtp.host) smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString)) @@ -41,7 +41,7 @@ } } props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString) - if(settings.ldapAuthentication){ + if (settings.ldapAuthentication) { settings.ldap.map { ldap => props.setProperty(LdapHost, ldap.host) ldap.port.foreach(x => props.setProperty(LdapPort, x.toString)) @@ -63,21 +63,22 @@ props.setProperty(OidcIssuer, oidc.issuer.getValue) props.setProperty(OidcClientId, oidc.clientID.getValue) props.setProperty(OidcClientSecret, oidc.clientSecret.getValue) - oidc.jwsAlgorithm.map { x => props.setProperty(OidcJwsAlgorithm, x.getName) } + oidc.jwsAlgorithm.map { x => + props.setProperty(OidcJwsAlgorithm, x.getName) + } } } props.setProperty(SkinName, settings.skinName.toString) - using(new java.io.FileOutputStream(GitBucketConf)){ out => + using(new java.io.FileOutputStream(GitBucketConf)) { out => props.store(out, null) } } } - def loadSystemSettings(): SystemSettings = { - defining(new java.util.Properties()){ props => - if(GitBucketConf.exists){ - using(new java.io.FileInputStream(GitBucketConf)){ in => + defining(new java.util.Properties()) { props => + if (GitBucketConf.exists) { + using(new java.io.FileInputStream(GitBucketConf)) { in => props.load(in) } } @@ -93,46 +94,54 @@ getValue(props, Ssh, false), getOptionValue[String](props, SshHost, None).map(_.trim), getOptionValue(props, SshPort, Some(DefaultSshPort)), - getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP - if(getValue(props, UseSMTP, getValue(props, Notification, false))){ - Some(Smtp( - getValue(props, SmtpHost, ""), - getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)), - getOptionValue(props, SmtpUser, None), - getOptionValue(props, SmtpPassword, None), - getOptionValue[Boolean](props, SmtpSsl, None), - getOptionValue[Boolean](props, SmtpStarttls, None), - getOptionValue(props, SmtpFromAddress, None), - getOptionValue(props, SmtpFromName, None))) + getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP + if (getValue(props, UseSMTP, getValue(props, Notification, false))) { + Some( + Smtp( + getValue(props, SmtpHost, ""), + getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)), + getOptionValue(props, SmtpUser, None), + getOptionValue(props, SmtpPassword, None), + getOptionValue[Boolean](props, SmtpSsl, None), + getOptionValue[Boolean](props, SmtpStarttls, None), + getOptionValue(props, SmtpFromAddress, None), + getOptionValue(props, SmtpFromName, None) + ) + ) } else { None }, getValue(props, LdapAuthentication, false), - if(getValue(props, LdapAuthentication, false)){ - Some(Ldap( - getValue(props, LdapHost, ""), - getOptionValue(props, LdapPort, Some(DefaultLdapPort)), - getOptionValue(props, LdapBindDN, None), - getOptionValue(props, LdapBindPassword, None), - getValue(props, LdapBaseDN, ""), - getValue(props, LdapUserNameAttribute, ""), - getOptionValue(props, LdapAdditionalFilterCondition, None), - getOptionValue(props, LdapFullNameAttribute, None), - getOptionValue(props, LdapMailAddressAttribute, None), - getOptionValue[Boolean](props, LdapTls, None), - getOptionValue[Boolean](props, LdapSsl, None), - getOptionValue(props, LdapKeystore, None))) + if (getValue(props, LdapAuthentication, false)) { + Some( + Ldap( + getValue(props, LdapHost, ""), + getOptionValue(props, LdapPort, Some(DefaultLdapPort)), + getOptionValue(props, LdapBindDN, None), + getOptionValue(props, LdapBindPassword, None), + getValue(props, LdapBaseDN, ""), + getValue(props, LdapUserNameAttribute, ""), + getOptionValue(props, LdapAdditionalFilterCondition, None), + getOptionValue(props, LdapFullNameAttribute, None), + getOptionValue(props, LdapMailAddressAttribute, None), + getOptionValue[Boolean](props, LdapTls, None), + getOptionValue[Boolean](props, LdapSsl, None), + getOptionValue(props, LdapKeystore, None) + ) + ) } else { None }, getValue(props, OidcAuthentication, false), if (getValue(props, OidcAuthentication, false)) { - Some(OIDC( - getValue(props, OidcIssuer, ""), - getValue(props, OidcClientId, ""), - getValue(props, OidcClientSecret, ""), - getOptionValue(props, OidcJwsAlgorithm, None) - )) + Some( + OIDC( + getValue(props, OidcIssuer, ""), + getValue(props, OidcClientId, ""), + getValue(props, OidcClientSecret, ""), + getOptionValue(props, OidcJwsAlgorithm, None) + ) + ) } else { None }, @@ -164,16 +173,19 @@ ldap: Option[Ldap], oidcAuthentication: Boolean, oidc: Option[OIDC], - skinName: String){ + skinName: String + ) { - def baseUrl(request: HttpServletRequest): String = baseUrl.fold { - val url = request.getRequestURL.toString - val len = url.length - (request.getRequestURI.length - request.getContextPath.length) - url.substring(0, len).stripSuffix("/") - } (_.stripSuffix("/")) + def baseUrl(request: HttpServletRequest): String = + baseUrl.fold { + val url = request.getRequestURL.toString + val len = url.length - (request.getRequestURI.length - request.getContextPath.length) + url.substring(0, len).stripSuffix("/") + }(_.stripSuffix("/")) - def sshAddress:Option[SshAddress] = sshHost.collect { case host if ssh => - SshAddress(host, sshPort.getOrElse(DefaultSshPort), "git") + def sshAddress: Option[SshAddress] = sshHost.collect { + case host if ssh => + SshAddress(host, sshPort.getOrElse(DefaultSshPort), "git") } } @@ -189,16 +201,18 @@ mailAttribute: Option[String], tls: Option[Boolean], ssl: Option[Boolean], - keystore: Option[String]) + keystore: Option[String] + ) - case class OIDC( - issuer: Issuer, - clientID: ClientID, - clientSecret: Secret, - jwsAlgorithm: Option[JWSAlgorithm]) + case class OIDC(issuer: Issuer, clientID: ClientID, clientSecret: Secret, jwsAlgorithm: Option[JWSAlgorithm]) object OIDC { def apply(issuer: String, clientID: String, clientSecret: String, jwsAlgorithm: Option[String]): OIDC = - new OIDC(new Issuer(issuer), new ClientID(clientID), new Secret(clientSecret), jwsAlgorithm.map(JWSAlgorithm.parse)) + new OIDC( + new Issuer(issuer), + new ClientID(clientID), + new Secret(clientSecret), + jwsAlgorithm.map(JWSAlgorithm.parse) + ) } case class Smtp( @@ -209,15 +223,12 @@ ssl: Option[Boolean], starttls: Option[Boolean], fromAddress: Option[String], - fromName: Option[String]) + fromName: Option[String] + ) - case class SshAddress( - host: String, - port: Int, - genericUser: String) + case class SshAddress(host: String, port: Int, genericUser: String) - case class Lfs( - serverUrl: Option[String]) + case class Lfs(serverUrl: Option[String]) val DefaultSshPort = 29418 val DefaultSmtpPort = 25 @@ -265,8 +276,8 @@ private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = { getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse { - defining(props.getProperty(key)){ value => - if(value == null || value.isEmpty){ + defining(props.getProperty(key)) { value => + if (value == null || value.isEmpty) { default } else { convertType(value).asInstanceOf[A] @@ -277,8 +288,8 @@ private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = { getSystemProperty(key).orElse(getEnvironmentVariable(key).orElse { - defining(props.getProperty(key)){ value => - if(value == null || value.isEmpty){ + defining(props.getProperty(key)) { value => + if (value == null || value.isEmpty) { default } else { Some(convertType(value)).asInstanceOf[Option[A]] diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala index 2dfca94..d8c0e48 100644 --- a/src/main/scala/gitbucket/core/service/WebHookService.scala +++ b/src/main/scala/gitbucket/core/service/WebHookService.scala @@ -3,7 +3,19 @@ import fr.brouillard.oss.security.xhub.XHub import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest} import gitbucket.core.api._ -import gitbucket.core.model.{Account, CommitComment, Issue, IssueComment, Label, PullRequest, WebHook, RepositoryWebHook, RepositoryWebHookEvent, AccountWebHook, AccountWebHookEvent} +import gitbucket.core.model.{ + Account, + CommitComment, + Issue, + IssueComment, + Label, + PullRequest, + WebHook, + RepositoryWebHook, + RepositoryWebHookEvent, + AccountWebHook, + AccountWebHookEvent +} import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile.blockingApi._ import org.apache.http.client.utils.URLEncodedUtils @@ -25,84 +37,156 @@ import org.apache.http.client.entity.EntityBuilder import org.apache.http.entity.ContentType - trait WebHookService { import WebHookService._ private val logger = LoggerFactory.getLogger(classOf[WebHookService]) /** get All WebHook informations of repository */ - def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(RepositoryWebHook, Set[WebHook.Event])] = - RepositoryWebHooks.filter(_.byRepository(owner, repository)) - .join(RepositoryWebHookEvents).on { (w, t) => t.byRepositoryWebHook(w) } + def getWebHooks(owner: String, repository: String)( + implicit s: Session + ): List[(RepositoryWebHook, Set[WebHook.Event])] = + RepositoryWebHooks + .filter(_.byRepository(owner, repository)) + .join(RepositoryWebHookEvents) + .on { (w, t) => + t.byRepositoryWebHook(w) + } .map { case (w, t) => w -> t.event } - .list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url) + .list + .groupBy(_._1) + .mapValues(_.map(_._2).toSet) + .toList + .sortBy(_._1.url) /** get All WebHook informations of repository event */ - def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[RepositoryWebHook] = - RepositoryWebHooks.filter(_.byRepository(owner, repository)) - .join(RepositoryWebHookEvents).on { (wh, whe) => whe.byRepositoryWebHook(wh) } - .filter { case (wh, whe) => whe.event === event.bind} - .map{ case (wh, whe) => wh } - .list.distinct + def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)( + implicit s: Session + ): List[RepositoryWebHook] = + RepositoryWebHooks + .filter(_.byRepository(owner, repository)) + .join(RepositoryWebHookEvents) + .on { (wh, whe) => + whe.byRepositoryWebHook(wh) + } + .filter { case (wh, whe) => whe.event === event.bind } + .map { case (wh, whe) => wh } + .list + .distinct /** get All WebHook information from repository to url */ - def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(RepositoryWebHook, Set[WebHook.Event])] = + def getWebHook(owner: String, repository: String, url: String)( + implicit s: Session + ): Option[(RepositoryWebHook, Set[WebHook.Event])] = RepositoryWebHooks .filter(_.byPrimaryKey(owner, repository, url)) - .join(RepositoryWebHookEvents).on { (w, t) => t.byRepositoryWebHook(w) } + .join(RepositoryWebHookEvents) + .on { (w, t) => + t.byRepositoryWebHook(w) + } .map { case (w, t) => w -> t.event } - .list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption + .list + .groupBy(_._1) + .mapValues(_.map(_._2).toSet) + .headOption - def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = { + def addWebHook( + owner: String, + repository: String, + url: String, + events: Set[WebHook.Event], + ctype: WebHookContentType, + token: Option[String] + )(implicit s: Session): Unit = { RepositoryWebHooks insert RepositoryWebHook(owner, repository, url, ctype, token) events.map { event: WebHook.Event => RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event) } } - def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = { - RepositoryWebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token)) + def updateWebHook( + owner: String, + repository: String, + url: String, + events: Set[WebHook.Event], + ctype: WebHookContentType, + token: Option[String] + )(implicit s: Session): Unit = { + RepositoryWebHooks + .filter(_.byPrimaryKey(owner, repository, url)) + .map(w => (w.ctype, w.token)) + .update((ctype, token)) RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete events.map { event: WebHook.Event => RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event) } } - def deleteWebHook(owner: String, repository: String, url :String)(implicit s: Session): Unit = + def deleteWebHook(owner: String, repository: String, url: String)(implicit s: Session): Unit = RepositoryWebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete /** get All AccountWebHook informations of user */ def getAccountWebHooks(owner: String)(implicit s: Session): List[(AccountWebHook, Set[WebHook.Event])] = - AccountWebHooks.filter(_.byAccount(owner)) - .join(AccountWebHookEvents).on { (w, t) => t.byAccountWebHook(w) } + AccountWebHooks + .filter(_.byAccount(owner)) + .join(AccountWebHookEvents) + .on { (w, t) => + t.byAccountWebHook(w) + } .map { case (w, t) => w -> t.event } - .list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url) + .list + .groupBy(_._1) + .mapValues(_.map(_._2).toSet) + .toList + .sortBy(_._1.url) /** get All AccountWebHook informations of repository event */ def getAccountWebHooksByEvent(owner: String, event: WebHook.Event)(implicit s: Session): List[AccountWebHook] = - AccountWebHooks.filter(_.byAccount(owner)) - .join(AccountWebHookEvents).on { (wh, whe) => whe.byAccountWebHook(wh) } - .filter { case (wh, whe) => whe.event === event.bind} - .map{ case (wh, whe) => wh } - .list.distinct + AccountWebHooks + .filter(_.byAccount(owner)) + .join(AccountWebHookEvents) + .on { (wh, whe) => + whe.byAccountWebHook(wh) + } + .filter { case (wh, whe) => whe.event === event.bind } + .map { case (wh, whe) => wh } + .list + .distinct /** get All AccountWebHook information from repository to url */ def getAccountWebHook(owner: String, url: String)(implicit s: Session): Option[(AccountWebHook, Set[WebHook.Event])] = AccountWebHooks .filter(_.byPrimaryKey(owner, url)) - .join(AccountWebHookEvents).on { (w, t) => t.byAccountWebHook(w) } + .join(AccountWebHookEvents) + .on { (w, t) => + t.byAccountWebHook(w) + } .map { case (w, t) => w -> t.event } - .list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption + .list + .groupBy(_._1) + .mapValues(_.map(_._2).toSet) + .headOption - def addAccountWebHook(owner: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = { + def addAccountWebHook( + owner: String, + url: String, + events: Set[WebHook.Event], + ctype: WebHookContentType, + token: Option[String] + )(implicit s: Session): Unit = { AccountWebHooks insert AccountWebHook(owner, url, ctype, token) events.map { event: WebHook.Event => AccountWebHookEvents insert AccountWebHookEvent(owner, url, event) } } - def updateAccountWebHook(owner: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = { + def updateAccountWebHook( + owner: String, + url: String, + events: Set[WebHook.Event], + ctype: WebHookContentType, + token: Option[String] + )(implicit s: Session): Unit = { AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token)) AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete events.map { event: WebHook.Event => @@ -110,29 +194,31 @@ } } - def deleteAccountWebHook(owner: String, url :String)(implicit s: Session): Unit = + def deleteAccountWebHook(owner: String, url: String)(implicit s: Session): Unit = AccountWebHooks.filter(_.byPrimaryKey(owner, url)).delete - def callWebHookOf(owner: String, repository: String, event: WebHook.Event)(makePayload: => Option[WebHookPayload]) - (implicit s: Session, c: JsonFormat.Context): Unit = { + def callWebHookOf(owner: String, repository: String, event: WebHook.Event)( + makePayload: => Option[WebHookPayload] + )(implicit s: Session, c: JsonFormat.Context): Unit = { val webHooks = getWebHooksByEvent(owner, repository, event) - if(webHooks.nonEmpty){ + if (webHooks.nonEmpty) { makePayload.map(callWebHook(event, webHooks, _)) } val accountWebHooks = getAccountWebHooksByEvent(owner, event) - if(accountWebHooks.nonEmpty){ + if (accountWebHooks.nonEmpty) { makePayload.map(callWebHook(event, accountWebHooks, _)) } } - def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload) - (implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = { + def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload)( + implicit c: JsonFormat.Context + ): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = { import org.apache.http.impl.client.HttpClientBuilder import ExecutionContext.Implicits.global // TODO Shouldn't use the default execution context import org.apache.http.protocol.HttpContext import org.apache.http.client.methods.HttpPost - if(webHooks.nonEmpty){ + if (webHooks.nonEmpty) { val json = JsonFormat(payload) webHooks.map { webHook => @@ -143,7 +229,7 @@ reqPromise.success(res) } } - try{ + try { val httpClient = HttpClientBuilder.create.useSystemProperties.addInterceptorLast(itcp).build logger.debug(s"start web hook invocation for ${webHook.url}") val httpPost = new HttpPost(webHook.url) @@ -154,20 +240,38 @@ webHook.ctype match { case WebHookContentType.FORM => { - val params: java.util.List[NameValuePair] = new java.util.ArrayList() + val params: java.util.List[NameValuePair] = new java.util.ArrayList() params.add(new BasicNameValuePair("payload", json)) def postContent = new UrlEncodedFormEntity(params, "UTF-8") httpPost.setEntity(postContent) if (webHook.token.exists(_.trim.nonEmpty)) { // TODO find a better way and see how to extract content from postContent val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8") - httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.get, contentAsBytes)) + httpPost.addHeader( + "X-Hub-Signature", + XHub.generateHeaderXHubToken( + XHubConverter.HEXA_LOWERCASE, + XHubDigest.SHA1, + webHook.token.get, + contentAsBytes + ) + ) } } case WebHookContentType.JSON => { - httpPost.setEntity(EntityBuilder.create().setContentType(ContentType.APPLICATION_JSON).setText(json).build()) + httpPost.setEntity( + EntityBuilder.create().setContentType(ContentType.APPLICATION_JSON).setText(json).build() + ) if (webHook.token.exists(_.trim.nonEmpty)) { - httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8"))) + httpPost.addHeader( + "X-Hub-Signature", + XHub.generateHeaderXHubToken( + XHubConverter.HEXA_LOWERCASE, + XHubDigest.SHA1, + webHook.token.orNull, + json.getBytes("UTF-8") + ) + ) } } } @@ -178,7 +282,7 @@ res } catch { case e: Throwable => { - if(!reqPromise.isCompleted){ + if (!reqPromise.isCompleted) { reqPromise.failure(e) } throw e @@ -198,103 +302,138 @@ } } - trait WebHookPullRequestService extends WebHookService { self: AccountService with RepositoryService with PullRequestService with IssuesService => import WebHookService._ // https://developer.github.com/v3/activity/events/types/#issuesevent - def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: Account) - (implicit s: Session, context: JsonFormat.Context): Unit = { - callWebHookOf(repository.owner, repository.name, WebHook.Issues){ + def callIssuesWebHook( + action: String, + repository: RepositoryService.RepositoryInfo, + issue: Issue, + baseUrl: String, + sender: Account + )(implicit s: Session, context: JsonFormat.Context): Unit = { + callWebHookOf(repository.owner, repository.name, WebHook.Issues) { val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender)) - for{ + for { repoOwner <- users.get(repository.owner) issueUser <- users.get(issue.openedUserName) } yield { WebHookIssuesPayload( - action = action, - number = issue.issueId, + action = action, + number = issue.issueId, repository = ApiRepository(repository, ApiUser(repoOwner)), - issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser), - getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))), - sender = ApiUser(sender)) + issue = ApiIssue( + issue, + RepositoryName(repository), + ApiUser(issueUser), + getIssueLabels(repository.owner, repository.name, issue.issueId) + .map(ApiLabel(_, RepositoryName(repository))) + ), + sender = ApiUser(sender) + ) } } } - def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account) - (implicit s: Session, c: JsonFormat.Context): Unit = { + def callPullRequestWebHook( + action: String, + repository: RepositoryService.RepositoryInfo, + issueId: Int, + baseUrl: String, + sender: Account + )(implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ - callWebHookOf(repository.owner, repository.name, WebHook.PullRequest){ - for{ + callWebHookOf(repository.owner, repository.name, WebHook.PullRequest) { + for { (issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) - users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender)) + users = getAccountsByUserNames( + Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), + Set(sender) + ) baseOwner <- users.get(repository.owner) headOwner <- users.get(pullRequest.requestUserName) issueUser <- users.get(issue.openedUserName) - assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) } - headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) - labels = getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))) + assignee = issue.assignedUserName.flatMap { userName => + getAccountByUserName(userName, false) + } + headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) + labels = getIssueLabels(repository.owner, repository.name, issue.issueId) + .map(ApiLabel(_, RepositoryName(repository))) } yield { WebHookPullRequestPayload( - action = action, - issue = issue, - issueUser = issueUser, - assignee = assignee, - pullRequest = pullRequest, + action = action, + issue = issue, + issueUser = issueUser, + assignee = assignee, + pullRequest = pullRequest, headRepository = headRepo, - headOwner = headOwner, + headOwner = headOwner, baseRepository = repository, - baseOwner = baseOwner, - labels = labels, - sender = sender, - mergedComment = getMergedComment(repository.owner, repository.name, issueId) + baseOwner = baseOwner, + labels = labels, + sender = sender, + mergedComment = getMergedComment(repository.owner, repository.name, issueId) ) } } } /** @return Map[(issue, issueUser, pullRequest, baseOwner, headOwner), webHooks] */ - def getPullRequestsByRequestForWebhook(userName:String, repositoryName:String, branch:String) - (implicit s: Session): Map[(Issue, Account, PullRequest, Account, Account), List[RepositoryWebHook]] = - (for{ - is <- Issues if is.closed === false.bind + def getPullRequestsByRequestForWebhook(userName: String, repositoryName: String, branch: String)( + implicit s: Session + ): Map[(Issue, Account, PullRequest, Account, Account), List[RepositoryWebHook]] = + (for { + is <- Issues if is.closed === false.bind pr <- PullRequests if pr.byPrimaryKey(is.userName, is.repositoryName, is.issueId) - if pr.requestUserName === userName.bind - if pr.requestRepositoryName === repositoryName.bind - if pr.requestBranch === branch.bind + if pr.requestUserName === userName.bind + if pr.requestRepositoryName === repositoryName.bind + if pr.requestBranch === branch.bind bu <- Accounts if bu.userName === pr.userName ru <- Accounts if ru.userName === pr.requestUserName iu <- Accounts if iu.userName === is.openedUserName - wh <- RepositoryWebHooks if wh.byRepository(is.userName , is.repositoryName) - wht <- RepositoryWebHookEvents if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byRepositoryWebHook(wh) + wh <- RepositoryWebHooks if wh.byRepository(is.userName, is.repositoryName) + wht <- RepositoryWebHookEvents + if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byRepositoryWebHook(wh) } yield { ((is, iu, pr, bu, ru), wh) }).list.groupBy(_._1).mapValues(_.map(_._2)) - def callPullRequestWebHookByRequestBranch(action: String, requestRepository: RepositoryService.RepositoryInfo, requestBranch: String, baseUrl: String, sender: Account) - (implicit s: Session, c: JsonFormat.Context): Unit = { + def callPullRequestWebHookByRequestBranch( + action: String, + requestRepository: RepositoryService.RepositoryInfo, + requestBranch: String, + baseUrl: String, + sender: Account + )(implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ - for{ - ((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch) - assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) } + for { + ((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook( + requestRepository.owner, + requestRepository.name, + requestBranch + ) + assignee = issue.assignedUserName.flatMap { userName => + getAccountByUserName(userName, false) + } baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName) - labels = getIssueLabels(pullRequest.userName, pullRequest.repositoryName, issue.issueId).map(ApiLabel(_, RepositoryName(pullRequest.userName, pullRequest.repositoryName))) + labels = getIssueLabels(pullRequest.userName, pullRequest.repositoryName, issue.issueId) + .map(ApiLabel(_, RepositoryName(pullRequest.userName, pullRequest.repositoryName))) } yield { val payload = WebHookPullRequestPayload( - action = action, - issue = issue, - issueUser = issueUser, - assignee = assignee, - pullRequest = pullRequest, + action = action, + issue = issue, + issueUser = issueUser, + assignee = assignee, + pullRequest = pullRequest, headRepository = requestRepository, - headOwner = headOwner, + headOwner = headOwner, baseRepository = baseRepo, - baseOwner = baseOwner, - labels = labels, - sender = sender, - mergedComment = getMergedComment(baseRepo.owner, baseRepo.name, issue.issueId) + baseOwner = baseOwner, + labels = labels, + sender = sender, + mergedComment = getMergedComment(baseRepo.owner, baseRepo.name, issue.issueId) ) callWebHook(WebHook.PullRequest, webHooks, payload) @@ -305,34 +444,44 @@ trait WebHookPullRequestReviewCommentService extends WebHookService { self: AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService => - def callPullRequestReviewCommentWebHook(action: String, comment: CommitComment, repository: RepositoryService.RepositoryInfo, - issue: Issue, pullRequest: PullRequest, baseUrl: String, sender: Account) - (implicit s: Session, c: JsonFormat.Context): Unit = { + def callPullRequestReviewCommentWebHook( + action: String, + comment: CommitComment, + repository: RepositoryService.RepositoryInfo, + issue: Issue, + pullRequest: PullRequest, + baseUrl: String, + sender: Account + )(implicit s: Session, c: JsonFormat.Context): Unit = { import WebHookService._ - callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment){ - val users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender)) - for{ + callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment) { + val users = + getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender)) + for { baseOwner <- users.get(repository.owner) headOwner <- users.get(pullRequest.requestUserName) issueUser <- users.get(issue.openedUserName) - assignee = issue.assignedUserName.flatMap { userName => getAccountByUserName(userName, false) } - headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) - labels = getIssueLabels(pullRequest.userName, pullRequest.repositoryName, issue.issueId).map(ApiLabel(_, RepositoryName(pullRequest.userName, pullRequest.repositoryName))) + assignee = issue.assignedUserName.flatMap { userName => + getAccountByUserName(userName, false) + } + headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) + labels = getIssueLabels(pullRequest.userName, pullRequest.repositoryName, issue.issueId) + .map(ApiLabel(_, RepositoryName(pullRequest.userName, pullRequest.repositoryName))) } yield { WebHookPullRequestReviewCommentPayload( - action = action, - comment = comment, - issue = issue, - issueUser = issueUser, - assignee = assignee, - pullRequest = pullRequest, + action = action, + comment = comment, + issue = issue, + issueUser = issueUser, + assignee = assignee, + pullRequest = pullRequest, headRepository = headRepo, - headOwner = headOwner, + headOwner = headOwner, baseRepository = repository, - baseOwner = baseOwner, - labels = labels, - sender = sender, - mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) + baseOwner = baseOwner, + labels = labels, + sender = sender, + mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) ) } } @@ -343,26 +492,34 @@ self: AccountService with RepositoryService with PullRequestService with IssuesService => import WebHookService._ - def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: Account) - (implicit s: Session, c: JsonFormat.Context): Unit = { - callWebHookOf(repository.owner, repository.name, WebHook.IssueComment){ - for{ + def callIssueCommentWebHook( + repository: RepositoryService.RepositoryInfo, + issue: Issue, + issueCommentId: Int, + sender: Account + )(implicit s: Session, c: JsonFormat.Context): Unit = { + callWebHookOf(repository.owner, repository.name, WebHook.IssueComment) { + for { issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString()) - users = getAccountsByUserNames(Set(issue.openedUserName, repository.owner, issueComment.commentedUserName), Set(sender)) + users = getAccountsByUserNames( + Set(issue.openedUserName, repository.owner, issueComment.commentedUserName), + Set(sender) + ) issueUser <- users.get(issue.openedUserName) repoOwner <- users.get(repository.owner) commenter <- users.get(issueComment.commentedUserName) labels = getIssueLabels(repository.owner, repository.name, issue.issueId) } yield { WebHookIssueCommentPayload( - issue = issue, - issueUser = issueUser, - comment = issueComment, - commentUser = commenter, - repository = repository, + issue = issue, + issueUser = issueUser, + comment = issueComment, + commentUser = commenter, + repository = repository, repositoryUser = repoOwner, - sender = sender, - labels = labels) + sender = sender, + labels = labels + ) } } } @@ -379,24 +536,30 @@ ref_type: String, master_branch: String, repository: ApiRepository - ) extends FieldSerializable with WebHookPayload { + ) extends FieldSerializable + with WebHookPayload { val pusher_type = "user" } object WebHookCreatePayload { - def apply(git: Git, sender: Account, refName: String, repositoryInfo: RepositoryInfo, - commits: List[CommitInfo], repositoryOwner: Account, - ref: String, refType: String): WebHookCreatePayload = + def apply( + git: Git, + sender: Account, + refName: String, + repositoryInfo: RepositoryInfo, + commits: List[CommitInfo], + repositoryOwner: Account, + ref: String, + refType: String + ): WebHookCreatePayload = WebHookCreatePayload( - sender = ApiUser(sender), - ref = ref, - ref_type = refType, - description = repositoryInfo.repository.description.getOrElse(""), + sender = ApiUser(sender), + ref = ref, + ref_type = refType, + description = repositoryInfo.repository.description.getOrElse(""), master_branch = repositoryInfo.repository.defaultBranch, - repository = ApiRepository.forWebhookPayload( - repositoryInfo, - owner= ApiUser(repositoryOwner)) + repository = ApiRepository.forWebhookPayload(repositoryInfo, owner = ApiUser(repositoryOwner)) ) } @@ -409,30 +572,38 @@ after: String, commits: List[ApiCommit], repository: ApiRepository - ) extends FieldSerializable with WebHookPayload { + ) extends FieldSerializable + with WebHookPayload { val compare = commits.size match { - case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initialized repository - case 1 => ApiPath(s"/${repository.full_name}/commit/${after}") - case _ if before.forall(_=='0') => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}") - case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}") + case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initialized repository + case 1 => ApiPath(s"/${repository.full_name}/commit/${after}") + case _ if before.forall(_ == '0') => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}") + case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}") } val head_commit = commits.lastOption } object WebHookPushPayload { - def apply(git: Git, sender: Account, refName: String, repositoryInfo: RepositoryInfo, - commits: List[CommitInfo], repositoryOwner: Account, - newId: ObjectId, oldId: ObjectId): WebHookPushPayload = + def apply( + git: Git, + sender: Account, + refName: String, + repositoryInfo: RepositoryInfo, + commits: List[CommitInfo], + repositoryOwner: Account, + newId: ObjectId, + oldId: ObjectId + ): WebHookPushPayload = WebHookPushPayload( - pusher = ApiPusher(sender), - sender = ApiUser(sender), - ref = refName, - before = ObjectId.toString(oldId), - after = ObjectId.toString(newId), - commits = commits.map{ commit => ApiCommit.forWebhookPayload(git, RepositoryName(repositoryInfo), commit) }, - repository = ApiRepository.forWebhookPayload( - repositoryInfo, - owner= ApiUser(repositoryOwner)) + pusher = ApiPusher(sender), + sender = ApiUser(sender), + ref = refName, + before = ObjectId.toString(oldId), + after = ObjectId.toString(newId), + commits = commits.map { commit => + ApiCommit.forWebhookPayload(git, RepositoryName(repositoryInfo), commit) + }, + repository = ApiRepository.forWebhookPayload(repositoryInfo, owner = ApiUser(repositoryOwner)) ) def createDummyPayload(sender: Account): WebHookPushPayload = @@ -453,7 +624,8 @@ number: Int, repository: ApiRepository, issue: ApiIssue, - sender: ApiUser) extends WebHookPayload + sender: ApiUser + ) extends WebHookPayload // https://developer.github.com/v3/activity/events/types/#pullrequestevent case class WebHookPullRequestPayload( @@ -464,40 +636,42 @@ sender: ApiUser ) extends WebHookPayload - object WebHookPullRequestPayload{ - def apply(action: String, - issue: Issue, - issueUser: Account, - assignee: Option[Account], - pullRequest: PullRequest, - headRepository: RepositoryInfo, - headOwner: Account, - baseRepository: RepositoryInfo, - baseOwner: Account, - labels: List[ApiLabel], - sender: Account, - mergedComment: Option[(IssueComment, Account)]): WebHookPullRequestPayload = { + object WebHookPullRequestPayload { + def apply( + action: String, + issue: Issue, + issueUser: Account, + assignee: Option[Account], + pullRequest: PullRequest, + headRepository: RepositoryInfo, + headOwner: Account, + baseRepository: RepositoryInfo, + baseOwner: Account, + labels: List[ApiLabel], + sender: Account, + mergedComment: Option[(IssueComment, Account)] + ): WebHookPullRequestPayload = { val headRepoPayload = ApiRepository(headRepository, headOwner) val baseRepoPayload = ApiRepository(baseRepository, baseOwner) val senderPayload = ApiUser(sender) val pr = ApiPullRequest( - issue = issue, - pullRequest = pullRequest, - headRepo = headRepoPayload, - baseRepo = baseRepoPayload, - user = ApiUser(issueUser), - labels = labels, - assignee = assignee.map(ApiUser.apply), + issue = issue, + pullRequest = pullRequest, + headRepo = headRepoPayload, + baseRepo = baseRepoPayload, + user = ApiUser(issueUser), + labels = labels, + assignee = assignee.map(ApiUser.apply), mergedComment = mergedComment ) WebHookPullRequestPayload( - action = action, - number = issue.issueId, - repository = pr.base.repo, + action = action, + number = issue.issueId, + repository = pr.base.repo, pull_request = pr, - sender = senderPayload + sender = senderPayload ) } } @@ -513,20 +687,28 @@ object WebHookIssueCommentPayload { def apply( - issue: Issue, - issueUser: Account, - comment: IssueComment, - commentUser: Account, - repository: RepositoryInfo, - repositoryUser: Account, - sender: Account, - labels: List[Label]): WebHookIssueCommentPayload = + issue: Issue, + issueUser: Account, + comment: IssueComment, + commentUser: Account, + repository: RepositoryInfo, + repositoryUser: Account, + sender: Account, + labels: List[Label] + ): WebHookIssueCommentPayload = WebHookIssueCommentPayload( - action = "created", - repository = ApiRepository(repository, repositoryUser), - issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser), labels.map(ApiLabel(_, RepositoryName(repository)))), - comment = ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest), - sender = ApiUser(sender)) + action = "created", + repository = ApiRepository(repository, repositoryUser), + issue = ApiIssue( + issue, + RepositoryName(repository), + ApiUser(issueUser), + labels.map(ApiLabel(_, RepositoryName(repository))) + ), + comment = + ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest), + sender = ApiUser(sender) + ) } // https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent @@ -559,25 +741,26 @@ val senderPayload = ApiUser(sender) WebHookPullRequestReviewCommentPayload( - action = action, - comment = ApiPullRequestReviewComment( - comment = comment, - commentedUser = senderPayload, + action = action, + comment = ApiPullRequestReviewComment( + comment = comment, + commentedUser = senderPayload, repositoryName = RepositoryName(baseRepository), - issueId = issue.issueId + issueId = issue.issueId ), pull_request = ApiPullRequest( - issue = issue, - pullRequest = pullRequest, - headRepo = headRepoPayload, - baseRepo = baseRepoPayload, - user = ApiUser(issueUser), - labels = labels, - assignee = assignee.map(ApiUser.apply), + issue = issue, + pullRequest = pullRequest, + headRepo = headRepoPayload, + baseRepo = baseRepoPayload, + user = ApiUser(issueUser), + labels = labels, + assignee = assignee.map(ApiUser.apply), mergedComment = mergedComment ), - repository = baseRepoPayload, - sender = senderPayload) + repository = baseRepoPayload, + sender = senderPayload + ) } } @@ -614,14 +797,15 @@ sender: Account ): WebHookGollumPayload = { WebHookGollumPayload( - pages = pages.map { case (action, pageName, sha) => - WebHookGollumPagePayload( - action = action, - page_name = pageName, - title = pageName, - sha = sha, - html_url = ApiPath(s"/${RepositoryName(repository).fullName}/wiki/${StringUtil.urlDecode(pageName)}") - ) + pages = pages.map { + case (action, pageName, sha) => + WebHookGollumPagePayload( + action = action, + page_name = pageName, + title = pageName, + sha = sha, + html_url = ApiPath(s"/${RepositoryName(repository).fullName}/wiki/${StringUtil.urlDecode(pageName)}") + ) }, repository = ApiRepository(repository, repositoryUser), sender = ApiUser(sender) diff --git a/src/main/scala/gitbucket/core/service/WikiService.scala b/src/main/scala/gitbucket/core/service/WikiService.scala index 921db3f..9330fa4 100644 --- a/src/main/scala/gitbucket/core/service/WikiService.scala +++ b/src/main/scala/gitbucket/core/service/WikiService.scala @@ -39,12 +39,11 @@ */ case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date) + def wikiHttpUrl(repositoryInfo: RepositoryInfo)(implicit context: Context): String = + RepositoryService.httpUrl(repositoryInfo.owner, repositoryInfo.name + ".wiki") - def wikiHttpUrl(repositoryInfo: RepositoryInfo)(implicit context: Context): String - = RepositoryService.httpUrl(repositoryInfo.owner, repositoryInfo.name + ".wiki") - - def wikiSshUrl(repositoryInfo: RepositoryInfo)(implicit context: Context): Option[String] - = RepositoryService.sshUrl(repositoryInfo.owner, repositoryInfo.name + ".wiki") + def wikiSshUrl(repositoryInfo: RepositoryInfo)(implicit context: Context): Option[String] = + RepositoryService.sshUrl(repositoryInfo.owner, repositoryInfo.name + ".wiki") } @@ -52,11 +51,20 @@ import WikiService._ def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit = - LockUtil.lock(s"${owner}/${repository}/wiki"){ - defining(Directory.getWikiRepositoryDir(owner, repository)){ dir => - if(!dir.exists){ + LockUtil.lock(s"${owner}/${repository}/wiki") { + defining(Directory.getWikiRepositoryDir(owner, repository)) { dir => + if (!dir.exists) { JGitUtil.initRepository(dir) - saveWikiPage(owner, repository, "Home", "Home", s"Welcome to the ${repository} wiki!!", loginAccount, "Initial Commit", None) + saveWikiPage( + owner, + repository, + "Home", + "Home", + s"Welcome to the ${repository} wiki!!", + loginAccount, + "Initial Commit", + None + ) } } } @@ -65,11 +73,16 @@ * Returns the wiki page. */ def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = { - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - if(!JGitUtil.isEmpty(git)){ + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + if (!JGitUtil.isEmpty(git)) { JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file => - WikiPageInfo(file.name, StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes), - file.author, file.time, file.commitId) + WikiPageInfo( + file.name, + StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes), + file.author, + file.time, + file.commitId + ) } } else None } @@ -79,9 +92,11 @@ * Returns the list of wiki page names. */ def getWikiPageList(owner: String, repository: String): List[String] = { - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - JGitUtil.getFileList(git, "master", ".") - .filter(_.name.endsWith(".md")).filterNot(_.name.startsWith("_")) + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + JGitUtil + .getFileList(git, "master", ".") + .filter(_.name.endsWith(".md")) + .filterNot(_.name.startsWith("_")) .map(_.name.stripSuffix(".md")) .sortBy(x => x) } @@ -90,15 +105,20 @@ /** * Reverts specified changes. */ - def revertWikiPage(owner: String, repository: String, from: String, to: String, - committer: Account, pageName: Option[String]): Boolean = { + def revertWikiPage( + owner: String, + repository: String, + from: String, + to: String, + committer: Account, + pageName: Option[String] + ): Boolean = { case class RevertInfo(operation: String, filePath: String, source: String) try { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - + LockUtil.lock(s"${owner}/${repository}/wiki") { + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => val reader = git.getRepository.newObjectReader val oldTreeIter = new CanonicalTreeParser oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}")) @@ -113,7 +133,7 @@ } } - val patch = using(new java.io.ByteArrayOutputStream()){ out => + val patch = using(new java.io.ByteArrayOutputStream()) { out => val formatter = new DiffFormatter(out) formatter.setRepository(git.getRepository) formatter.format(diffs.asJava) @@ -122,21 +142,22 @@ val p = new Patch() p.parse(new ByteArrayInputStream(patch.getBytes("UTF-8"))) - if(!p.getErrors.isEmpty){ + if (!p.getErrors.isEmpty) { throw new PatchFormatException(p.getErrors()) } val revertInfo = (p.getFiles.asScala.map { fh => fh.getChangeType match { case DiffEntry.ChangeType.MODIFY => { - val source = getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("") + val source = + getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("") val applied = PatchUtil.apply(source, patch, fh) - if(applied != null){ + if (applied != null) { Seq(RevertInfo("ADD", fh.getNewPath, applied)) } else Nil } case DiffEntry.ChangeType.ADD => { val applied = PatchUtil.apply("", patch, fh) - if(applied != null){ + if (applied != null) { Seq(RevertInfo("ADD", fh.getNewPath, applied)) } else Nil } @@ -145,7 +166,7 @@ } case DiffEntry.ChangeType.RENAME => { val applied = PatchUtil.apply("", patch, fh) - if(applied != null){ + if (applied != null) { Seq(RevertInfo("DELETE", fh.getOldPath, ""), RevertInfo("ADD", fh.getNewPath, applied)) } else { Seq(RevertInfo("DELETE", fh.getOldPath, "")) @@ -155,28 +176,41 @@ } }).flatten - if(revertInfo.nonEmpty){ - val builder = DirCache.newInCore.builder() + if (revertInfo.nonEmpty) { + val builder = DirCache.newInCore.builder() val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - JGitUtil.processTree(git, headId){ (path, tree) => - if(!revertInfo.exists(x => x.filePath == path)){ + JGitUtil.processTree(git, headId) { (path, tree) => + if (!revertInfo.exists(x => x.filePath == path)) { builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) } } revertInfo.filter(_.operation == "ADD").foreach { x => - builder.add(JGitUtil.createDirCacheEntry(x.filePath, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8")))) + builder.add( + JGitUtil.createDirCacheEntry( + x.filePath, + FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, x.source.getBytes("UTF-8")) + ) + ) } builder.finish() - JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, committer.fullName, committer.mailAddress, + JGitUtil.createNewCommit( + git, + inserter, + headId, + builder.getDirCache.writeTree(inserter), + Constants.HEAD, + committer.fullName, + committer.mailAddress, pageName match { case Some(x) => s"Revert ${from} ... ${to} on ${x}" case None => s"Revert ${from} ... ${to}" - }) + } + ) } } } @@ -192,46 +226,70 @@ /** * Save the wiki page and return the commit id. */ - def saveWikiPage(owner: String, repository: String, currentPageName: String, newPageName: String, - content: String, committer: Account, message: String, currentId: Option[String]): Option[String] = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - val builder = DirCache.newInCore.builder() + def saveWikiPage( + owner: String, + repository: String, + currentPageName: String, + newPageName: String, + content: String, + committer: Account, + message: String, + currentId: Option[String] + ): Option[String] = { + LockUtil.lock(s"${owner}/${repository}/wiki") { + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + val builder = DirCache.newInCore.builder() val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - var created = true - var updated = false - var removed = false + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") + var created = true + var updated = false + var removed = false - if(headId != null){ - JGitUtil.processTree(git, headId){ (path, tree) => - if(path == currentPageName + ".md" && currentPageName != newPageName){ + if (headId != null) { + JGitUtil.processTree(git, headId) { (path, tree) => + if (path == currentPageName + ".md" && currentPageName != newPageName) { removed = true - } else if(path != newPageName + ".md"){ + } else if (path != newPageName + ".md") { builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) } else { created = false - updated = JGitUtil.getContentFromId(git, tree.getEntryObjectId, true).map(new String(_, "UTF-8") != content).getOrElse(false) + updated = JGitUtil + .getContentFromId(git, tree.getEntryObjectId, true) + .map(new String(_, "UTF-8") != content) + .getOrElse(false) } } } - if(created || updated || removed){ - builder.add(JGitUtil.createDirCacheEntry(newPageName + ".md", FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")))) + if (created || updated || removed) { + builder.add( + JGitUtil.createDirCacheEntry( + newPageName + ".md", + FileMode.REGULAR_FILE, + inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8")) + ) + ) builder.finish() - val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, committer.fullName, committer.mailAddress, - if(message.trim.isEmpty) { - if(removed){ + val newHeadId = JGitUtil.createNewCommit( + git, + inserter, + headId, + builder.getDirCache.writeTree(inserter), + Constants.HEAD, + committer.fullName, + committer.mailAddress, + if (message.trim.isEmpty) { + if (removed) { s"Rename ${currentPageName} to ${newPageName}" - } else if(created){ + } else if (created) { s"Created ${newPageName}" } else { s"Updated ${newPageName}" } } else { message - }) + } + ) Some(newHeadId.getName) } else None @@ -242,26 +300,40 @@ /** * Delete the wiki page. */ - def deleteWikiPage(owner: String, repository: String, pageName: String, - committer: String, mailAddress: String, message: String): Unit = { - LockUtil.lock(s"${owner}/${repository}/wiki"){ - using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git => - val builder = DirCache.newInCore.builder() + def deleteWikiPage( + owner: String, + repository: String, + pageName: String, + committer: String, + mailAddress: String, + message: String + ): Unit = { + LockUtil.lock(s"${owner}/${repository}/wiki") { + using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + val builder = DirCache.newInCore.builder() val inserter = git.getRepository.newObjectInserter() - val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") - var removed = false + val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}") + var removed = false - JGitUtil.processTree(git, headId){ (path, tree) => - if(path != pageName + ".md"){ + JGitUtil.processTree(git, headId) { (path, tree) => + if (path != pageName + ".md") { builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId)) } else { removed = true } } - if(removed){ + if (removed) { builder.finish() - JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, committer, mailAddress, message) + JGitUtil.createNewCommit( + git, + inserter, + headId, + builder.getDirCache.writeTree(inserter), + Constants.HEAD, + committer, + mailAddress, + message + ) } } } diff --git a/src/main/scala/gitbucket/core/servlet/ApiAuthenticationFilter.scala b/src/main/scala/gitbucket/core/servlet/ApiAuthenticationFilter.scala index c3bd85c..cca7d7b 100644 --- a/src/main/scala/gitbucket/core/servlet/ApiAuthenticationFilter.scala +++ b/src/main/scala/gitbucket/core/servlet/ApiAuthenticationFilter.scala @@ -8,7 +8,6 @@ import gitbucket.core.service.{AccessTokenService, AccountService, SystemSettingsService} import gitbucket.core.util.{AuthUtil, Keys} - class ApiAuthenticationFilter extends Filter with AccessTokenService with AccountService with SystemSettingsService { override def init(filterConfig: FilterConfig): Unit = {} @@ -19,15 +18,18 @@ implicit val request = req.asInstanceOf[HttpServletRequest] implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session] val response = res.asInstanceOf[HttpServletResponse] - Option(request.getHeader("Authorization")).map{ - case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(()) - case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(()) - case _ => Left(()) - }.orElse{ - Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_)) - } match { + Option(request.getHeader("Authorization")) + .map { + case auth if auth.startsWith("token ") => + AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(()) + case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(()) + case _ => Left(()) + } + .orElse { + Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_)) + } match { case Some(Right(account)) => request.setAttribute(Keys.Session.LoginAccount, account); chain.doFilter(req, res) - case None => chain.doFilter(req, res) + case None => chain.doFilter(req, res) case Some(Left(_)) => { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) response.setContentType("application/json; charset=utf-8") diff --git a/src/main/scala/gitbucket/core/servlet/CompositeScalatraFilter.scala b/src/main/scala/gitbucket/core/servlet/CompositeScalatraFilter.scala index beff25d..56bf848 100644 --- a/src/main/scala/gitbucket/core/servlet/CompositeScalatraFilter.scala +++ b/src/main/scala/gitbucket/core/servlet/CompositeScalatraFilter.scala @@ -16,43 +16,46 @@ } override def init(filterConfig: FilterConfig): Unit = { - filters.foreach { case (filter, _) => - filter.init(filterConfig) + filters.foreach { + case (filter, _) => + filter.init(filterConfig) } } override def destroy(): Unit = { - filters.foreach { case (filter, _) => - filter.destroy() + filters.foreach { + case (filter, _) => + filter.destroy() } } override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = { val contextPath = request.getServletContext.getContextPath val requestPath = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length) - val checkPath = if(requestPath.endsWith("/")){ + val checkPath = if (requestPath.endsWith("/")) { requestPath } else { requestPath + "/" } - if(!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") && - !checkPath.startsWith("/plugin-assets/")){ + if (!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") && + !checkPath.startsWith("/plugin-assets/")) { filters - .filter { case (_, path) => - val start = path.replaceFirst("/\\*$", "/") - checkPath.startsWith(start) + .filter { + case (_, path) => + val start = path.replaceFirst("/\\*$", "/") + checkPath.startsWith(start) } - .foreach { case (filter, _) => - val mockChain = new MockFilterChain() - filter.doFilter(request, response, mockChain) - if(mockChain.continue == false){ - return () - } + .foreach { + case (filter, _) => + val mockChain = new MockFilterChain() + filter.doFilter(request, response, mockChain) + if (mockChain.continue == false) { + return () + } } } - chain.doFilter(request, response) } diff --git a/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala b/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala index 2febc4e..f542230 100644 --- a/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala +++ b/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala @@ -24,25 +24,31 @@ def destroy(): Unit = {} def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { - val request = req.asInstanceOf[HttpServletRequest] + val request = req.asInstanceOf[HttpServletRequest] val response = res.asInstanceOf[HttpServletResponse] - val wrappedResponse = new HttpServletResponseWrapper(response){ + val wrappedResponse = new HttpServletResponseWrapper(response) { override def setCharacterEncoding(encoding: String) = {} } - val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals(request.getQueryString) + val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals( + request.getQueryString + ) val settings = loadSystemSettings() try { - PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).map { case GitRepositoryRouting(_, _, filter) => - // served by plug-ins - pluginRepository(request, wrappedResponse, chain, settings, isUpdating, filter) + PluginRegistry() + .getRepositoryRouting(request.gitRepositoryPath) + .map { + case GitRepositoryRouting(_, _, filter) => + // served by plug-ins + pluginRepository(request, wrappedResponse, chain, settings, isUpdating, filter) - }.getOrElse { - // default repositories - defaultRepository(request, wrappedResponse, chain, settings, isUpdating) - } + } + .getOrElse { + // default repositories + defaultRepository(request, wrappedResponse, chain, settings, isUpdating) + } } catch { case ex: Exception => { logger.error("error", ex) @@ -51,8 +57,14 @@ } } - private def pluginRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, - settings: SystemSettings, isUpdating: Boolean, filter: GitRepositoryFilter): Unit = { + private def pluginRepository( + request: HttpServletRequest, + response: HttpServletResponse, + chain: FilterChain, + settings: SystemSettings, + isUpdating: Boolean, + filter: GitRepositoryFilter + ): Unit = { Database() withSession { implicit session => val account = for { authorizationHeader <- Option(request.getHeader("Authorization")) @@ -70,8 +82,13 @@ } } - private def defaultRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, - settings: SystemSettings, isUpdating: Boolean): Unit = { + private def defaultRepository( + request: HttpServletRequest, + response: HttpServletResponse, + chain: FilterChain, + settings: SystemSettings, + isUpdating: Boolean + ): Unit = { val action = request.paths match { case Array(_, repositoryOwner, repositoryName, _*) => Database() withSession { implicit session => @@ -85,59 +102,65 @@ val passed = for { authorizationHeader <- Option(request.getHeader("Authorization")) account <- authenticateByHeader(authorizationHeader, settings) - } yield if (isUpdating) { - if (hasDeveloperRole(repository.owner, repository.name, Some(account))) { - request.setAttribute(Keys.Request.UserName, account.userName) - true - } else false - } else if(repository.repository.isPrivate){ - if (hasGuestRole(repository.owner, repository.name, Some(account))) { - request.setAttribute(Keys.Request.UserName, account.userName) - true - } else false - } else true + } yield + if (isUpdating) { + if (hasDeveloperRole(repository.owner, repository.name, Some(account))) { + request.setAttribute(Keys.Request.UserName, account.userName) + true + } else false + } else if (repository.repository.isPrivate) { + if (hasGuestRole(repository.owner, repository.name, Some(account))) { + request.setAttribute(Keys.Request.UserName, account.userName) + true + } else false + } else true passed.getOrElse(false) } - if (execute) { - () => chain.doFilter(request, response) - } else { - () => AuthUtil.requireAuth(response) + if (execute) { () => + chain.doFilter(request, response) + } else { () => + AuthUtil.requireAuth(response) } } - case None => () => { - logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.") - response.sendError(HttpServletResponse.SC_NOT_FOUND) - } + case None => + () => + { + logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.") + response.sendError(HttpServletResponse.SC_NOT_FOUND) + } } } - case _ => () => { - logger.debug(s"Not enough path arguments: ${request.paths}") - response.sendError(HttpServletResponse.SC_NOT_FOUND) - } + case _ => + () => + { + logger.debug(s"Not enough path arguments: ${request.paths}") + response.sendError(HttpServletResponse.SC_NOT_FOUND) + } } action() } /** - * Authenticate by an Authorization header. - * This accepts one of the following credentials: - * - username and password - * - username and personal access token - * - * @param authorizationHeader Authorization header - * @param settings system settings - * @param s database session - * @return an account or none - */ - private def authenticateByHeader(authorizationHeader: String, - settings: SystemSettings)(implicit s: Session): Option[Account] = { + * Authenticate by an Authorization header. + * This accepts one of the following credentials: + * - username and password + * - username and personal access token + * + * @param authorizationHeader Authorization header + * @param settings system settings + * @param s database session + * @return an account or none + */ + private def authenticateByHeader(authorizationHeader: String, settings: SystemSettings)( + implicit s: Session + ): Option[Account] = { val Array(username, password) = AuthUtil.decodeAuthHeader(authorizationHeader).split(":", 2) authenticate(settings, username, password).orElse { AccessTokenService.getAccountByAccessToken(password) match { case Some(account) if account.userName == username => Some(account) - case _ => None + case _ => None } } } diff --git a/src/main/scala/gitbucket/core/servlet/GitLfsTransferServlet.scala b/src/main/scala/gitbucket/core/servlet/GitLfsTransferServlet.scala index 903a1d9..f9f66dd 100644 --- a/src/main/scala/gitbucket/core/servlet/GitLfsTransferServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitLfsTransferServlet.scala @@ -25,17 +25,16 @@ (owner, repository, oid) <- getPathInfo(req, res) if checkToken(req, oid) } yield { val file = new File(FileUtil.getLfsFilePath(owner, repository, oid)) - if(file.exists()){ + 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) => + 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)) + sendError(res, HttpStatus.SC_NOT_FOUND, MessageFormat.format("Object ''{0}'' not found", oid)) } } } @@ -46,7 +45,7 @@ } yield { val file = new File(FileUtil.getLfsFilePath(owner, repository, oid)) FileUtils.forceMkdir(file.getParentFile) - using(req.getInputStream, new FileOutputStream(file)){ (in, out) => + using(req.getInputStream, new FileOutputStream(file)) { (in, out) => IOUtils.copy(in, out) } res.setStatus(HttpStatus.SC_OK) @@ -55,7 +54,7 @@ private def checkToken(req: HttpServletRequest, oid: String): Boolean = { val token = req.getHeader("Authorization") - if(token != null){ + if (token != null) { val Array(expireAt, targetOid) = StringUtil.decodeBlowfish(token).split(" ") oid == targetOid && expireAt.toLong > System.currentTimeMillis } else { @@ -66,18 +65,16 @@ private def getPathInfo(req: HttpServletRequest, res: HttpServletResponse): Option[(String, String, String)] = { req.getRequestURI.substring(1).split("/").reverse match { case Array(oid, repository, owner, _*) => Some((owner, repository, oid)) - case _ => None + case _ => None } } private def sendError(res: HttpServletResponse, status: Int, message: String): Unit = { res.setStatus(status) - using(res.getWriter()){ out => + 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 eac61a0..0a8d956 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -26,7 +26,6 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType import org.json4s.jackson.Serialization._ - /** * Provides Git repository via HTTP. * @@ -50,12 +49,12 @@ override def service(req: HttpServletRequest, res: HttpServletResponse): Unit = { val agent = req.getHeader("USER-AGENT") val index = req.getRequestURI.indexOf(".git") - if(index >= 0 && (agent == null || agent.toLowerCase.indexOf("git") < 0)){ + if (index >= 0 && (agent == null || agent.toLowerCase.indexOf("git") < 0)) { // redirect for browsers val paths = req.getRequestURI.substring(0, index).split("/") res.sendRedirect(baseUrl(req) + "/" + paths.dropRight(1).last + "/" + paths.last) - } else if(req.getMethod.toUpperCase == "POST" && req.getRequestURI.endsWith("/info/lfs/objects/batch")){ + } else if (req.getMethod.toUpperCase == "POST" && req.getRequestURI.endsWith("/info/lfs/objects/batch")) { serviceGitLfsBatchAPI(req, res) } else { @@ -78,39 +77,57 @@ } case Some(baseUrl) => { val index = req.getRequestURI.indexOf(".git") - if(index >= 0){ + if (index >= 0) { req.getRequestURI.substring(0, index).split("/").reverse match { case Array(repository, owner, _*) => val timeout = System.currentTimeMillis + (60000 * 10) // 10 min. val batchResponse = batchRequest.operation match { case "upload" => - GitLfs.BatchUploadResponse("basic", batchRequest.objects.map { requestObject => - GitLfs.BatchResponseObject(requestObject.oid, requestObject.size, true, - GitLfs.Actions( - upload = Some(GitLfs.Action( - href = baseUrl + "/git-lfs/" + owner + "/" + repository + "/" + requestObject.oid, - header = Map("Authorization" -> StringUtil.encodeBlowfish(timeout + " " + requestObject.oid)), - expires_at = new Date(timeout) - )) + GitLfs.BatchUploadResponse( + "basic", + batchRequest.objects.map { requestObject => + GitLfs.BatchResponseObject( + requestObject.oid, + requestObject.size, + true, + GitLfs.Actions( + upload = Some( + GitLfs.Action( + href = baseUrl + "/git-lfs/" + owner + "/" + repository + "/" + requestObject.oid, + header = + Map("Authorization" -> StringUtil.encodeBlowfish(timeout + " " + requestObject.oid)), + expires_at = new Date(timeout) + ) + ) + ) ) - ) - }) + } + ) case "download" => - GitLfs.BatchUploadResponse("basic", batchRequest.objects.map { requestObject => - GitLfs.BatchResponseObject(requestObject.oid, requestObject.size, true, - GitLfs.Actions( - download = Some(GitLfs.Action( - href = baseUrl + "/git-lfs/" + owner + "/" + repository + "/" + requestObject.oid, - header = Map("Authorization" -> StringUtil.encodeBlowfish(timeout + " " + requestObject.oid)), - expires_at = new Date(timeout) - )) + GitLfs.BatchUploadResponse( + "basic", + batchRequest.objects.map { requestObject => + GitLfs.BatchResponseObject( + requestObject.oid, + requestObject.size, + true, + GitLfs.Actions( + download = Some( + GitLfs.Action( + href = baseUrl + "/git-lfs/" + owner + "/" + repository + "/" + requestObject.oid, + header = + Map("Authorization" -> StringUtil.encodeBlowfish(timeout + " " + requestObject.oid)), + expires_at = new Date(timeout) + ) + ) + ) ) - ) - }) + } + ) } res.setContentType("application/vnd.git-lfs+json") - using(res.getWriter){ out => + using(res.getWriter) { out => out.print(write(batchResponse)) out.flush() } @@ -121,18 +138,23 @@ } } -class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest]) extends RepositoryResolver[HttpServletRequest] { +class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest]) + extends RepositoryResolver[HttpServletRequest] { private val resolver = new FileResolver[HttpServletRequest](new File(Directory.GitBucketHome), true) override def open(req: HttpServletRequest, name: String): Repository = { // Rewrite repository path if routing is marched - PluginRegistry().getRepositoryRouting("/" + name).map { case GitRepositoryRouting(urlPattern, localPath, _) => - val path = urlPattern.r.replaceFirstIn(name, localPath) - resolver.open(req, path) - }.getOrElse { - parent.open(req, name) - } + PluginRegistry() + .getRepositoryRouting("/" + name) + .map { + case GitRepositoryRouting(urlPattern, localPath, _) => + val path = urlPattern.r.replaceFirstIn(name, localPath) + resolver.open(req, path) + } + .getOrElse { + parent.open(req, name) + } } } @@ -144,23 +166,25 @@ override def create(request: HttpServletRequest, db: Repository): ReceivePack = { val receivePack = new ReceivePack(db) - if(PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).isEmpty){ + if (PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).isEmpty) { val pusher = request.getAttribute(Keys.Request.UserName).asInstanceOf[String] logger.debug("requestURI: " + request.getRequestURI) logger.debug("pusher:" + pusher) - defining(request.paths){ paths => - val owner = paths(1) + defining(request.paths) { paths => + val owner = paths(1) val repository = paths(2).stripSuffix(".git") logger.debug("repository:" + owner + "/" + repository) val settings = loadSystemSettings() val baseUrl = settings.baseUrl(request) - val sshUrl = settings.sshAddress.map { x => s"${x.genericUser}@${x.host}:${x.port}" } + val sshUrl = settings.sshAddress.map { x => + s"${x.genericUser}@${x.host}:${x.port}" + } - if(!repository.endsWith(".wiki")){ + if (!repository.endsWith(".wiki")) { defining(request) { implicit r => val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl) receivePack.setPreReceiveHook(hook) @@ -168,9 +192,11 @@ } } - if(repository.endsWith(".wiki")){ + if (repository.endsWith(".wiki")) { defining(request) { implicit r => - receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)) + receivePack.setPostReceiveHook( + new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl) + ) } } } @@ -183,10 +209,19 @@ import scala.collection.JavaConverters._ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String]) - extends PostReceiveHook with PreReceiveHook - with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService - with LabelsService with PrioritiesService with MilestonesService - with WebHookPullRequestService with CommitsService { + extends PostReceiveHook + with PreReceiveHook + with RepositoryService + with AccountService + with IssuesService + with ActivityService + with PullRequestService + with WebHookService + with LabelsService + with PrioritiesService + with MilestonesService + with WebHookPullRequestService + with CommitsService { private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) private var existIds: Seq[String] = Nil @@ -232,14 +267,14 @@ } else { command.getType match { case ReceiveCommand.Type.DELETE => Nil - case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name) + case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name) } } val repositoryInfo = getRepository(owner, repository).get // Update default branch if repository is empty and pushed branch is not current default branch - if(JGitUtil.isEmpty(git) && commits.nonEmpty && branchName != repositoryInfo.repository.defaultBranch){ + if (JGitUtil.isEmpty(git) && commits.nonEmpty && branchName != repositoryInfo.repository.defaultBranch) { saveRepositoryDefaultBranch(owner, repository, branchName) // Change repository HEAD using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => @@ -274,24 +309,31 @@ case ReceiveCommand.Type.CREATE => recordCreateBranchActivity(owner, repository, pusher, branchName) case ReceiveCommand.Type.UPDATE => recordPushActivity(owner, repository, pusher, branchName, newCommits) case ReceiveCommand.Type.DELETE => recordDeleteBranchActivity(owner, repository, pusher, branchName) - case _ => + case _ => } } else if (refName(1) == "tags") { command.getType match { - case ReceiveCommand.Type.CREATE => recordCreateTagActivity(owner, repository, pusher, branchName, newCommits) - case ReceiveCommand.Type.DELETE => recordDeleteTagActivity(owner, repository, pusher, branchName, newCommits) + case ReceiveCommand.Type.CREATE => + recordCreateTagActivity(owner, repository, pusher, branchName, newCommits) + case ReceiveCommand.Type.DELETE => + recordDeleteTagActivity(owner, repository, pusher, branchName, newCommits) case _ => } } if (refName(1) == "heads") { command.getType match { - case ReceiveCommand.Type.CREATE | - ReceiveCommand.Type.UPDATE | - ReceiveCommand.Type.UPDATE_NONFASTFORWARD => + case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE | + ReceiveCommand.Type.UPDATE_NONFASTFORWARD => updatePullRequests(owner, repository, branchName) getAccountByUserName(pusher).map { pusherAccount => - callPullRequestWebHookByRequestBranch("synchronize", repositoryInfo, branchName, baseUrl, pusherAccount) + callPullRequestWebHookByRequestBranch( + "synchronize", + repositoryInfo, + branchName, + baseUrl, + pusherAccount + ) } case _ => } @@ -301,21 +343,37 @@ callWebHookOf(owner, repository, WebHook.Push) { for { pusherAccount <- getAccountByUserName(pusher) - ownerAccount <- getAccountByUserName(owner) + ownerAccount <- getAccountByUserName(owner) } yield { - WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount, - newId = command.getNewId(), oldId = command.getOldId()) + WebHookPushPayload( + git, + pusherAccount, + command.getRefName, + repositoryInfo, + newCommits, + ownerAccount, + newId = command.getNewId(), + oldId = command.getOldId() + ) } } - if (command.getType == ReceiveCommand.Type.CREATE) { + if (command.getType == ReceiveCommand.Type.CREATE) { callWebHookOf(owner, repository, WebHook.Create) { for { pusherAccount <- getAccountByUserName(pusher) - ownerAccount <- getAccountByUserName(owner) + ownerAccount <- getAccountByUserName(owner) } yield { val refType = if (refName(1) == "tags") "tag" else "branch" - WebHookCreatePayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount, - ref = branchName, refType = refType) + WebHookCreatePayload( + git, + pusherAccount, + command.getRefName, + repositoryInfo, + newCommits, + ownerAccount, + ref = branchName, + refType = refType + ) } } } @@ -338,7 +396,10 @@ } class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String]) - extends PostReceiveHook with WebHookService with AccountService with RepositoryService { + extends PostReceiveHook + with WebHookService + with AccountService + with RepositoryService { private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook]) @@ -353,37 +414,40 @@ } else { command.getType match { case ReceiveCommand.Type.DELETE => None - case _ => Some((command.getOldId.getName, command.getNewId.name)) + case _ => Some((command.getOldId.getName, command.getNewId.name)) } } - commitIds.map { case (oldCommitId, newCommitId) => - val commits = using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => - JGitUtil.getCommitLog(git, oldCommitId, newCommitId).flatMap { commit => - val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false) - diffs.collect { case diff if diff.newPath.toLowerCase.endsWith(".md") => - val action = if(diff.changeType == ChangeType.ADD) "created" else "edited" - val fileName = diff.newPath - (action, fileName, commit.id) + commitIds.map { + case (oldCommitId, newCommitId) => + val commits = using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => + JGitUtil.getCommitLog(git, oldCommitId, newCommitId).flatMap { commit => + val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false) + diffs.collect { + case diff if diff.newPath.toLowerCase.endsWith(".md") => + val action = if (diff.changeType == ChangeType.ADD) "created" else "edited" + val fileName = diff.newPath + (action, fileName, commit.id) + } } } - } - val pages = commits - .groupBy { case (action, fileName, commitId) => fileName } - .map { case (fileName, commits) => - (commits.head._1, fileName, commits.last._3) - } + val pages = commits + .groupBy { case (action, fileName, commitId) => fileName } + .map { + case (fileName, commits) => + (commits.head._1, fileName, commits.last._3) + } - callWebHookOf(owner, repository, WebHook.Gollum) { - for { - pusherAccount <- getAccountByUserName(pusher) - repositoryUser <- getAccountByUserName(owner) - repositoryInfo <- getRepository(owner, repository) - } yield { - WebHookGollumPayload(pages.toSeq, repositoryInfo, repositoryUser, pusherAccount) + callWebHookOf(owner, repository, WebHook.Gollum) { + for { + pusherAccount <- getAccountByUserName(pusher) + repositoryUser <- getAccountByUserName(owner) + repositoryInfo <- getRepository(owner, repository) + } yield { + WebHookGollumPayload(pages.toSeq, repositoryInfo, repositoryUser, pusherAccount) + } } - } } } } catch { diff --git a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala index 93ec28f..326608b 100644 --- a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala +++ b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala @@ -24,7 +24,6 @@ import scala.collection.JavaConverters._ - /** * Initialize GitBucket system. * Update database schema and load plug-ins automatically in the context initializing. @@ -34,8 +33,9 @@ private val logger = LoggerFactory.getLogger(classOf[InitializeListener]) // ActorSystem for Quartz scheduler - private val system = ActorSystem("job", ConfigFactory.parseString( - """ + private val system = ActorSystem( + "job", + ConfigFactory.parseString(""" |akka { | daemonic = on | coordinated-shutdown.run-by-jvm-shutdown-hook = off @@ -47,11 +47,12 @@ | } | } |} - """.stripMargin)) + """.stripMargin) + ) override def contextInitialized(event: ServletContextEvent): Unit = { val dataDir = event.getServletContext.getInitParameter("gitbucket.home") - if(dataDir != null){ + if (dataDir != null) { System.setProperty("gitbucket.home", dataDir) } org.h2.Driver.load() @@ -65,19 +66,22 @@ // Run normal migration logger.info("Start schema update") - new Solidbase().migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule) + new Solidbase() + .migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule) // Rescue code for users who updated from 3.14 to 4.0.0 // https://github.com/gitbucket/gitbucket/issues/1227 val currentVersion = manager.getCurrentVersion(GitBucketCoreModule.getModuleId) - val databaseVersion = if(currentVersion == "4.0"){ + val databaseVersion = if (currentVersion == "4.0") { manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0") "4.0.0" } else currentVersion val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion - if(databaseVersion != gitbucketVersion){ - throw new IllegalStateException(s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}.") + if (databaseVersion != gitbucketVersion) { + throw new IllegalStateException( + s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}." + ) } // Install bundled plugins @@ -98,26 +102,26 @@ logger.info("Check version") val versionFile = new File(GitBucketHome, "version") - if(versionFile.exists()){ + if (versionFile.exists()) { val version = FileUtils.readFileToString(versionFile, "UTF-8") - if(version == "3.14"){ + if (version == "3.14") { // Initialization for GitBucket 3.14 logger.info("Migration to GitBucket 4.x start") // Backup current data val dataMvFile = new File(GitBucketHome, "data.mv.db") - if(dataMvFile.exists) { + if (dataMvFile.exists) { FileUtils.copyFile(dataMvFile, new File(GitBucketHome, "data.mv.db_3.14")) } val dataTraceFile = new File(GitBucketHome, "data.trace.db") - if(dataTraceFile.exists) { + if (dataTraceFile.exists) { FileUtils.copyFile(dataTraceFile, new File(GitBucketHome, "data.trace.db_3.14")) } // Change form manager.initialize() manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0") - conn.select("SELECT PLUGIN_ID, VERSION FROM PLUGIN"){ rs => + conn.select("SELECT PLUGIN_ID, VERSION FROM PLUGIN") { rs => manager.updateVersion(rs.getString("PLUGIN_ID"), rs.getString("VERSION")) } conn.update("DROP TABLE PLUGIN") @@ -135,8 +139,8 @@ logger.info("Extract bundled plugins") val cl = Thread.currentThread.getContextClassLoader try { - using(cl.getResourceAsStream("plugins/plugins.json")){ pluginsFile => - if(pluginsFile != null){ + using(cl.getResourceAsStream("plugins/plugins.json")) { pluginsFile => + if (pluginsFile != null) { val pluginsJson = IOUtils.toString(pluginsFile, "UTF-8") FileUtils.forceMkdir(PluginRepository.LocalRepositoryDir) @@ -144,19 +148,28 @@ val plugins = PluginRepository.parsePluginJson(pluginsJson) plugins.foreach { plugin => - plugin.versions.sortBy { x => Semver.valueOf(x.version) }.reverse.zipWithIndex.foreach { case (version, i) => - val file = new File(PluginRepository.LocalRepositoryDir, version.file) - if(!file.exists) { - logger.info(s"Copy ${plugin} to ${file.getAbsolutePath}") - FileUtils.forceMkdirParent(file) - using(cl.getResourceAsStream("plugins/" + version.file), new FileOutputStream(file)){ case (in, out) => IOUtils.copy(in, out) } - - if(plugin.default && i == 0){ - logger.info(s"Enable ${file.getName} in default") - FileUtils.copyFile(file, new File(PluginHome, version.file)) - } + plugin.versions + .sortBy { x => + Semver.valueOf(x.version) } - } + .reverse + .zipWithIndex + .foreach { + case (version, i) => + val file = new File(PluginRepository.LocalRepositoryDir, version.file) + if (!file.exists) { + logger.info(s"Copy ${plugin} to ${file.getAbsolutePath}") + FileUtils.forceMkdirParent(file) + using(cl.getResourceAsStream("plugins/" + version.file), new FileOutputStream(file)) { + case (in, out) => IOUtils.copy(in, out) + } + + if (plugin.default && i == 0) { + logger.info(s"Enable ${file.getName} in default") + FileUtils.copyFile(file, new File(PluginHome, version.file)) + } + } + } } } } @@ -183,7 +196,7 @@ def receive = { case s: String => { loadSystemSettings().activityLogLimit.foreach { limit => - if(limit > 0){ + if (limit > 0) { Database() withTransaction { implicit session => val rows = deleteOldActivities(limit) logger.info(s"Deleted ${rows} activity logs") diff --git a/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala b/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala index e2261a1..016d2ea 100644 --- a/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala @@ -16,10 +16,11 @@ val path = req.getRequestURI.substring(req.getContextPath.length) assetsMappings - .find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) } - .flatMap { case (prefix, resourcePath, classLoader) => - val resourceName = path.substring(("/plugin-assets" + prefix).length) - Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName)) + .find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) } + .flatMap { + case (prefix, resourcePath, classLoader) => + val resourceName = path.substring(("/plugin-assets" + prefix).length) + Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName)) } .map { in => try { diff --git a/src/main/scala/gitbucket/core/servlet/PluginControllerFilter.scala b/src/main/scala/gitbucket/core/servlet/PluginControllerFilter.scala index 66837e2..9137c15 100644 --- a/src/main/scala/gitbucket/core/servlet/PluginControllerFilter.scala +++ b/src/main/scala/gitbucket/core/servlet/PluginControllerFilter.scala @@ -15,8 +15,9 @@ } override def destroy(): Unit = { - PluginRegistry().getControllers().foreach { case (controller, _) => - controller.destroy() + PluginRegistry().getControllers().foreach { + case (controller, _) => + controller.destroy() } } @@ -24,22 +25,25 @@ val contextPath = request.getServletContext.getContextPath val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length) - PluginRegistry().getControllers() - .filter { case (_, path) => - val start = path.replaceFirst("/\\*$", "/") - (requestUri + "/").startsWith(start) + PluginRegistry() + .getControllers() + .filter { + case (_, path) => + val start = path.replaceFirst("/\\*$", "/") + (requestUri + "/").startsWith(start) } - .foreach { case (controller, _) => - controller match { - case x: ControllerBase if(x.config == null) => x.init(filterConfig) - case _ => () - } - val mockChain = new MockFilterChain() - controller.doFilter(request, response, mockChain) + .foreach { + case (controller, _) => + controller match { + case x: ControllerBase if (x.config == null) => x.init(filterConfig) + case _ => () + } + val mockChain = new MockFilterChain() + controller.doFilter(request, response, mockChain) - if(mockChain.continue == false){ - return () - } + if (mockChain.continue == false) { + return () + } } chain.doFilter(request, response) diff --git a/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala b/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala index d6677d1..8c4f335 100644 --- a/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala +++ b/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala @@ -23,7 +23,7 @@ def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { val servletPath = req.asInstanceOf[HttpServletRequest].getServletPath() - if(servletPath.startsWith("/assets/") || servletPath == "/git" || servletPath == "/git-lfs"){ + if (servletPath.startsWith("/assets/") || servletPath == "/git" || servletPath == "/git-lfs") { // assets and git-lfs don't need transaction chain.doFilter(req, res) } else { diff --git a/src/main/scala/gitbucket/core/ssh/GitCommand.scala b/src/main/scala/gitbucket/core/ssh/GitCommand.scala index 59a0b1f..0087915 100644 --- a/src/main/scala/gitbucket/core/ssh/GitCommand.scala +++ b/src/main/scala/gitbucket/core/ssh/GitCommand.scala @@ -20,7 +20,7 @@ object GitCommand { val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r - val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r + val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r } abstract class GitCommand extends Command with SessionAware { @@ -80,7 +80,7 @@ } override def setSession(serverSession: ServerSession) { - this.authType = PublicKeyAuthenticator.getAuthType(serverSession) + this.authType = PublicKeyAuthenticator.getAuthType(serverSession) } } @@ -91,41 +91,43 @@ protected def userName(authType: AuthType): String = { authType match { case AuthType.UserAuthType(userName) => userName - case AuthType.DeployKeyType(_) => owner + case AuthType.DeployKeyType(_) => owner } } - protected def isReadableUser(authType: AuthType, repositoryInfo: RepositoryService.RepositoryInfo) - (implicit session: Session): Boolean = { + protected def isReadableUser(authType: AuthType, repositoryInfo: RepositoryService.RepositoryInfo)( + implicit session: Session + ): Boolean = { authType match { case AuthType.UserAuthType(username) => { getAccountByUserName(username) match { case Some(account) => hasGuestRole(owner, repoName, Some(account)) - case None => false + case None => false } } case AuthType.DeployKeyType(key) => { getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).contains(key)) match { case List(_) => true - case _ => false + case _ => false } } } } - protected def isWritableUser(authType: AuthType, repositoryInfo: RepositoryService.RepositoryInfo) - (implicit session: Session): Boolean = { + protected def isWritableUser(authType: AuthType, repositoryInfo: RepositoryService.RepositoryInfo)( + implicit session: Session + ): Boolean = { authType match { case AuthType.UserAuthType(username) => { getAccountByUserName(username) match { case Some(account) => hasDeveloperRole(owner, repoName, Some(account)) - case None => false + case None => false } } case AuthType.DeployKeyType(key) => { getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).contains(key)) match { case List(x) if x.allowWrite => true - case _ => false + case _ => false } } } @@ -133,18 +135,22 @@ } - -class DefaultGitUploadPack(owner: String, repoName: String) extends DefaultGitCommand(owner, repoName) - with RepositoryService with AccountService with DeployKeyService { +class DefaultGitUploadPack(owner: String, repoName: String) + extends DefaultGitCommand(owner, repoName) + with RepositoryService + with AccountService + with DeployKeyService { override protected def runTask(authType: AuthType): Unit = { val execute = Database() withSession { implicit session => - getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).map { repositoryInfo => - !repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo) - }.getOrElse(false) + getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")) + .map { repositoryInfo => + !repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo) + } + .getOrElse(false) } - if(execute){ + if (execute) { using(Git.open(getRepositoryDir(owner, repoName))) { git => val repository = git.getRepository val upload = new UploadPack(repository) @@ -154,17 +160,22 @@ } } -class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String]) extends DefaultGitCommand(owner, repoName) - with RepositoryService with AccountService with DeployKeyService { +class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String]) + extends DefaultGitCommand(owner, repoName) + with RepositoryService + with AccountService + with DeployKeyService { override protected def runTask(authType: AuthType): Unit = { val execute = Database() withSession { implicit session => - getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).map { repositoryInfo => - isWritableUser(authType, repositoryInfo) - }.getOrElse(false) + getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")) + .map { repositoryInfo => + isWritableUser(authType, repositoryInfo) + } + .getOrElse(false) } - if(execute) { + if (execute) { using(Git.open(getRepositoryDir(owner, repoName))) { git => val repository = git.getRepository val receive = new ReceivePack(repository) @@ -179,16 +190,18 @@ } } -class PluginGitUploadPack(repoName: String, routing: GitRepositoryRouting) extends GitCommand with SystemSettingsService { +class PluginGitUploadPack(repoName: String, routing: GitRepositoryRouting) + extends GitCommand + with SystemSettingsService { override protected def runTask(authType: AuthType): Unit = { val execute = Database() withSession { implicit session => routing.filter.filter("/" + repoName, AuthType.userName(authType), loadSystemSettings(), false) } - if(execute){ + if (execute) { val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath) - using(Git.open(new File(Directory.GitBucketHome, path))){ git => + using(Git.open(new File(Directory.GitBucketHome, path))) { git => val repository = git.getRepository val upload = new UploadPack(repository) upload.upload(in, out, err) @@ -197,16 +210,18 @@ } } -class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting) extends GitCommand with SystemSettingsService { +class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting) + extends GitCommand + with SystemSettingsService { override protected def runTask(authType: AuthType): Unit = { val execute = Database() withSession { implicit session => routing.filter.filter("/" + repoName, AuthType.userName(authType), loadSystemSettings(), true) } - if(execute){ + if (execute) { val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath) - using(Git.open(new File(Directory.GitBucketHome, path))){ git => + using(Git.open(new File(Directory.GitBucketHome, path))) { git => val repository = git.getRepository val receive = new ReceivePack(repository) receive.receive(in, out, err) @@ -215,7 +230,6 @@ } } - class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends CommandFactory { private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory]) @@ -229,17 +243,23 @@ pluginCommand match { case Some(x) => x - case None => command match { - case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, routing(repoName)) - case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, routing(repoName)) - case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName) - case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl) - case _ => new UnknownCommand(command) - } + case None => + command match { + case SimpleCommandRegex("upload", repoName) if (pluginRepository(repoName)) => + new PluginGitUploadPack(repoName, routing(repoName)) + case SimpleCommandRegex("receive", repoName) if (pluginRepository(repoName)) => + new PluginGitReceivePack(repoName, routing(repoName)) + case DefaultCommandRegex("upload", owner, repoName) => new DefaultGitUploadPack(owner, repoName) + case DefaultCommandRegex("receive", owner, repoName) => + new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl) + case _ => new UnknownCommand(command) + } } } - private def pluginRepository(repoName: String): Boolean = PluginRegistry().getRepositoryRouting("/" + repoName).isDefined - private def routing(repoName: String): GitRepositoryRouting = PluginRegistry().getRepositoryRouting("/" + repoName).get + private def pluginRepository(repoName: String): Boolean = + PluginRegistry().getRepositoryRouting("/" + repoName).isDefined + private def routing(repoName: String): GitRepositoryRouting = + PluginRegistry().getRepositoryRouting("/" + repoName).get } diff --git a/src/main/scala/gitbucket/core/ssh/NoShell.scala b/src/main/scala/gitbucket/core/ssh/NoShell.scala index 065bee0..45130e4 100644 --- a/src/main/scala/gitbucket/core/ssh/NoShell.scala +++ b/src/main/scala/gitbucket/core/ssh/NoShell.scala @@ -6,7 +6,7 @@ import java.io.{OutputStream, InputStream} import org.eclipse.jgit.lib.Constants -class NoShell(sshAddress:SshAddress) extends Factory[Command] { +class NoShell(sshAddress: SshAddress) extends Factory[Command] { override def create(): Command = new Command() { private var in: InputStream = null private var out: OutputStream = null diff --git a/src/main/scala/gitbucket/core/ssh/PublicKeyAuthenticator.scala b/src/main/scala/gitbucket/core/ssh/PublicKeyAuthenticator.scala index e3be40d..2f79f4e 100644 --- a/src/main/scala/gitbucket/core/ssh/PublicKeyAuthenticator.scala +++ b/src/main/scala/gitbucket/core/ssh/PublicKeyAuthenticator.scala @@ -16,7 +16,7 @@ // put in the ServerSession here to be read by GitCommand later private val authTypeSessionKey = new AttributeStore.AttributeKey[AuthType] - def putAuthType(serverSession: ServerSession, authType: AuthType):Unit = + def putAuthType(serverSession: ServerSession, authType: AuthType): Unit = serverSession.setAttribute(authTypeSessionKey, authType) def getAuthType(serverSession: ServerSession): Option[AuthType] = @@ -34,13 +34,16 @@ def userName(authType: AuthType): Option[String] = { authType match { case UserAuthType(userName) => Some(userName) - case _ => None + case _ => None } } } } -class PublicKeyAuthenticator(genericUser: String) extends PublickeyAuthenticator with SshKeyService with DeployKeyService { +class PublicKeyAuthenticator(genericUser: String) + extends PublickeyAuthenticator + with SshKeyService + with DeployKeyService { private val logger = LoggerFactory.getLogger(classOf[PublicKeyAuthenticator]) override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = { @@ -53,7 +56,9 @@ } } - private def authenticateLoginUser(userName: String, key: PublicKey, session: ServerSession)(implicit s: Session): Boolean = { + private def authenticateLoginUser(userName: String, key: PublicKey, session: ServerSession)( + implicit s: Session + ): Boolean = { val authenticated = getPublicKeys(userName).map(_.publicKey).flatMap(SshUtil.str2PublicKey).contains(key) if (authenticated) { @@ -65,11 +70,16 @@ authenticated } - private def authenticateGenericUser(userName: String, key: PublicKey, session: ServerSession, genericUser: String)(implicit s: Session): Boolean = { + private def authenticateGenericUser(userName: String, key: PublicKey, session: ServerSession, genericUser: String)( + implicit s: Session + ): Boolean = { // find all users having the key we got from ssh - val possibleUserNames = getAllKeys().filter { sshKey => - SshUtil.str2PublicKey(sshKey.publicKey).contains(key) - }.map(_.userName).distinct + val possibleUserNames = getAllKeys() + .filter { sshKey => + SshUtil.str2PublicKey(sshKey.publicKey).contains(key) + } + .map(_.userName) + .distinct // determine the user - if different accounts share the same key, tough luck val uniqueUserName = possibleUserNames match { @@ -77,27 +87,29 @@ case _ => None } - uniqueUserName.map { userName => - // found public key for user - logger.info(s"authentication as generic user ${genericUser} succeeded, identified ${userName}") - PublicKeyAuthenticator.putAuthType(session, AuthType.UserAuthType(userName)) - true - }.getOrElse { - // search deploy keys - val existsDeployKey = getAllDeployKeys().exists { sshKey => - SshUtil.str2PublicKey(sshKey.publicKey).contains(key) - } - if(existsDeployKey){ - // found deploy key for repository - PublicKeyAuthenticator.putAuthType(session, AuthType.DeployKeyType(key)) - logger.info(s"authentication as generic user ${genericUser} succeeded, deploy key was found") + uniqueUserName + .map { userName => + // found public key for user + logger.info(s"authentication as generic user ${genericUser} succeeded, identified ${userName}") + PublicKeyAuthenticator.putAuthType(session, AuthType.UserAuthType(userName)) true - } else { - // public key not found - logger.info(s"authentication by generic user ${genericUser} failed") - false } - } + .getOrElse { + // search deploy keys + val existsDeployKey = getAllDeployKeys().exists { sshKey => + SshUtil.str2PublicKey(sshKey.publicKey).contains(key) + } + if (existsDeployKey) { + // found deploy key for repository + PublicKeyAuthenticator.putAuthType(session, AuthType.DeployKeyType(key)) + logger.info(s"authentication as generic user ${genericUser} succeeded, deploy key was found") + true + } else { + // public key not found + logger.info(s"authentication by generic user ${genericUser} failed") + false + } + } } } diff --git a/src/main/scala/gitbucket/core/ssh/SshServerListener.scala b/src/main/scala/gitbucket/core/ssh/SshServerListener.scala index 857db45..fe04b9f 100644 --- a/src/main/scala/gitbucket/core/ssh/SshServerListener.scala +++ b/src/main/scala/gitbucket/core/ssh/SshServerListener.scala @@ -22,12 +22,14 @@ provider.setOverwriteAllowed(false) server.setKeyPairProvider(provider) server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser)) - server.setCommandFactory(new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}"))) + server.setCommandFactory( + new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}")) + ) server.setShellFactory(new NoShell(sshAddress)) } def start(sshAddress: SshAddress, baseUrl: String) = { - if(active.compareAndSet(false, true)){ + if (active.compareAndSet(false, true)) { configure(sshAddress, baseUrl) server.start() logger.info(s"Start SSH Server Listen on ${server.getPort}") @@ -35,7 +37,7 @@ } def stop() = { - if(active.compareAndSet(true, false)){ + if (active.compareAndSet(true, false)) { server.stop(true) logger.info("SSH Server is stopped.") } @@ -57,13 +59,12 @@ override def contextInitialized(sce: ServletContextEvent): Unit = { val settings = loadSystemSettings() if (settings.sshAddress.isDefined && settings.baseUrl.isEmpty) { - logger.error("Could not start SshServer because the baseUrl is not configured.") + logger.error("Could not start SshServer because the baseUrl is not configured.") } for { sshAddress <- settings.sshAddress - baseUrl <- settings.baseUrl - } - SshServer.start(sshAddress, baseUrl) + baseUrl <- settings.baseUrl + } SshServer.start(sshAddress, baseUrl) } override def contextDestroyed(sce: ServletContextEvent): Unit = { diff --git a/src/main/scala/gitbucket/core/ssh/SshUtil.scala b/src/main/scala/gitbucket/core/ssh/SshUtil.scala index 9563ab3..9a7f7ed 100644 --- a/src/main/scala/gitbucket/core/ssh/SshUtil.scala +++ b/src/main/scala/gitbucket/core/ssh/SshUtil.scala @@ -8,7 +8,6 @@ import org.eclipse.jgit.lib.Constants import org.slf4j.LoggerFactory - object SshUtil { private val logger = LoggerFactory.getLogger(SshUtil.getClass) diff --git a/src/main/scala/gitbucket/core/util/Authenticator.scala b/src/main/scala/gitbucket/core/util/Authenticator.scala index 8065130..c555ec4 100644 --- a/src/main/scala/gitbucket/core/util/Authenticator.scala +++ b/src/main/scala/gitbucket/core/util/Authenticator.scala @@ -16,11 +16,11 @@ private def authenticate(action: => Any) = { { - defining(request.paths){ paths => + defining(request.paths) { paths => context.loginAccount match { - case Some(x) if(x.isAdmin) => action - case Some(x) if(paths(0) == x.userName) => action - case _ => Unauthorized() + case Some(x) if (x.isAdmin) => action + case Some(x) if (paths(0) == x.userName) => action + case _ => Unauthorized() } } } @@ -36,14 +36,18 @@ private def authenticate(action: (RepositoryInfo) => Any) = { { - defining(request.paths){ paths => + defining(request.paths) { paths => getRepository(paths(0), paths(1)).map { repository => context.loginAccount match { - case Some(x) if(x.isAdmin) => action(repository) - case Some(x) if(repository.owner == x.userName) => action(repository) + case Some(x) if (x.isAdmin) => action(repository) + case Some(x) if (repository.owner == x.userName) => action(repository) // TODO Repository management is allowed for only group managers? - case Some(x) if(getGroupMembers(repository.owner).exists { m => m.userName == x.userName && m.isManager == true }) => action(repository) - case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq(Role.ADMIN)).contains(x.userName)) => action(repository) + case Some(x) if (getGroupMembers(repository.owner).exists { m => + m.userName == x.userName && m.isManager == true + }) => + action(repository) + case Some(x) if (getCollaboratorUserNames(paths(0), paths(1), Seq(Role.ADMIN)).contains(x.userName)) => + action(repository) case _ => Unauthorized() } } getOrElse NotFound() @@ -63,7 +67,7 @@ { context.loginAccount match { case Some(x) => action - case None => Unauthorized() + case None => Unauthorized() } } } @@ -79,8 +83,8 @@ private def authenticate(action: => Any) = { { context.loginAccount match { - case Some(x) if(x.isAdmin) => action - case _ => Unauthorized() + case Some(x) if (x.isAdmin) => action + case _ => Unauthorized() } } } @@ -95,9 +99,9 @@ private def authenticate(action: (RepositoryInfo) => Any) = { { - defining(request.paths){ paths => + defining(request.paths) { paths => getRepository(paths(0), paths(1)).map { repository => - if(isReadable(repository.repository, context.loginAccount)){ + if (isReadable(repository.repository, context.loginAccount)) { action(repository) } else { Unauthorized() @@ -113,19 +117,21 @@ */ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService => protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } - protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } + protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { + authenticate(action(form, _)) + } private def authenticate(action: (RepositoryInfo) => Any) = { { - defining(request.paths){ paths => + defining(request.paths) { paths => getRepository(paths(0), paths(1)).map { repository => context.loginAccount match { - case Some(x) if(x.isAdmin) => action(repository) - case Some(x) if(!repository.repository.isPrivate) => action(repository) - case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository) - case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).contains(x.userName)) => action(repository) - case _ => Unauthorized() + case Some(x) if (x.isAdmin) => action(repository) + case Some(x) if (!repository.repository.isPrivate) => action(repository) + case Some(x) if (paths(0) == x.userName) => action(repository) + case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository) + case Some(x) if (getCollaboratorUserNames(paths(0), paths(1)).contains(x.userName)) => action(repository) + case _ => Unauthorized() } } getOrElse NotFound() } @@ -138,17 +144,22 @@ */ trait WritableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService => protected def writableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } - protected def writableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } + protected def writableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { + authenticate(action(form, _)) + } private def authenticate(action: (RepositoryInfo) => Any) = { { - defining(request.paths){ paths => + defining(request.paths) { paths => getRepository(paths(0), paths(1)).map { repository => context.loginAccount match { - case Some(x) if(x.isAdmin) => action(repository) - case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository) - case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq(Role.ADMIN, Role.DEVELOPER)).contains(x.userName)) => action(repository) + case Some(x) if (x.isAdmin) => action(repository) + case Some(x) if (paths(0) == x.userName) => action(repository) + case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository) + case Some(x) + if (getCollaboratorUserNames(paths(0), paths(1), Seq(Role.ADMIN, Role.DEVELOPER)) + .contains(x.userName)) => + action(repository) case _ => Unauthorized() } } getOrElse NotFound() @@ -166,11 +177,12 @@ private def authenticate(action: => Any) = { { - defining(request.paths){ paths => + defining(request.paths) { paths => context.loginAccount match { - case Some(x) if(getGroupMembers(paths(0)).exists { member => - member.userName == x.userName && member.isManager - }) => action + case Some(x) if (getGroupMembers(paths(0)).exists { member => + member.userName == x.userName && member.isManager + }) => + action case _ => Unauthorized() } } diff --git a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala index 1ef20dc..a2f6682 100644 --- a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala +++ b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala @@ -17,8 +17,9 @@ private lazy val config = { val file = new File(GitBucketHome, "database.conf") - if(!file.exists){ - FileUtils.write(file, + if (!file.exists) { + FileUtils.write( + file, """db { | url = "jdbc:h2:${DatabaseHome};MVCC=true" | user = "sa" @@ -29,7 +30,9 @@ |# minimumIdle = 10 |# maximumPoolSize = 10 |} - |""".stripMargin, "UTF-8") + |""".stripMargin, + "UTF-8" + ) } ConfigFactory.parseFile(file) } @@ -39,27 +42,27 @@ def url(directory: Option[String]): String = dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome)) - lazy val url : String = url(None) - lazy val user : String = getValue("db.user", config.getString) - lazy val password : String = getValue("db.password", config.getString) - lazy val jdbcDriver : String = DatabaseType(url).jdbcDriver - lazy val slickDriver : BlockingJdbcProfile = DatabaseType(url).slickDriver - lazy val liquiDriver : AbstractJdbcDatabase = DatabaseType(url).liquiDriver - lazy val connectionTimeout : Option[Long] = getOptionValue("db.connectionTimeout", config.getLong) - lazy val idleTimeout : Option[Long] = getOptionValue("db.idleTimeout" , config.getLong) - lazy val maxLifetime : Option[Long] = getOptionValue("db.maxLifetime" , config.getLong) - lazy val minimumIdle : Option[Int] = getOptionValue("db.minimumIdle" , config.getInt) - lazy val maximumPoolSize : Option[Int] = getOptionValue("db.maximumPoolSize" , config.getInt) + lazy val url: String = url(None) + lazy val user: String = getValue("db.user", config.getString) + lazy val password: String = getValue("db.password", config.getString) + lazy val jdbcDriver: String = DatabaseType(url).jdbcDriver + lazy val slickDriver: BlockingJdbcProfile = DatabaseType(url).slickDriver + lazy val liquiDriver: AbstractJdbcDatabase = DatabaseType(url).liquiDriver + lazy val connectionTimeout: Option[Long] = getOptionValue("db.connectionTimeout", config.getLong) + lazy val idleTimeout: Option[Long] = getOptionValue("db.idleTimeout", config.getLong) + lazy val maxLifetime: Option[Long] = getOptionValue("db.maxLifetime", config.getLong) + lazy val minimumIdle: Option[Int] = getOptionValue("db.minimumIdle", config.getInt) + lazy val maximumPoolSize: Option[Int] = getOptionValue("db.maximumPoolSize", config.getInt) private def getValue[T](path: String, f: String => T): T = { - getSystemProperty(path).getOrElse(getEnvironmentVariable(path).getOrElse{ + getSystemProperty(path).getOrElse(getEnvironmentVariable(path).getOrElse { f(path) }) } private def getOptionValue[T](path: String, f: String => T): Option[T] = { getSystemProperty(path).orElse(getEnvironmentVariable(path).orElse { - if(config.hasPath(path)) Some(f(path)) else None + if (config.hasPath(path)) Some(f(path)) else None }) } @@ -74,11 +77,11 @@ object DatabaseType { def apply(url: String): DatabaseType = { - if(url.startsWith("jdbc:h2:")){ + if (url.startsWith("jdbc:h2:")) { H2 - } else if(url.startsWith("jdbc:mysql:")){ + } else if (url.startsWith("jdbc:mysql:")) { MySQL - } else if(url.startsWith("jdbc:postgresql:")){ + } else if (url.startsWith("jdbc:postgresql:")) { PostgreSQL } else { throw new IllegalArgumentException(s"${url} is not supported.") @@ -106,7 +109,7 @@ object BlockingPostgresDriver extends slick.jdbc.PostgresProfile with BlockingJdbcProfile { override def quoteIdentifier(id: String): String = { val s = new StringBuilder(id.length + 4) append '"' - for(c <- id) if(c == '"') s append "\"\"" else s append c.toLower + for (c <- id) if (c == '"') s append "\"\"" else s append c.toLower (s append '"').toString } } @@ -116,7 +119,7 @@ def getEnvironmentVariable[A](key: String): Option[A] = { val value = System.getenv("GITBUCKET_" + key.toUpperCase.replace('.', '_')) - if(value != null && value.nonEmpty){ + if (value != null && value.nonEmpty) { Some(convertType(value)).asInstanceOf[Option[A]] } else { None @@ -125,7 +128,7 @@ def getSystemProperty[A](key: String): Option[A] = { val value = System.getProperty("gitbucket." + key) - if(value != null && value.nonEmpty){ + if (value != null && value.nonEmpty) { Some(convertType(value)).asInstanceOf[Option[A]] } else { None @@ -133,10 +136,10 @@ } def convertType[A: ClassTag](value: String) = - defining(implicitly[ClassTag[A]].runtimeClass){ c => - if(c == classOf[Boolean]) value.toBoolean - else if(c == classOf[Long]) value.toLong - else if(c == classOf[Int]) value.toInt + defining(implicitly[ClassTag[A]].runtimeClass) { c => + if (c == classOf[Boolean]) value.toBoolean + else if (c == classOf[Long]) value.toLong + else if (c == classOf[Int]) value.toInt else value } diff --git a/src/main/scala/gitbucket/core/util/Directory.scala b/src/main/scala/gitbucket/core/util/Directory.scala index b30b455..6e8a0b8 100644 --- a/src/main/scala/gitbucket/core/util/Directory.scala +++ b/src/main/scala/gitbucket/core/util/Directory.scala @@ -9,21 +9,22 @@ val GitBucketHome = (System.getProperty("gitbucket.home") match { // -Dgitbucket.home= - case path if(path != null) => new File(path) - case _ => scala.util.Properties.envOrNone("GITBUCKET_HOME") match { - // environment variable GITBUCKET_HOME - case Some(env) => new File(env) - // default is HOME/.gitbucket - case None => { - val oldHome = new File(System.getProperty("user.home"), "gitbucket") - if(oldHome.exists && oldHome.isDirectory && new File(oldHome, "version").exists){ - //FileUtils.moveDirectory(oldHome, newHome) - oldHome - } else { - new File(System.getProperty("user.home"), ".gitbucket") + case path if (path != null) => new File(path) + case _ => + scala.util.Properties.envOrNone("GITBUCKET_HOME") match { + // environment variable GITBUCKET_HOME + case Some(env) => new File(env) + // default is HOME/.gitbucket + case None => { + val oldHome = new File(System.getProperty("user.home"), "gitbucket") + if (oldHome.exists && oldHome.isDirectory && new File(oldHome, "version").exists) { + //FileUtils.moveDirectory(oldHome, newHome) + oldHome + } else { + new File(System.getProperty("user.home"), ".gitbucket") + } } } - } }).getAbsolutePath val GitBucketConf = new File(GitBucketHome, "gitbucket.conf") @@ -55,8 +56,8 @@ new File(getRepositoryFilesDir(owner, repository), "comments") /** - * Directory for released files - */ + * Directory for released files + */ def getReleaseFilesDir(owner: String, repository: String): File = new File(getRepositoryFilesDir(owner, repository), "releases") diff --git a/src/main/scala/gitbucket/core/util/FileUtil.scala b/src/main/scala/gitbucket/core/util/FileUtil.scala index 070beef..d399f77 100644 --- a/src/main/scala/gitbucket/core/util/FileUtil.scala +++ b/src/main/scala/gitbucket/core/util/FileUtil.scala @@ -9,7 +9,7 @@ object FileUtil { def getMimeType(name: String): String = - defining(new Tika()){ tika => + defining(new Tika()) { tika => tika.detect(name) match { case null => "application/octet-stream" case mimeType => mimeType @@ -17,8 +17,8 @@ } def getContentType(name: String, bytes: Array[Byte]): String = { - defining(getMimeType(name)){ mimeType => - if(mimeType == "application/octet-stream" && isText(bytes)){ + defining(getMimeType(name)) { mimeType => + if (mimeType == "application/octet-stream" && isText(bytes)) { "text/plain" } else { mimeType @@ -36,12 +36,12 @@ def getExtension(name: String): String = name.lastIndexOf('.') match { - case i if(i >= 0) => name.substring(i + 1) - case _ => "" + case i if (i >= 0) => name.substring(i + 1) + case _ => "" } def withTmpDir[A](dir: File)(action: File => A): A = { - if(dir.exists()){ + if (dir.exists()) { FileUtils.deleteDirectory(dir) } try { @@ -61,7 +61,7 @@ * Do nothing if the given File is not a directory or not empty. */ def deleteDirectoryIfEmpty(dir: File): Unit = { - if(dir.isDirectory() && dir.list().isEmpty) { + if (dir.isDirectory() && dir.list().isEmpty) { FileUtils.deleteDirectory(dir) } } @@ -70,15 +70,16 @@ * Delete file or directory forcibly. */ def deleteIfExists(file: java.io.File): java.io.File = { - if(file.exists){ + if (file.exists) { FileUtils.forceDelete(file) } file } - lazy val MaxFileSize = if (System.getProperty("gitbucket.maxFileSize") != null) - System.getProperty("gitbucket.maxFileSize").toLong - else - 3 * 1024 * 1024 + lazy val MaxFileSize = + if (System.getProperty("gitbucket.maxFileSize") != null) + System.getProperty("gitbucket.maxFileSize").toLong + else + 3 * 1024 * 1024 } diff --git a/src/main/scala/gitbucket/core/util/Implicits.scala b/src/main/scala/gitbucket/core/util/Implicits.scala index 393dbbf..de701ca 100644 --- a/src/main/scala/gitbucket/core/util/Implicits.scala +++ b/src/main/scala/gitbucket/core/util/Implicits.scala @@ -13,7 +13,6 @@ import slick.jdbc.JdbcBackend - /** * Provides some usable implicit conversions. */ @@ -23,7 +22,9 @@ implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request) implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context = - JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x => s"${x.genericUser}@${x.host}:${x.port}" }) + JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x => + s"${x.genericUser}@${x.host}:${x.port}" + }) implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal { @@ -53,7 +54,7 @@ case None => sb.append(m.matched) } } - if(i < value.length){ + if (i < value.length) { sb.append(value.substring(i)) } sb.toString @@ -66,24 +67,26 @@ implicit class RichRequest(private val request: HttpServletRequest) extends AnyVal { - def paths: Array[String] = (request.getRequestURI.substring(request.getContextPath.length + 1) match{ - case path if path.startsWith("api/v3/repos/") => path.substring(13/* "/api/v3/repos".length */) - case path if path.startsWith("api/v3/orgs/") => path.substring(12/* "/api/v3/orgs".length */) - case path => path - }).split("/") + def paths: Array[String] = + (request.getRequestURI.substring(request.getContextPath.length + 1) match { + case path if path.startsWith("api/v3/repos/") => path.substring(13 /* "/api/v3/repos".length */ ) + case path if path.startsWith("api/v3/orgs/") => path.substring(12 /* "/api/v3/orgs".length */ ) + case path => path + }).split("/") def hasQueryString: Boolean = request.getQueryString != null def hasAttribute(name: String): Boolean = request.getAttribute(name) != null - def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^" + quote(request.getContextPath) + "/git/", "/") + def gitRepositoryPath: String = + request.getRequestURI.replaceFirst("^" + quote(request.getContextPath) + "/git/", "/") } implicit class RichSession(private val session: HttpSession) extends AnyVal { def getAndRemove[T](key: String): Option[T] = { val value = session.getAttribute(key).asInstanceOf[T] - if(value == null){ + if (value == null) { session.removeAttribute(key) } Option(value) diff --git a/src/main/scala/gitbucket/core/util/JDBCUtil.scala b/src/main/scala/gitbucket/core/util/JDBCUtil.scala index 8a8574c..bb3b160 100644 --- a/src/main/scala/gitbucket/core/util/JDBCUtil.scala +++ b/src/main/scala/gitbucket/core/util/JDBCUtil.scala @@ -19,24 +19,24 @@ implicit class RichConnection(private val conn: Connection) extends AnyVal { def update(sql: String, params: Any*): Int = { - execute(sql, params: _*){ stmt => + execute(sql, params: _*) { stmt => stmt.executeUpdate() } } def find[T](sql: String, params: Any*)(f: ResultSet => T): Option[T] = { - execute(sql, params: _*){ stmt => - using(stmt.executeQuery()){ rs => - if(rs.next) Some(f(rs)) else None + execute(sql, params: _*) { stmt => + using(stmt.executeQuery()) { rs => + if (rs.next) Some(f(rs)) else None } } } def select[T](sql: String, params: Any*)(f: ResultSet => T): Seq[T] = { - execute(sql, params: _*){ stmt => - using(stmt.executeQuery()){ rs => + execute(sql, params: _*) { stmt => + using(stmt.executeQuery()) { rs => val list = new ListBuffer[T] - while(rs.next){ + while (rs.next) { list += f(rs) } list.toSeq @@ -45,20 +45,21 @@ } def selectInt(sql: String, params: Any*): Int = { - execute(sql, params: _*){ stmt => - using(stmt.executeQuery()){ rs => - if(rs.next) rs.getInt(1) else 0 + execute(sql, params: _*) { stmt => + using(stmt.executeQuery()) { rs => + if (rs.next) rs.getInt(1) else 0 } } } private def execute[T](sql: String, params: Any*)(f: (PreparedStatement) => T): T = { - using(conn.prepareStatement(sql)){ stmt => - params.zipWithIndex.foreach { case (p, i) => - p match { - case x: Int => stmt.setInt(i + 1, x) - case x: String => stmt.setString(i + 1, x) - } + using(conn.prepareStatement(sql)) { stmt => + params.zipWithIndex.foreach { + case (p, i) => + p match { + case x: Int => stmt.setInt(i + 1, x) + case x: String => stmt.setString(i + 1, x) + } } f(stmt) } @@ -67,20 +68,20 @@ def importAsSQL(in: InputStream): Unit = { conn.setAutoCommit(false) try { - using(in){ in => + using(in) { in => var out = new ByteArrayOutputStream() var length = 0 val bytes = new scala.Array[Byte](1024 * 8) var stringLiteral = false - while({ length = in.read(bytes); length != -1 }){ - for(i <- 0 until length){ + while ({ length = in.read(bytes); length != -1 }) { + for (i <- 0 until length) { val c = bytes(i) - if(c == '\''){ + if (c == '\'') { stringLiteral = !stringLiteral } - if(c == ';' && !stringLiteral){ + if (c == ';' && !stringLiteral) { val sql = new String(out.toByteArray, "UTF-8") conn.update(sql.trim) out = new ByteArrayOutputStream() @@ -91,7 +92,7 @@ } val remain = out.toByteArray - if(remain.length != 0){ + if (remain.length != 0) { val sql = new String(remain, "UTF-8") conn.update(sql.trim) } @@ -133,20 +134,21 @@ sb.append(columns.map(_._1).mkString(", ")) sb.append(") VALUES (") - val values = columns.map { case (columnName, columnType) => - if(rs.getObject(columnName) == null){ - null - } else { - columnType match { - case Types.BOOLEAN | Types.BIT => rs.getBoolean(columnName) - case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => rs.getString(columnName) - case Types.INTEGER => rs.getInt(columnName) - case Types.TIMESTAMP => rs.getTimestamp(columnName) + val values = columns.map { + case (columnName, columnType) => + if (rs.getObject(columnName) == null) { + null + } else { + columnType match { + case Types.BOOLEAN | Types.BIT => rs.getBoolean(columnName) + case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => rs.getString(columnName) + case Types.INTEGER => rs.getInt(columnName) + case Types.TIMESTAMP => rs.getTimestamp(columnName) + } } - } } - val columnValues = values.map { + val columnValues = values.map { case x: String => "'" + x.replace("'", "''") + "'" case x: Timestamp => "'" + dateFormat.format(x) + "'" case null => "NULL" @@ -179,7 +181,7 @@ private def childTables(meta: DatabaseMetaData, tableName: String): Seq[String] = { val normalizedTableName = - if(meta.getDatabaseProductName == "PostgreSQL"){ + if (meta.getDatabaseProductName == "PostgreSQL") { tableName.toLowerCase } else { tableName @@ -189,7 +191,7 @@ val children = new ListBuffer[String] while (rs.next) { val childTableName = rs.getString("FKTABLE_NAME").toUpperCase - if(!children.contains(childTableName)){ + if (!children.contains(childTableName)) { children += childTableName children ++= childTables(meta, childTableName) } @@ -198,7 +200,6 @@ } } - private def allTablesOrderByDependencies(meta: DatabaseMetaData): Seq[String] = { val tables = allTableNames.map { tableName => val result = TableDependency(tableName, childTables(meta, tableName)) @@ -206,13 +207,14 @@ } val edges = tables.flatMap { table => - table.children.map { child => (table.tableName, child) } + table.children.map { child => + (table.tableName, child) + } } tsort(edges).toSeq } - def tsort[A](edges: Traversable[(A, A)]): Iterable[A] = { @tailrec def tsort(toPreds: Map[A, Set[A]], done: Iterable[A]): Iterable[A] = { diff --git a/src/main/scala/gitbucket/core/util/JGitUtil.scala b/src/main/scala/gitbucket/core/util/JGitUtil.scala index 3b54730..bb33431 100644 --- a/src/main/scala/gitbucket/core/util/JGitUtil.scala +++ b/src/main/scala/gitbucket/core/util/JGitUtil.scala @@ -23,7 +23,12 @@ import java.util.function.Consumer import org.cache2k.Cache2kBuilder -import org.eclipse.jgit.api.errors.{InvalidRefNameException, JGitInternalException, NoHeadException, RefAlreadyExistsException} +import org.eclipse.jgit.api.errors.{ + InvalidRefNameException, + JGitInternalException, + NoHeadException, + RefAlreadyExistsException +} import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator} import org.eclipse.jgit.dircache.DirCacheEntry import org.eclipse.jgit.util.io.DisabledOutputStream @@ -44,7 +49,7 @@ * @param branchList the list of branch names * @param tags the list of tags */ - case class RepositoryInfo(owner: String, name: String, branchList: List[String], tags: List[TagInfo]){ + case class RepositoryInfo(owner: String, name: String, branchList: List[String], tags: List[TagInfo]) { def this(owner: String, name: String) = this(owner, name, Nil, Nil) } @@ -62,8 +67,18 @@ * @param mailAddress the committer's mail address * @param linkUrl the url of submodule */ - case class FileInfo(id: ObjectId, isDirectory: Boolean, name: String, path: String, message: String, commitId: String, - time: Date, author: String, mailAddress: String, linkUrl: Option[String]) + case class FileInfo( + id: ObjectId, + isDirectory: Boolean, + name: String, + path: String, + message: String, + commitId: String, + time: Date, + author: String, + mailAddress: String, + linkUrl: Option[String] + ) /** * The commit data. @@ -79,11 +94,21 @@ * @param committerName the committer name * @param committerEmailAddress the mail address of the committer */ - case class CommitInfo(id: String, shortMessage: String, fullMessage: String, parents: List[String], - authorTime: Date, authorName: String, authorEmailAddress: String, - commitTime: Date, committerName: String, committerEmailAddress: String){ + case class CommitInfo( + id: String, + shortMessage: String, + fullMessage: String, + parents: List[String], + authorTime: Date, + authorName: String, + authorEmailAddress: String, + commitTime: Date, + committerName: String, + committerEmailAddress: String + ) { - def this(rev: org.eclipse.jgit.revwalk.RevCommit) = this( + def this(rev: org.eclipse.jgit.revwalk.RevCommit) = + this( rev.getName, rev.getShortMessage, rev.getFullMessage, @@ -93,12 +118,13 @@ rev.getAuthorIdent.getEmailAddress, rev.getCommitterIdent.getWhen, rev.getCommitterIdent.getName, - rev.getCommitterIdent.getEmailAddress) + rev.getCommitterIdent.getEmailAddress + ) val summary = getSummaryMessage(fullMessage, shortMessage) - val description = defining(fullMessage.trim.indexOf('\n')){ i => - if(i >= 0){ + val description = defining(fullMessage.trim.indexOf('\n')) { i => + if (i >= 0) { Some(fullMessage.trim.substring(i).trim) } else None } @@ -130,11 +156,12 @@ * @param content the string content * @param charset the character encoding */ - case class ContentInfo(viewType: String, size: Option[Long], content: Option[String], charset: Option[String]){ + case class ContentInfo(viewType: String, size: Option[Long], content: Option[String], charset: Option[String]) { + /** * the line separator of this content ("LF" or "CRLF") */ - val lineSeparator: String = if(content.exists(_.indexOf("\r\n") >= 0)) "CRLF" else "LF" + val lineSeparator: String = if (content.exists(_.indexOf("\r\n") >= 0)) "CRLF" else "LF" } /** @@ -159,10 +186,26 @@ case class BranchMergeInfo(ahead: Int, behind: Int, isMerged: Boolean) - case class BranchInfo(name: String, committerName: String, commitTime: Date, committerEmailAddress:String, mergeInfo: Option[BranchMergeInfo], commitId: String) + case class BranchInfo( + name: String, + committerName: String, + commitTime: Date, + committerEmailAddress: String, + mergeInfo: Option[BranchMergeInfo], + commitId: String + ) - case class BlameInfo(id: String, authorName: String, authorEmailAddress: String, authorTime:java.util.Date, - prev: Option[String], prevPath: Option[String], commitTime:java.util.Date, message:String, lines:Set[Int]) + case class BlameInfo( + id: String, + authorName: String, + authorEmailAddress: String, + authorTime: java.util.Date, + prev: Option[String], + prevPath: Option[String], + commitTime: java.util.Date, + message: String, + lines: Set[Int] + ) /** * Returns RevCommit from the commit or tag id. @@ -207,7 +250,7 @@ val key = dir.getAbsolutePath + "@" + branch val entry = cache.getEntry(key) - if(entry == null) { + if (entry == null) { using(Git.open(dir)) { git => val commitId = git.getRepository.resolve(branch) val commitCount = git.log.add(commitId).call.iterator.asScala.take(10001).size @@ -223,23 +266,35 @@ * Returns the repository information. It contains branch names and tag names. */ def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = { - using(Git.open(getRepositoryDir(owner, repository))){ git => + using(Git.open(getRepositoryDir(owner, repository))) { git => try { - RepositoryInfo(owner, repository, + RepositoryInfo( + owner, + repository, // branches git.branchList.call.asScala.map { ref => ref.getName.stripPrefix("refs/heads/") }.toList, // tags - git.tagList.call.asScala.flatMap { ref => - try { - val revCommit = getRevCommitFromId(git, ref.getObjectId) - Some(TagInfo(ref.getName.stripPrefix("refs/tags/"), revCommit.getCommitterIdent.getWhen, revCommit.getName, revCommit.getShortMessage)) - } catch { - case _: IncorrectObjectTypeException => - None + git.tagList.call.asScala + .flatMap { ref => + try { + val revCommit = getRevCommitFromId(git, ref.getObjectId) + Some( + TagInfo( + ref.getName.stripPrefix("refs/tags/"), + revCommit.getCommitterIdent.getWhen, + revCommit.getName, + revCommit.getShortMessage + ) + ) + } catch { + case _: IncorrectObjectTypeException => + None + } } - }.sortBy(_.time).toList + .sortBy(_.time) + .toList ) } catch { // not initialized @@ -258,81 +313,101 @@ * @return HTML of the file list */ def getFileList(git: Git, revision: String, path: String = ".", baseUrl: Option[String] = None): List[FileInfo] = { - using(new RevWalk(git.getRepository)){ revWalk => + using(new RevWalk(git.getRepository)) { revWalk => val objectId = git.getRepository.resolve(revision) - if(objectId == null) return Nil + if (objectId == null) return Nil val revCommit = revWalk.parseCommit(objectId) - def useTreeWalk(rev:RevCommit)(f:TreeWalk => Any): Unit = if (path == ".") { - val treeWalk = new TreeWalk(git.getRepository) - treeWalk.addTree(rev.getTree) - using(treeWalk)(f) - } else { - val treeWalk = TreeWalk.forPath(git.getRepository, path, rev.getTree) - if(treeWalk != null){ - treeWalk.enterSubtree + def useTreeWalk(rev: RevCommit)(f: TreeWalk => Any): Unit = + if (path == ".") { + val treeWalk = new TreeWalk(git.getRepository) + treeWalk.addTree(rev.getTree) using(treeWalk)(f) + } else { + val treeWalk = TreeWalk.forPath(git.getRepository, path, rev.getTree) + if (treeWalk != null) { + treeWalk.enterSubtree + using(treeWalk)(f) + } } - } @tailrec - def simplifyPath(tuple: (ObjectId, FileMode, String, String, Option[String], RevCommit)): (ObjectId, FileMode, String, String, Option[String], RevCommit) = tuple match { - case (oid, FileMode.TREE, name, path, _, commit ) => + def simplifyPath( + tuple: (ObjectId, FileMode, String, String, Option[String], RevCommit) + ): (ObjectId, FileMode, String, String, Option[String], RevCommit) = tuple match { + case (oid, FileMode.TREE, name, path, _, commit) => (using(new TreeWalk(git.getRepository)) { walk => walk.addTree(oid) // single tree child, or None - if(walk.next() && walk.getFileMode(0) == FileMode.TREE){ - Some((walk.getObjectId(0), walk.getFileMode(0), name + "/" + walk.getNameString, path + "/" + walk.getNameString, None, commit)).filterNot(_ => walk.next()) + if (walk.next() && walk.getFileMode(0) == FileMode.TREE) { + Some( + ( + walk.getObjectId(0), + walk.getFileMode(0), + name + "/" + walk.getNameString, + path + "/" + walk.getNameString, + None, + commit + ) + ).filterNot(_ => walk.next()) } else { None } }) match { case Some(child) => simplifyPath(child) - case _ => tuple + case _ => tuple } case _ => tuple } - def tupleAdd(tuple:(ObjectId, FileMode, String, String, Option[String]), rev:RevCommit) = tuple match { + def tupleAdd(tuple: (ObjectId, FileMode, String, String, Option[String]), rev: RevCommit) = tuple match { case (oid, fmode, name, path, opt) => (oid, fmode, name, path, opt, rev) } @tailrec - def findLastCommits(result:List[(ObjectId, FileMode, String, String, Option[String], RevCommit)], - restList:List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])], - revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] = { - if(restList.isEmpty){ + def findLastCommits( + result: List[(ObjectId, FileMode, String, String, Option[String], RevCommit)], + restList: List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])], + revIterator: java.util.Iterator[RevCommit] + ): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] = { + if (restList.isEmpty) { result - } else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty + } else if (!revIterator.hasNext) { // maybe, revCommit has only 1 log. other case, restList be empty result ++ restList.map { case (tuple, map) => tupleAdd(tuple, map.values.headOption.getOrElse(revCommit)) } } else { val newCommit = revIterator.next - val (thisTimeChecks,skips) = restList.partition { case (tuple, parentsMap) => parentsMap.contains(newCommit) } - if(thisTimeChecks.isEmpty){ + val (thisTimeChecks, skips) = restList.partition { + case (tuple, parentsMap) => parentsMap.contains(newCommit) + } + if (thisTimeChecks.isEmpty) { findLastCommits(result, restList, revIterator) } else { var nextRest = skips var nextResult = result // Map[(name, oid), (tuple, parentsMap)] - val rest = scala.collection.mutable.Map(thisTimeChecks.map{ t => (t._1._3 -> t._1._1) -> t }:_*) + val rest = scala.collection.mutable.Map(thisTimeChecks.map { t => + (t._1._3 -> t._1._1) -> t + }: _*) lazy val newParentsMap = newCommit.getParents.map(_ -> newCommit).toMap - useTreeWalk(newCommit){ walk => - while(walk.next){ - rest.remove(walk.getNameString -> walk.getObjectId(0)).map { case (tuple, _) => - if(newParentsMap.isEmpty){ - nextResult +:= tupleAdd(tuple, newCommit) - } else { - nextRest +:= tuple -> newParentsMap - } + useTreeWalk(newCommit) { walk => + while (walk.next) { + rest.remove(walk.getNameString -> walk.getObjectId(0)).map { + case (tuple, _) => + if (newParentsMap.isEmpty) { + nextResult +:= tupleAdd(tuple, newCommit) + } else { + nextRest +:= tuple -> newParentsMap + } } } } - rest.values.map { case (tuple, parentsMap) => - val restParentsMap = parentsMap - newCommit - if(restParentsMap.isEmpty){ - nextResult +:= tupleAdd(tuple, parentsMap(newCommit)) - } else { - nextRest +:= tuple -> restParentsMap - } + rest.values.map { + case (tuple, parentsMap) => + val restParentsMap = parentsMap - newCommit + if (restParentsMap.isEmpty) { + nextResult +:= tupleAdd(tuple, parentsMap(newCommit)) + } else { + nextRest +:= tuple -> restParentsMap + } } findLastCommits(nextResult, nextRest, revIterator) } @@ -340,7 +415,7 @@ } var fileList: List[(ObjectId, FileMode, String, String, Option[String])] = Nil - useTreeWalk(revCommit){ treeWalk => + useTreeWalk(revCommit) { treeWalk => while (treeWalk.next()) { val linkUrl = if (treeWalk.getFileMode(0) == FileMode.GITLINK) { getSubmodules(git, revCommit.getTree, baseUrl).find(_.path == treeWalk.getPathString).map(_.viewerUrl) @@ -354,23 +429,26 @@ val nextParentsMap = Option(lastCommit).map(_.getParents.map(_ -> lastCommit).toMap).getOrElse(Map()) findLastCommits(List.empty, fileList.map(a => a -> nextParentsMap), it) .map(simplifyPath) - .map { case (objectId, fileMode, name, path, linkUrl, commit) => - FileInfo( - objectId, - fileMode == FileMode.TREE || fileMode == FileMode.GITLINK, - name, - path, - getSummaryMessage(commit.getFullMessage, commit.getShortMessage), - commit.getName, - commit.getAuthorIdent.getWhen, - commit.getAuthorIdent.getName, - commit.getAuthorIdent.getEmailAddress, - linkUrl) - }.sortWith { (file1, file2) => + .map { + case (objectId, fileMode, name, path, linkUrl, commit) => + FileInfo( + objectId, + fileMode == FileMode.TREE || fileMode == FileMode.GITLINK, + name, + path, + getSummaryMessage(commit.getFullMessage, commit.getShortMessage), + commit.getName, + commit.getAuthorIdent.getWhen, + commit.getAuthorIdent.getName, + commit.getAuthorIdent.getEmailAddress, + linkUrl + ) + } + .sortWith { (file1, file2) => (file1.isDirectory, file2.isDirectory) match { - case (true , false) => true - case (false, true ) => false - case _ => file1.name.compareTo(file2.name) < 0 + case (true, false) => true + case (false, true) => false + case _ => file1.name.compareTo(file2.name) < 0 } } } @@ -380,9 +458,9 @@ * Returns the first line of the commit message. */ private def getSummaryMessage(fullMessage: String, shortMessage: String): String = { - defining(fullMessage.trim.indexOf('\n')){ i => - defining(if(i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage){ firstLine => - if(firstLine.length > shortMessage.length) shortMessage else firstLine + defining(fullMessage.trim.indexOf('\n')) { i => + defining(if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage) { firstLine => + if (firstLine.length > shortMessage.length) shortMessage else firstLine } } } @@ -391,9 +469,9 @@ * get all file list by revision. only file. */ def getTreeId(git: Git, revision: String): Option[String] = { - using(new RevWalk(git.getRepository)){ revWalk => - val objectId = git.getRepository.resolve(revision) - if(objectId == null) return None + using(new RevWalk(git.getRepository)) { revWalk => + val objectId = git.getRepository.resolve(revision) + if (objectId == null) return None val revCommit = revWalk.parseCommit(objectId) Some(revCommit.getTree.name) } @@ -403,14 +481,14 @@ * get all file list by tree object id. */ def getAllFileListByTreeId(git: Git, treeId: String): List[String] = { - using(new RevWalk(git.getRepository)){ revWalk => - val objectId = git.getRepository.resolve(treeId+"^{tree}") - if(objectId == null) return Nil - using(new TreeWalk(git.getRepository)){ treeWalk => + using(new RevWalk(git.getRepository)) { revWalk => + val objectId = git.getRepository.resolve(treeId + "^{tree}") + if (objectId == null) return Nil + using(new TreeWalk(git.getRepository)) { treeWalk => treeWalk.addTree(objectId) treeWalk.setRecursive(true) var ret: List[String] = Nil - if(treeWalk != null){ + if (treeWalk != null) { while (treeWalk.next()) { ret +:= treeWalk.getPathString } @@ -430,26 +508,40 @@ * @param path filters by this path. default is no filter. * @return a tuple of the commit list and whether has next, or the error message */ - def getCommitLog(git: Git, revision: String, page: Int = 1, limit: Int = 0, path: String = ""): Either[String, (List[CommitInfo], Boolean)] = { - val fixedPage = if(page <= 0) 1 else page + def getCommitLog( + git: Git, + revision: String, + page: Int = 1, + limit: Int = 0, + path: String = "" + ): Either[String, (List[CommitInfo], Boolean)] = { + val fixedPage = if (page <= 0) 1 else page @scala.annotation.tailrec - def getCommitLog(i: java.util.Iterator[RevCommit], count: Int, logs: List[CommitInfo]): (List[CommitInfo], Boolean) = + def getCommitLog( + i: java.util.Iterator[RevCommit], + count: Int, + logs: List[CommitInfo] + ): (List[CommitInfo], Boolean) = i.hasNext match { - case true if(limit <= 0 || logs.size < limit) => { + case true if (limit <= 0 || logs.size < limit) => { val commit = i.next - getCommitLog(i, count + 1, if(limit <= 0 || (fixedPage - 1) * limit <= count) logs :+ new CommitInfo(commit) else logs) + getCommitLog( + i, + count + 1, + if (limit <= 0 || (fixedPage - 1) * limit <= count) logs :+ new CommitInfo(commit) else logs + ) } case _ => (logs, i.hasNext) } - using(new RevWalk(git.getRepository)){ revWalk => - defining(git.getRepository.resolve(revision)){ objectId => - if(objectId == null){ + using(new RevWalk(git.getRepository)) { revWalk => + defining(git.getRepository.resolve(revision)) { objectId => + if (objectId == null) { Left(s"${revision} can't be resolved.") } else { revWalk.markStart(revWalk.parseCommit(objectId)) - if(path.nonEmpty){ + if (path.nonEmpty) { revWalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF)) } Right(getCommitLog(revWalk.iterator, 0, Nil)) @@ -458,15 +550,16 @@ } } - def getCommitLogs(git: Git, begin: String, includesLastCommit: Boolean = false) - (endCondition: RevCommit => Boolean): List[CommitInfo] = { + def getCommitLogs(git: Git, begin: String, includesLastCommit: Boolean = false)( + endCondition: RevCommit => Boolean + ): List[CommitInfo] = { @scala.annotation.tailrec def getCommitLog(i: java.util.Iterator[RevCommit], logs: List[CommitInfo]): List[CommitInfo] = i.hasNext match { - case true => { + case true => { val revCommit = i.next - if(endCondition(revCommit)){ - if(includesLastCommit) logs :+ new CommitInfo(revCommit) else logs + if (endCondition(revCommit)) { + if (includesLastCommit) logs :+ new CommitInfo(revCommit) else logs } else { getCommitLog(i, logs :+ new CommitInfo(revCommit)) } @@ -474,13 +567,12 @@ case false => logs } - using(new RevWalk(git.getRepository)){ revWalk => + using(new RevWalk(git.getRepository)) { revWalk => revWalk.markStart(revWalk.parseCommit(git.getRepository.resolve(begin))) getCommitLog(revWalk.iterator, Nil).reverse } } - /** * Returns the commit list between two revisions. * @@ -531,7 +623,7 @@ } private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = { - using(new RevWalk(git.getRepository)){ revWalk => + using(new RevWalk(git.getRepository)) { revWalk => val df = new DiffFormatter(DisabledOutputStream.INSTANCE) df.setRepository(git.getRepository) @@ -539,7 +631,12 @@ from match { case None => { toCommit.getParentCount match { - case 0 => df.scan(new EmptyTreeIterator(), new CanonicalTreeParser(null, git.getRepository.newObjectReader(), toCommit.getTree)).asScala + case 0 => + df.scan( + new EmptyTreeIterator(), + new CanonicalTreeParser(null, git.getRepository.newObjectReader(), toCommit.getTree) + ) + .asScala case _ => df.scan(toCommit.getParent(0), toCommit.getTree).asScala } } @@ -552,7 +649,7 @@ } def getParentCommitId(git: Git, id: String): Option[String] = { - using(new RevWalk(git.getRepository)){ revWalk => + using(new RevWalk(git.getRepository)) { revWalk => val commit = revWalk.parseCommit(git.getRepository.resolve(id)) commit.getParentCount match { case 0 => None @@ -561,59 +658,71 @@ } } - def getDiffs(git: Git, from: Option[String], to: String, fetchContent: Boolean, makePatch: Boolean): List[DiffInfo] = { + def getDiffs( + git: Git, + from: Option[String], + to: String, + fetchContent: Boolean, + makePatch: Boolean + ): List[DiffInfo] = { val diffs = getDiffEntries(git, from, to) diffs.map { diff => - if(diffs.size > 100){ + if (diffs.size > 100) { DiffInfo( - changeType = diff.getChangeType, - oldPath = diff.getOldPath, - newPath = diff.getNewPath, - oldContent = None, - newContent = None, - oldIsImage = false, - newIsImage = false, + changeType = diff.getChangeType, + oldPath = diff.getOldPath, + newPath = diff.getNewPath, + oldContent = None, + newContent = None, + oldIsImage = false, + newIsImage = false, oldObjectId = Option(diff.getOldId).map(_.name), newObjectId = Option(diff.getNewId).map(_.name), - oldMode = diff.getOldMode.toString, - newMode = diff.getNewMode.toString, - tooLarge = true, - patch = None + oldMode = diff.getOldMode.toString, + newMode = diff.getNewMode.toString, + tooLarge = true, + patch = None ) } else { val oldIsImage = FileUtil.isImage(diff.getOldPath) val newIsImage = FileUtil.isImage(diff.getNewPath) - if(!fetchContent || oldIsImage || newIsImage){ + if (!fetchContent || oldIsImage || newIsImage) { DiffInfo( - changeType = diff.getChangeType, - oldPath = diff.getOldPath, - newPath = diff.getNewPath, - oldContent = None, - newContent = None, - oldIsImage = oldIsImage, - newIsImage = newIsImage, + changeType = diff.getChangeType, + oldPath = diff.getOldPath, + newPath = diff.getNewPath, + oldContent = None, + newContent = None, + oldIsImage = oldIsImage, + newIsImage = newIsImage, oldObjectId = Option(diff.getOldId).map(_.name), newObjectId = Option(diff.getNewId).map(_.name), - oldMode = diff.getOldMode.toString, - newMode = diff.getNewMode.toString, - tooLarge = false, - patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None) // TODO use DiffFormatter + oldMode = diff.getOldMode.toString, + newMode = diff.getNewMode.toString, + tooLarge = false, + patch = (if (makePatch) Some(makePatchFromDiffEntry(git, diff)) else None) // TODO use DiffFormatter ) } else { DiffInfo( - changeType = diff.getChangeType, - oldPath = diff.getOldPath, - newPath = diff.getNewPath, - oldContent = JGitUtil.getContentFromId(git, diff.getOldId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray), - newContent = JGitUtil.getContentFromId(git, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray), - oldIsImage = oldIsImage, - newIsImage = newIsImage, + changeType = diff.getChangeType, + oldPath = diff.getOldPath, + newPath = diff.getNewPath, + oldContent = JGitUtil + .getContentFromId(git, diff.getOldId.toObjectId, false) + .filter(FileUtil.isText) + .map(convertFromByteArray), + newContent = JGitUtil + .getContentFromId(git, diff.getNewId.toObjectId, false) + .filter(FileUtil.isText) + .map(convertFromByteArray), + oldIsImage = oldIsImage, + newIsImage = newIsImage, oldObjectId = Option(diff.getOldId).map(_.name), newObjectId = Option(diff.getNewId).map(_.name), - oldMode = diff.getOldMode.toString, - newMode = diff.getNewMode.toString, - tooLarge = false, - patch = (if(makePatch) Some(makePatchFromDiffEntry(git, diff)) else None) // TODO use DiffFormatter + oldMode = diff.getOldMode.toString, + newMode = diff.getNewMode.toString, + tooLarge = false, + patch = (if (makePatch) Some(makePatchFromDiffEntry(git, diff)) else None) // TODO use DiffFormatter ) } } @@ -622,7 +731,7 @@ private def makePatchFromDiffEntry(git: Git, diff: DiffEntry): String = { val out = new ByteArrayOutputStream() - using(new DiffFormatter(out)){ formatter => + using(new DiffFormatter(out)) { formatter => formatter.setRepository(git.getRepository) formatter.format(diff) val patch = new String(out.toByteArray) // TODO charset??? @@ -634,13 +743,20 @@ * Returns the list of branch names of the specified commit. */ def getBranchesOfCommit(git: Git, commitId: String): List[String] = - using(new RevWalk(git.getRepository)){ revWalk => - defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))){ commit => - git.getRepository.getAllRefs.entrySet.asScala.filter { e => - (e.getKey.startsWith(Constants.R_HEADS) && revWalk.isMergedInto(commit, revWalk.parseCommit(e.getValue.getObjectId))) - }.map { e => - e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length) - }.toList.sorted + using(new RevWalk(git.getRepository)) { revWalk => + defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit => + git.getRepository.getAllRefs.entrySet.asScala + .filter { e => + (e.getKey.startsWith(Constants.R_HEADS) && revWalk.isMergedInto( + commit, + revWalk.parseCommit(e.getValue.getObjectId) + )) + } + .map { e => + e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_HEADS.length) + } + .toList + .sorted } } @@ -648,44 +764,56 @@ * Returns the list of tags of the specified commit. */ def getTagsOfCommit(git: Git, commitId: String): List[String] = - using(new RevWalk(git.getRepository)){ revWalk => - defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))){ commit => - git.getRepository.getAllRefs.entrySet.asScala.filter { e => - (e.getKey.startsWith(Constants.R_TAGS) && revWalk.isMergedInto(commit, revWalk.parseCommit(e.getValue.getObjectId))) - }.map { e => - e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length) - }.toList.sorted.reverse + using(new RevWalk(git.getRepository)) { revWalk => + defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit => + git.getRepository.getAllRefs.entrySet.asScala + .filter { e => + (e.getKey.startsWith(Constants.R_TAGS) && revWalk.isMergedInto( + commit, + revWalk.parseCommit(e.getValue.getObjectId) + )) + } + .map { e => + e.getValue.getName.substring(org.eclipse.jgit.lib.Constants.R_TAGS.length) + } + .toList + .sorted + .reverse } } def initRepository(dir: java.io.File): Unit = - using(new RepositoryBuilder().setGitDir(dir).setBare.build){ repository => + using(new RepositoryBuilder().setGitDir(dir).setBare.build) { repository => repository.create(true) setReceivePack(repository) } def cloneRepository(from: java.io.File, to: java.io.File): Unit = - using(Git.cloneRepository.setURI(from.toURI.toString).setDirectory(to).setBare(true).call){ git => + using(Git.cloneRepository.setURI(from.toURI.toString).setDirectory(to).setBare(true).call) { git => setReceivePack(git.getRepository) } def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = - defining(repository.getConfig){ config => + defining(repository.getConfig) { config => config.setBoolean("http", null, "receivepack", true) config.save } - def getDefaultBranch(git: Git, repository: RepositoryService.RepositoryInfo, - revstr: String = ""): Option[(ObjectId, String)] = { + def getDefaultBranch( + git: Git, + repository: RepositoryService.RepositoryInfo, + revstr: String = "" + ): Option[(ObjectId, String)] = { Seq( - Some(if(revstr.isEmpty) repository.repository.defaultBranch else revstr), + Some(if (revstr.isEmpty) repository.repository.defaultBranch else revstr), repository.branchList.headOption ).flatMap { - case Some(rev) => Some((git.getRepository.resolve(rev), rev)) - case None => None - }.find(_._1 != null) + case Some(rev) => Some((git.getRepository.resolve(rev), rev)) + case None => None + } + .find(_._1 != null) } def createBranch(git: Git, fromBranch: String, newBranch: String) = { @@ -706,13 +834,21 @@ entry } - def createNewCommit(git: Git, inserter: ObjectInserter, headId: AnyObjectId, treeId: AnyObjectId, - ref: String, fullName: String, mailAddress: String, message: String): ObjectId = { + def createNewCommit( + git: Git, + inserter: ObjectInserter, + headId: AnyObjectId, + treeId: AnyObjectId, + ref: String, + fullName: String, + mailAddress: String, + message: String + ): ObjectId = { val newCommit = new CommitBuilder() newCommit.setCommitter(new PersonIdent(fullName, mailAddress)) newCommit.setAuthor(new PersonIdent(fullName, mailAddress)) newCommit.setMessage(message) - if(headId != null){ + if (headId != null) { newCommit.setParentIds(List(headId).asJava) } newCommit.setTreeId(treeId) @@ -740,7 +876,7 @@ val config = new BlobBasedConfig(repository.getConfig(), bytes) config.getSubsections("submodule").asScala.map { module => val path = config.getString("submodule", module, "path") - val url = config.getString("submodule", module, "url") + val url = config.getString("submodule", module, "url") SubmoduleInfo(module, path, url, StringUtil.getRepositoryViewerUrl(url, baseUrl)) } } catch { @@ -750,7 +886,7 @@ } }).toList } getOrElse Nil - } + } /** * Get object content of the given path as byte array from the Git repository. @@ -764,12 +900,12 @@ def getContentFromPath(git: Git, revTree: RevTree, path: String, fetchLargeFile: Boolean): Option[Array[Byte]] = { @scala.annotation.tailrec def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match { - case true if(walk.getPathString == path) => Some(walk.getObjectId(0)) - case true => getPathObjectId(path, walk) - case false => None + case true if (walk.getPathString == path) => Some(walk.getObjectId(0)) + case true => getPathObjectId(path, walk) + case false => None } - using(new TreeWalk(git.getRepository)){ treeWalk => + using(new TreeWalk(git.getRepository)) { treeWalk => treeWalk.addTree(revTree) treeWalk.setRecursive(true) getPathObjectId(path, treeWalk) @@ -779,19 +915,22 @@ } def getLfsObjects(text: String): Map[String, String] = { - if(text.startsWith("version https://git-lfs.github.com/spec/v1")){ - // LFS objects - text.split("\n").map { line => + if (text.startsWith("version https://git-lfs.github.com/spec/v1")) { + // LFS objects + text + .split("\n") + .map { line => val dim = line.split(" ") dim(0) -> dim(1) - }.toMap - } else { - Map.empty - } + } + .toMap + } else { + Map.empty + } } def getContentSize(loader: ObjectLoader): Long = { - if(loader.isLarge) { + if (loader.isLarge) { loader.getSize } else { val bytes = loader.getCachedBytes @@ -800,7 +939,7 @@ val attr = getLfsObjects(text) attr.get("size") match { case Some(size) => size.toLong - case None => loader.getSize + case None => loader.getSize } } } @@ -811,18 +950,23 @@ def getContentInfo(git: Git, path: String, objectId: ObjectId): ContentInfo = { // Viewer - using(git.getRepository.getObjectDatabase){ db => + using(git.getRepository.getObjectDatabase) { db => val loader = db.open(objectId) - val isLfs = isLfsPointer(loader) - val large = FileUtil.isLarge(loader.getSize) - val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other" - val bytes = if(viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None + val isLfs = isLfsPointer(loader) + val large = FileUtil.isLarge(loader.getSize) + val viewer = if (FileUtil.isImage(path)) "image" else if (large) "large" else "other" + val bytes = if (viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None val size = Some(getContentSize(loader)) - if(viewer == "other"){ - if(!isLfs && bytes.isDefined && FileUtil.isText(bytes.get)){ + if (viewer == "other") { + if (!isLfs && bytes.isDefined && FileUtil.isText(bytes.get)) { // text - ContentInfo("text", size, Some(StringUtil.convertFromByteArray(bytes.get)), Some(StringUtil.detectEncoding(bytes.get))) + ContentInfo( + "text", + size, + Some(StringUtil.convertFromByteArray(bytes.get)), + Some(StringUtil.detectEncoding(bytes.get)) + ) } else { // binary ContentInfo("binary", size, None, None) @@ -842,18 +986,19 @@ * @param fetchLargeFile if false then returns None for the large file * @return the byte array of content or None if object does not exist */ - def getContentFromId(git: Git, id: ObjectId, fetchLargeFile: Boolean): Option[Array[Byte]] = try { - using(git.getRepository.getObjectDatabase){ db => - val loader = db.open(id) - if(loader.isLarge || (fetchLargeFile == false && FileUtil.isLarge(loader.getSize))){ - None - } else { - Some(loader.getBytes) + def getContentFromId(git: Git, id: ObjectId, fetchLargeFile: Boolean): Option[Array[Byte]] = + try { + using(git.getRepository.getObjectDatabase) { db => + val loader = db.open(id) + if (loader.isLarge || (fetchLargeFile == false && FileUtil.isLarge(loader.getSize))) { + None + } else { + Some(loader.getBytes) + } } + } catch { + case e: MissingObjectException => None } - } catch { - case e: MissingObjectException => None - } /** * Get objectLoader of the given object id from the Git repository. @@ -863,35 +1008,37 @@ * @param f the function process ObjectLoader * @return None if object does not exist */ - def getObjectLoaderFromId[A](git: Git, id: ObjectId)(f: ObjectLoader => A):Option[A] = try { - using(git.getRepository.getObjectDatabase){ db => - Some(f(db.open(id))) + def getObjectLoaderFromId[A](git: Git, id: ObjectId)(f: ObjectLoader => A): Option[A] = + try { + using(git.getRepository.getObjectDatabase) { db => + Some(f(db.open(id))) + } + } catch { + case e: MissingObjectException => None } - } catch { - case e: MissingObjectException => None - } /** * Returns all commit id in the specified repository. */ - def getAllCommitIds(git: Git): Seq[String] = if(isEmpty(git)) { - Nil - } else { - val existIds = new scala.collection.mutable.ListBuffer[String]() - val i = git.log.all.call.iterator - while(i.hasNext){ - existIds += i.next.name + def getAllCommitIds(git: Git): Seq[String] = + if (isEmpty(git)) { + Nil + } else { + val existIds = new scala.collection.mutable.ListBuffer[String]() + val i = git.log.all.call.iterator + while (i.hasNext) { + existIds += i.next.name + } + existIds.toSeq } - existIds.toSeq - } def processTree[T](git: Git, id: ObjectId)(f: (String, CanonicalTreeParser) => T): Seq[T] = { - using(new RevWalk(git.getRepository)){ revWalk => - using(new TreeWalk(git.getRepository)){ treeWalk => + using(new RevWalk(git.getRepository)) { revWalk => + using(new TreeWalk(git.getRepository)) { treeWalk => val index = treeWalk.addTree(revWalk.parseTree(id)) treeWalk.setRecursive(true) val result = new collection.mutable.ListBuffer[T]() - while(treeWalk.next){ + while (treeWalk.next) { result += f(treeWalk.getPathString, treeWalk.getTree(index, classOf[CanonicalTreeParser])) } result.toSeq @@ -902,10 +1049,17 @@ /** * Returns the identifier of the root commit (or latest merge commit) of the specified branch. */ - def getForkedCommitId(oldGit: Git, newGit: Git, - userName: String, repositoryName: String, branch: String, - requestUserName: String, requestRepositoryName: String, requestBranch: String): String = - defining(getAllCommitIds(oldGit)){ existIds => + def getForkedCommitId( + oldGit: Git, + newGit: Git, + userName: String, + repositoryName: String, + branch: String, + requestUserName: String, + requestRepositoryName: String, + requestBranch: String + ): String = + defining(getAllCommitIds(oldGit)) { existIds => getCommitLogs(newGit, requestBranch, true) { commit => existIds.contains(commit.name) && getBranchesOfCommit(oldGit, commit.getName).contains(branch) }.head.id @@ -914,19 +1068,35 @@ /** * Fetch pull request contents into refs/pull/${issueId}/head and return (commitIdTo, commitIdFrom) */ - def updatePullRequest(userName: String, repositoryName:String, branch: String, issueId: Int, - requestUserName: String, requestRepositoryName: String, requestBranch: String):(String, String) = - using(Git.open(Directory.getRepositoryDir(userName, repositoryName)), - Git.open(Directory.getRepositoryDir(requestUserName, requestRepositoryName))){ (oldGit, newGit) => + def updatePullRequest( + userName: String, + repositoryName: String, + branch: String, + issueId: Int, + requestUserName: String, + requestRepositoryName: String, + requestBranch: String + ): (String, String) = + using( + Git.open(Directory.getRepositoryDir(userName, repositoryName)), + Git.open(Directory.getRepositoryDir(requestUserName, requestRepositoryName)) + ) { (oldGit, newGit) => oldGit.fetch .setRemote(Directory.getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString) .setRefSpecs(new RefSpec(s"refs/heads/${requestBranch}:refs/pull/${issueId}/head").setForceUpdate(true)) .call val commitIdTo = oldGit.getRepository.resolve(s"refs/pull/${issueId}/head").getName - val commitIdFrom = getForkedCommitId(oldGit, newGit, - userName, repositoryName, branch, - requestUserName, requestRepositoryName, requestBranch) + val commitIdFrom = getForkedCommitId( + oldGit, + newGit, + userName, + repositoryName, + branch, + requestUserName, + requestRepositoryName, + requestBranch + ) (commitIdTo, commitIdFrom) } @@ -943,20 +1113,20 @@ } def getBranches(owner: String, name: String, defaultBranch: String, origin: Boolean): Seq[BranchInfo] = { - using(Git.open(getRepositoryDir(owner, name))){ git => + using(Git.open(getRepositoryDir(owner, name))) { git => val repo = git.getRepository val defaultObject = repo.resolve(defaultBranch) git.branchList.call.asScala.map { ref => val walk = new RevWalk(repo) try { - val defaultCommit = walk.parseCommit(defaultObject) - val branchName = ref.getName.stripPrefix("refs/heads/") - val branchCommit = walk.parseCommit(ref.getObjectId) - val when = branchCommit.getCommitterIdent.getWhen - val committer = branchCommit.getCommitterIdent.getName + val defaultCommit = walk.parseCommit(defaultObject) + val branchName = ref.getName.stripPrefix("refs/heads/") + val branchCommit = walk.parseCommit(ref.getObjectId) + val when = branchCommit.getCommitterIdent.getWhen + val committer = branchCommit.getCommitterIdent.getName val committerEmail = branchCommit.getCommitterIdent.getEmailAddress - val mergeInfo = if(origin && branchName == defaultBranch){ + val mergeInfo = if (origin && branchName == defaultBranch) { None } else { walk.reset() @@ -966,10 +1136,13 @@ val mergeBase = walk.next() walk.reset() walk.setRevFilter(RevFilter.ALL) - Some(BranchMergeInfo( - ahead = RevWalkUtils.count(walk, branchCommit, mergeBase), - behind = RevWalkUtils.count(walk, defaultCommit, mergeBase), - isMerged = walk.isMergedInto(branchCommit, defaultCommit))) + Some( + BranchMergeInfo( + ahead = RevWalkUtils.count(walk, branchCommit, mergeBase), + behind = RevWalkUtils.count(walk, defaultCommit, mergeBase), + isMerged = walk.isMergedInto(branchCommit, defaultCommit) + ) + ) } BranchInfo(branchName, committer, when, committerEmail, mergeInfo, ref.getObjectId.name) } finally { @@ -980,33 +1153,38 @@ } def getBlame(git: Git, id: String, path: String): Iterable[BlameInfo] = { - Option(git.getRepository.resolve(id)).map{ commitId => - val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository) - blamer.setStartCommit(commitId) - blamer.setFilePath(path) - val blame = blamer.call() - var blameMap = Map[String, JGitUtil.BlameInfo]() - var idLine = List[(String, Int)]() - val commits = 0.to(blame.getResultContents().size() - 1).map { i => - val c = blame.getSourceCommit(i) - if(!blameMap.contains(c.name)){ - blameMap += c.name -> JGitUtil.BlameInfo( - c.name, - c.getAuthorIdent.getName, - c.getAuthorIdent.getEmailAddress, - c.getAuthorIdent.getWhen, - Option(git.log.add(c).addPath(blame.getSourcePath(i)).setSkip(1).setMaxCount(2).call.iterator.next) - .map(_.name), - if(blame.getSourcePath(i)==path){ None } else { Some(blame.getSourcePath(i)) }, - c.getCommitterIdent.getWhen, - c.getShortMessage, - Set.empty) + Option(git.getRepository.resolve(id)) + .map { commitId => + val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository) + blamer.setStartCommit(commitId) + blamer.setFilePath(path) + val blame = blamer.call() + var blameMap = Map[String, JGitUtil.BlameInfo]() + var idLine = List[(String, Int)]() + val commits = 0.to(blame.getResultContents().size() - 1).map { i => + val c = blame.getSourceCommit(i) + if (!blameMap.contains(c.name)) { + blameMap += c.name -> JGitUtil.BlameInfo( + c.name, + c.getAuthorIdent.getName, + c.getAuthorIdent.getEmailAddress, + c.getAuthorIdent.getWhen, + Option(git.log.add(c).addPath(blame.getSourcePath(i)).setSkip(1).setMaxCount(2).call.iterator.next) + .map(_.name), + if (blame.getSourcePath(i) == path) { None } else { Some(blame.getSourcePath(i)) }, + c.getCommitterIdent.getWhen, + c.getShortMessage, + Set.empty + ) + } + idLine :+= (c.name, i) } - idLine :+= (c.name, i) + val limeMap = idLine.groupBy(_._1).mapValues(_.map(_._2).toSet) + blameMap.values.map { b => + b.copy(lines = limeMap(b.id)) + } } - val limeMap = idLine.groupBy(_._1).mapValues(_.map(_._2).toSet) - blameMap.values.map{b => b.copy(lines=limeMap(b.id))} - }.getOrElse(Seq.empty) + .getOrElse(Seq.empty) } /** @@ -1017,8 +1195,8 @@ * @param revstr A git object references expression * @return sha1 */ - def getShaByRef(owner:String, name:String,revstr: String): Option[String] = { - using(Git.open(getRepositoryDir(owner, name))){ git => + def getShaByRef(owner: String, name: String, revstr: String): Option[String] = { + using(Git.open(getRepositoryDir(owner, name))) { git => Option(git.getRepository.resolve(revstr)).map(ObjectId.toString(_)) } } diff --git a/src/main/scala/gitbucket/core/util/Keys.scala b/src/main/scala/gitbucket/core/util/Keys.scala index 4fd8e42..cf3135c 100644 --- a/src/main/scala/gitbucket/core/util/Keys.scala +++ b/src/main/scala/gitbucket/core/util/Keys.scala @@ -26,8 +26,8 @@ val DashboardPulls = "dashboard/pulls" /** - * Session key for the OpenID Connect authentication. - */ + * Session key for the OpenID Connect authentication. + */ val OidcContext = "oidcContext" /** diff --git a/src/main/scala/gitbucket/core/util/LDAPUtil.scala b/src/main/scala/gitbucket/core/util/LDAPUtil.scala index 65d2279..9facd51 100644 --- a/src/main/scala/gitbucket/core/util/LDAPUtil.scala +++ b/src/main/scala/gitbucket/core/util/LDAPUtil.scala @@ -43,48 +43,70 @@ */ def authenticate(ldapSettings: Ldap, userName: String, password: String): Either[String, LDAPUserInfo] = { bind( - host = ldapSettings.host, - port = ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort), - dn = ldapSettings.bindDN.getOrElse(""), + host = ldapSettings.host, + port = ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort), + dn = ldapSettings.bindDN.getOrElse(""), password = ldapSettings.bindPassword.getOrElse(""), - tls = ldapSettings.tls.getOrElse(false), - ssl = ldapSettings.ssl.getOrElse(false), + tls = ldapSettings.tls.getOrElse(false), + ssl = ldapSettings.ssl.getOrElse(false), keystore = ldapSettings.keystore.getOrElse(""), - error = "System LDAP authentication failed." - ){ conn => - findUser(conn, userName, ldapSettings.baseDN, ldapSettings.userNameAttribute, ldapSettings.additionalFilterCondition) match { + error = "System LDAP authentication failed." + ) { conn => + findUser( + conn, + userName, + ldapSettings.baseDN, + ldapSettings.userNameAttribute, + ldapSettings.additionalFilterCondition + ) match { case Some(userDN) => userAuthentication(ldapSettings, userDN, userName, password) case None => Left("User does not exist.") } } } - private def userAuthentication(ldapSettings: Ldap, userDN: String, userName: String, password: String): Either[String, LDAPUserInfo] = { + private def userAuthentication( + ldapSettings: Ldap, + userDN: String, + userName: String, + password: String + ): Either[String, LDAPUserInfo] = { bind( - host = ldapSettings.host, - port = ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort), - dn = userDN, + host = ldapSettings.host, + port = ldapSettings.port.getOrElse(SystemSettingsService.DefaultLdapPort), + dn = userDN, password = password, - tls = ldapSettings.tls.getOrElse(false), - ssl = ldapSettings.ssl.getOrElse(false), + tls = ldapSettings.tls.getOrElse(false), + ssl = ldapSettings.ssl.getOrElse(false), keystore = ldapSettings.keystore.getOrElse(""), - error = "User LDAP Authentication Failed." - ){ conn => - if(ldapSettings.mailAttribute.getOrElse("").isEmpty) { - Right(LDAPUserInfo( - userName = userName, - fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute => - findFullName(conn, userDN, ldapSettings.userNameAttribute, userName, fullNameAttribute) - }.getOrElse(userName), - mailAddress = createDummyMailAddress(userName))) + error = "User LDAP Authentication Failed." + ) { conn => + if (ldapSettings.mailAttribute.getOrElse("").isEmpty) { + Right( + LDAPUserInfo( + userName = userName, + fullName = ldapSettings.fullNameAttribute + .flatMap { fullNameAttribute => + findFullName(conn, userDN, ldapSettings.userNameAttribute, userName, fullNameAttribute) + } + .getOrElse(userName), + mailAddress = createDummyMailAddress(userName) + ) + ) } else { findMailAddress(conn, userDN, ldapSettings.userNameAttribute, userName, ldapSettings.mailAttribute.get) match { - case Some(mailAddress) => Right(LDAPUserInfo( - userName = getUserNameFromMailAddress(userName), - fullName = ldapSettings.fullNameAttribute.flatMap { fullNameAttribute => - findFullName(conn, userDN, ldapSettings.userNameAttribute, userName, fullNameAttribute) - }.getOrElse(userName), - mailAddress = mailAddress)) + case Some(mailAddress) => + Right( + LDAPUserInfo( + userName = getUserNameFromMailAddress(userName), + fullName = ldapSettings.fullNameAttribute + .flatMap { fullNameAttribute => + findFullName(conn, userDN, ldapSettings.userNameAttribute, userName, fullNameAttribute) + } + .getOrElse(userName), + mailAddress = mailAddress + ) + ) case None => Left("Can't find mail address.") } } @@ -98,8 +120,16 @@ }).replaceAll("[^a-zA-Z0-9\\-_.]", "").replaceAll("^[_\\-]", "") } - private def bind[A](host: String, port: Int, dn: String, password: String, tls: Boolean, ssl: Boolean, keystore: String, error: String) - (f: LDAPConnection => Either[String, A]): Either[String, A] = { + private def bind[A]( + host: String, + port: Int, + dn: String, + password: String, + tls: Boolean, + ssl: Boolean, + keystore: String, + error: String + )(f: LDAPConnection => Either[String, A]): Either[String, A] = { if (tls) { // Dynamically set Sun as the security provider Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()) @@ -112,11 +142,11 @@ } val conn: LDAPConnection = - if(ssl) { - new LDAPConnection(new LDAPJSSESecureSocketFactory()) - }else { - new LDAPConnection(new LDAPJSSEStartTLSFactory()) - } + if (ssl) { + new LDAPConnection(new LDAPJSSESecureSocketFactory()) + } else { + new LDAPConnection(new LDAPJSSEStartTLSFactory()) + } try { // Connect to the server @@ -150,15 +180,24 @@ /** * Search a specified user and returns userDN if exists. */ - private def findUser(conn: LDAPConnection, userName: String, baseDN: String, userNameAttribute: String, additionalFilterCondition: Option[String]): Option[String] = { + private def findUser( + conn: LDAPConnection, + userName: String, + baseDN: String, + userNameAttribute: String, + additionalFilterCondition: Option[String] + ): Option[String] = { @tailrec def getEntries(results: LDAPSearchResults, entries: List[Option[LDAPEntry]] = Nil): List[LDAPEntry] = { - if(results.hasMore){ - getEntries(results, entries :+ (try { - Option(results.next) - } catch { - case ex: LDAPReferralException => None // NOTE(tanacasino): Referral follow is off. so ignores it.(for AD) - })) + if (results.hasMore) { + getEntries( + results, + entries :+ (try { + Option(results.next) + } catch { + case ex: LDAPReferralException => None // NOTE(tanacasino): Referral follow is off. so ignores it.(for AD) + }) + ) } else { entries.flatten } @@ -166,7 +205,7 @@ val filterCond = additionalFilterCondition.getOrElse("") match { case "" => userNameAttribute + "=" + userName - case x => "(&(" + x + ")(" + userNameAttribute + "=" + userName + "))" + case x => "(&(" + x + ")(" + userNameAttribute + "=" + userName + "))" } getEntries(conn.search(baseDN, LDAPConnection.SCOPE_SUB, filterCond, null, false)).collectFirst { @@ -174,16 +213,44 @@ } } - private def findMailAddress(conn: LDAPConnection, userDN: String, userNameAttribute: String, userName: String, mailAttribute: String): Option[String] = - defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, userNameAttribute + "=" + userName, Array[String](mailAttribute), false)){ results => - if(results.hasMore) { + private def findMailAddress( + conn: LDAPConnection, + userDN: String, + userNameAttribute: String, + userName: String, + mailAttribute: String + ): Option[String] = + defining( + conn.search( + userDN, + LDAPConnection.SCOPE_BASE, + userNameAttribute + "=" + userName, + Array[String](mailAttribute), + false + ) + ) { results => + if (results.hasMore) { Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue) } else None } - private def findFullName(conn: LDAPConnection, userDN: String, userNameAttribute: String, userName: String, nameAttribute: String): Option[String] = - defining(conn.search(userDN, LDAPConnection.SCOPE_BASE, userNameAttribute + "=" + userName, Array[String](nameAttribute), false)){ results => - if(results.hasMore) { + private def findFullName( + conn: LDAPConnection, + userDN: String, + userNameAttribute: String, + userName: String, + nameAttribute: String + ): Option[String] = + defining( + conn.search( + userDN, + LDAPConnection.SCOPE_BASE, + userNameAttribute + "=" + userName, + Array[String](nameAttribute), + false + ) + ) { results => + if (results.hasMore) { Option(results.next.getAttribute(nameAttribute)).map(_.getStringValue) } else None } diff --git a/src/main/scala/gitbucket/core/util/LockUtil.scala b/src/main/scala/gitbucket/core/util/LockUtil.scala index 0924719..75c4e33 100644 --- a/src/main/scala/gitbucket/core/util/LockUtil.scala +++ b/src/main/scala/gitbucket/core/util/LockUtil.scala @@ -15,7 +15,7 @@ * Returns the lock object for the specified repository. */ private def getLockObject(key: String): Lock = synchronized { - if(!locks.containsKey(key)){ + if (!locks.containsKey(key)) { locks.put(key, new ReentrantLock()) } locks.get(key) @@ -24,7 +24,7 @@ /** * Synchronizes a given function which modifies the working copy of the wiki repository. */ - def lock[T](key: String)(f: => T): T = defining(getLockObject(key)){ lock => + def lock[T](key: String)(f: => T): T = defining(getLockObject(key)) { lock => try { lock.lock() f diff --git a/src/main/scala/gitbucket/core/util/Mailer.scala b/src/main/scala/gitbucket/core/util/Mailer.scala index 703f4b4..91d063a 100644 --- a/src/main/scala/gitbucket/core/util/Mailer.scala +++ b/src/main/scala/gitbucket/core/util/Mailer.scala @@ -6,15 +6,27 @@ import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail} import SystemSettingsService.SystemSettings -class Mailer(settings: SystemSettings){ +class Mailer(settings: SystemSettings) { - def send(to: String, subject: String, textMsg: String, htmlMsg: Option[String] = None, loginAccount: Option[Account] = None): Unit = { + def send( + to: String, + subject: String, + textMsg: String, + htmlMsg: Option[String] = None, + loginAccount: Option[Account] = None + ): Unit = { createMail(subject, textMsg, htmlMsg, loginAccount).foreach { email => email.addTo(to).send } } - def sendBcc(bcc: Seq[String], subject: String, textMsg: String, htmlMsg: Option[String] = None, loginAccount: Option[Account] = None): Unit = { + def sendBcc( + bcc: Seq[String], + subject: String, + textMsg: String, + htmlMsg: Option[String] = None, + loginAccount: Option[Account] = None + ): Unit = { createMail(subject, textMsg, htmlMsg, loginAccount).foreach { email => bcc.foreach { address => email.addBcc(address) @@ -23,8 +35,13 @@ } } - def createMail(subject: String, textMsg: String, htmlMsg: Option[String] = None, loginAccount: Option[Account] = None): Option[HtmlEmail] = { - if(settings.notification == true){ + def createMail( + subject: String, + textMsg: String, + htmlMsg: Option[String] = None, + loginAccount: Option[Account] = None + ): Option[HtmlEmail] = { + if (settings.notification == true) { settings.smtp.map { smtp => val email = new HtmlEmail email.setHostName(smtp.host) @@ -34,7 +51,7 @@ } smtp.ssl.foreach { ssl => email.setSSLOnConnect(ssl) - if(ssl == true) { + if (ssl == true) { email.setSslSmtpPort(smtp.port.get.toString) } } @@ -43,10 +60,11 @@ email.setStartTLSRequired(starttls) } smtp.fromAddress - .map (_ -> smtp.fromName.getOrElse(loginAccount.map(_.userName).getOrElse("GitBucket"))) - .orElse (Some("notifications@gitbucket.com" -> loginAccount.map(_.userName).getOrElse("GitBucket"))) - .foreach { case (address, name) => - email.setFrom(address, name) + .map(_ -> smtp.fromName.getOrElse(loginAccount.map(_.userName).getOrElse("GitBucket"))) + .orElse(Some("notifications@gitbucket.com" -> loginAccount.map(_.userName).getOrElse("GitBucket"))) + .foreach { + case (address, name) => + email.setFrom(address, name) } email.setCharset("UTF-8") email.setSubject(subject) diff --git a/src/main/scala/gitbucket/core/util/RepositoryName.scala b/src/main/scala/gitbucket/core/util/RepositoryName.scala index 9f0825b..b3f41a4 100644 --- a/src/main/scala/gitbucket/core/util/RepositoryName.scala +++ b/src/main/scala/gitbucket/core/util/RepositoryName.scala @@ -1,19 +1,23 @@ package gitbucket.core.util // TODO Move to gitbucket.core.api package? -case class RepositoryName(owner: String, name: String){ +case class RepositoryName(owner: String, name: String) { val fullName = s"${owner}/${name}" } -object RepositoryName{ +object RepositoryName { def apply(fullName: String): RepositoryName = { fullName.split("/").toList match { case owner :: name :: Nil => RepositoryName(owner, name) - case _ => throw new IllegalArgumentException(s"${fullName} is not repositoryName (only 'owner/name')") + case _ => throw new IllegalArgumentException(s"${fullName} is not repositoryName (only 'owner/name')") } } - def apply(repository: gitbucket.core.model.Repository): RepositoryName = RepositoryName(repository.userName, repository.repositoryName) - def apply(repository: gitbucket.core.util.JGitUtil.RepositoryInfo): RepositoryName = RepositoryName(repository.owner, repository.name) - def apply(repository: gitbucket.core.service.RepositoryService.RepositoryInfo): RepositoryName = RepositoryName(repository.owner, repository.name) - def apply(repository: gitbucket.core.model.CommitStatus): RepositoryName = RepositoryName(repository.userName, repository.repositoryName) + def apply(repository: gitbucket.core.model.Repository): RepositoryName = + RepositoryName(repository.userName, repository.repositoryName) + def apply(repository: gitbucket.core.util.JGitUtil.RepositoryInfo): RepositoryName = + RepositoryName(repository.owner, repository.name) + def apply(repository: gitbucket.core.service.RepositoryService.RepositoryInfo): RepositoryName = + RepositoryName(repository.owner, repository.name) + def apply(repository: gitbucket.core.model.CommitStatus): RepositoryName = + RepositoryName(repository.userName, repository.repositoryName) } diff --git a/src/main/scala/gitbucket/core/util/StringUtil.scala b/src/main/scala/gitbucket/core/util/StringUtil.scala index bf79ebf..f61c898 100644 --- a/src/main/scala/gitbucket/core/util/StringUtil.scala +++ b/src/main/scala/gitbucket/core/util/StringUtil.scala @@ -19,7 +19,7 @@ } def sha1(value: String): String = - defining(java.security.MessageDigest.getInstance("SHA-1")){ md => + defining(java.security.MessageDigest.getInstance("SHA-1")) { md => md.update(value.getBytes) md.digest.map(b => "%02x".format(b)).mkString } @@ -50,7 +50,7 @@ def splitWords(value: String): Array[String] = value.split("[ \\t ]+") - def isInteger(value: String): Boolean = allCatch opt { value.toInt } map(_ => true) getOrElse(false) + def isInteger(value: String): Boolean = allCatch opt { value.toInt } map (_ => true) getOrElse (false) def escapeHtml(value: String): String = value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """) @@ -63,7 +63,7 @@ IOUtils.toString(new BOMInputStream(new java.io.ByteArrayInputStream(content)), detectEncoding(content)) def detectEncoding(content: Array[Byte]): String = - defining(new UniversalDetector(null)){ detector => + defining(new UniversalDetector(null)) { detector => detector.handleData(content, 0, content.length) detector.dataEnd() detector.getDetectedCharset match { @@ -81,7 +81,7 @@ */ def convertLineSeparator(content: String, lineSeparator: String): String = { val lf = content.replace("\r\n", "\n").replace("\r", "\n") - if(lineSeparator == "CRLF"){ + if (lineSeparator == "CRLF") { lf.replace("\n", "\r\n") } else { lf @@ -96,7 +96,7 @@ * @return the converted content */ def appendNewLine(content: String, lineSeparator: String): String = { - if(lineSeparator == "CRLF") { + if (lineSeparator == "CRLF") { if (content.endsWith("\r\n")) content else content + "\r\n" } else { if (content.endsWith("\n")) content else content + "\n" @@ -111,7 +111,11 @@ */ def extractIssueId(message: String): Seq[String] = "(^|\\W)#(\\d+)(\\W|$)".r - .findAllIn(message).matchData.map(_.group(2)).toSeq.distinct + .findAllIn(message) + .matchData + .map(_.group(2)) + .toSeq + .distinct /** * Extract close issue id like ```close #issueId ``` from the given message. @@ -121,23 +125,28 @@ */ def extractCloseId(message: String): Seq[String] = "(?i)(? s"${removeUserName(base)}/$user/$repository" - case GitHubUrlPattern (_, user, repository) => s"https://github.com/$user/$repository" + case GitBucketUrlPattern(base, user, repository) + if baseUrl.map(removeUserName(base).startsWith).getOrElse(false) => + s"${removeUserName(base)}/$user/$repository" + case GitHubUrlPattern(_, user, repository) => s"https://github.com/$user/$repository" case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository" - case GitLabUrlPattern (_, user, repository) => s"https://gitlab.com/$user/$repository" - case _ => gitRepositoryUrl + case GitLabUrlPattern(_, user, repository) => s"https://gitlab.com/$user/$repository" + case _ => gitRepositoryUrl } } diff --git a/src/main/scala/gitbucket/core/util/SyntaxSugars.scala b/src/main/scala/gitbucket/core/util/SyntaxSugars.scala index 74d39cb..def4c3a 100644 --- a/src/main/scala/gitbucket/core/util/SyntaxSugars.scala +++ b/src/main/scala/gitbucket/core/util/SyntaxSugars.scala @@ -12,8 +12,9 @@ def defining[A, B](value: A)(f: A => B): B = f(value) def using[A <% { def close(): Unit }, B](resource: A)(f: A => B): B = - try f(resource) finally { - if(resource != null){ + try f(resource) + finally { + if (resource != null) { ignoring(classOf[Throwable]) { resource.close() } @@ -21,13 +22,14 @@ } 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){ + try f(resource1, resource2) + finally { + if (resource1 != null) { ignoring(classOf[Throwable]) { resource1.close() } } - if(resource2 != null){ + if (resource2 != null) { ignoring(classOf[Throwable]) { resource2.close() } @@ -35,19 +37,22 @@ } def using[T](git: Git)(f: Git => T): T = - try f(git) finally git.getRepository.close() + try f(git) + finally git.getRepository.close() def using[T](git1: Git, git2: Git)(f: (Git, Git) => T): T = - try f(git1, git2) finally { + try f(git1, git2) + finally { git1.getRepository.close() git2.getRepository.close() } - def ignore[T](f: => Unit): Unit = try { - f - } catch { - case _: Exception => () - } + def ignore[T](f: => Unit): Unit = + try { + f + } catch { + case _: Exception => () + } object ~ { def unapply[A, B](t: (A, B)): Option[(A, B)] = Some(t) @@ -57,7 +62,7 @@ * Provides easier and explicit ways to access to a head value of `Map[String, Seq[String]]`. * This is intended to use in implementations of scalatra-forms's `Constraint` or `ValueType`. */ - implicit class HeadValueAccessibleMap(map: Map[String, Seq[String]]){ + implicit class HeadValueAccessibleMap(map: Map[String, Seq[String]]) { def value(key: String): String = map(key).head def optionValue(key: String): Option[String] = map.get(key).flatMap(_.headOption) def values(key: String): Seq[String] = map.get(key).getOrElse(Seq.empty) diff --git a/src/main/scala/gitbucket/core/util/TextAvatarUtil.scala b/src/main/scala/gitbucket/core/util/TextAvatarUtil.scala index 48c0317..0f6d7ce 100644 --- a/src/main/scala/gitbucket/core/util/TextAvatarUtil.scala +++ b/src/main/scala/gitbucket/core/util/TextAvatarUtil.scala @@ -7,7 +7,6 @@ import java.awt.font.{FontRenderContext, TextLayout} import java.awt.geom.AffineTransform - object TextAvatarUtil { private val iconSize = 200 private val fontSize = 180 @@ -21,7 +20,9 @@ // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef private def relativeLuminance(c: Color): Double = { - val rgb = Seq(c.getRed, c.getGreen, c.getBlue).map{_/255.0}.map{x => if (x <= 0.03928) x / 12.92 else math.pow((x + 0.055) / 1.055, 2.4)} + val rgb = Seq(c.getRed, c.getGreen, c.getBlue).map { _ / 255.0 }.map { x => + if (x <= 0.03928) x / 12.92 else math.pow((x + 0.055) / 1.055, 2.4) + } 0.2126 * rgb(0) + 0.7152 * rgb(1) + 0.0722 * rgb(2) } diff --git a/src/main/scala/gitbucket/core/util/Validations.scala b/src/main/scala/gitbucket/core/util/Validations.scala index be580d7..0402c72 100644 --- a/src/main/scala/gitbucket/core/util/Validations.scala +++ b/src/main/scala/gitbucket/core/util/Validations.scala @@ -8,11 +8,11 @@ /** * Constraint for the identifier such as user name or page name. */ - def identifier: Constraint = new Constraint(){ + def identifier: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(!value.matches("[a-zA-Z0-9\\-_.]+")){ + if (!value.matches("[a-zA-Z0-9\\-_.]+")) { Some(s"${name} contains invalid character.") - } else if(value.startsWith("_") || value.startsWith("-")){ + } else if (value.startsWith("_") || value.startsWith("-")) { Some(s"${name} starts with invalid character.") } else { None @@ -22,24 +22,23 @@ /** * Constraint for the password. */ - def password: Constraint = new Constraint(){ + def password: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(System.getProperty("gitbucket.validate.password") != "false" && !value.matches("[a-zA-Z0-9\\-_.]+")){ + if (System.getProperty("gitbucket.validate.password") != "false" && !value.matches("[a-zA-Z0-9\\-_.]+")) { Some(s"${name} contains invalid character.") } else { None } } - /** * Constraint for the repository identifier. */ - def repository: Constraint = new Constraint(){ + def repository: Constraint = new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] = - if(!value.matches("[a-zA-Z0-9\\-\\+_.]+")){ + if (!value.matches("[a-zA-Z0-9\\-\\+_.]+")) { Some(s"${name} contains invalid character.") - } else if(value.startsWith("_") || value.startsWith("-")){ + } else if (value.startsWith("_") || value.startsWith("-")) { Some(s"${name} starts with invalid character.") } else { None @@ -55,8 +54,9 @@ * ValueType for the java.util.Date property. */ def date(constraints: Constraint*): SingleValueType[java.util.Date] = - new SingleValueType[java.util.Date]((pattern("\\d{4}-\\d{2}-\\d{2}") +: constraints): _*){ - def convert(value: String, messages: Messages): java.util.Date = new java.text.SimpleDateFormat("yyyy-MM-dd").parse(value) + new SingleValueType[java.util.Date]((pattern("\\d{4}-\\d{2}-\\d{2}") +: constraints): _*) { + def convert(value: String, messages: Messages): java.util.Date = + new java.text.SimpleDateFormat("yyyy-MM-dd").parse(value) } } diff --git a/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala b/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala index 8cd64d0..1c233e9 100644 --- a/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala +++ b/src/main/scala/gitbucket/core/view/AvatarImageProvider.scala @@ -11,13 +11,14 @@ * Returns <img> which displays the avatar icon. * Looks up Gravatar if avatar icon has not been configured in user settings. */ - protected def getAvatarImageHtml(userName: String, size: Int, - mailAddress: String = "", tooltip: Boolean = false)(implicit context: Context): Html = { + protected def getAvatarImageHtml(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false)( + implicit context: Context + ): Html = { - val src = if(mailAddress.isEmpty){ + val src = if (mailAddress.isEmpty) { // by user name getAccountByUserName(userName).map { account => - if(account.image.isEmpty && context.settings.gravatar){ + if (account.image.isEmpty && context.settings.gravatar) { s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { s"""${context.path}/${account.userName}/_avatar?${helpers.hashDate(account.updatedDate)}""" @@ -28,13 +29,13 @@ } else { // by mail address getAccountByMailAddress(mailAddress).map { account => - if(account.image.isEmpty && context.settings.gravatar){ + if (account.image.isEmpty && context.settings.gravatar) { s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { s"""${context.path}/${account.userName}/_avatar?${helpers.hashDate(account.updatedDate)}""" } } getOrElse { - if(context.settings.gravatar){ + if (context.settings.gravatar) { s"""https://www.gravatar.com/avatar/${StringUtil.md5(mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" } else { s"""${context.path}/_unknown/_avatar""" @@ -42,10 +43,14 @@ } } - if(tooltip){ - Html(s"""""") + if (tooltip) { + Html( + s"""""" + ) } else { - Html(s"""""") + Html( + s"""""" + ) } } diff --git a/src/main/scala/gitbucket/core/view/LinkConverter.scala b/src/main/scala/gitbucket/core/view/LinkConverter.scala index 00d4e88..5b689d9 100644 --- a/src/main/scala/gitbucket/core/view/LinkConverter.scala +++ b/src/main/scala/gitbucket/core/view/LinkConverter.scala @@ -9,8 +9,10 @@ /** * Creates a link to the issue or the pull request from the issue id. */ - protected def createIssueLink(repository: RepositoryService.RepositoryInfo, issueId: Int)(implicit context: Context): String = { - val userName = repository.repository.userName + protected def createIssueLink(repository: RepositoryService.RepositoryInfo, issueId: Int)( + implicit context: Context + ): String = { + val userName = repository.repository.userName val repositoryName = repository.repository.repositoryName getIssue(userName, repositoryName, issueId.toString) match { @@ -23,78 +25,96 @@ } } - /** * Converts issue id, username and commit id to link in the given text. */ - protected def convertRefsLinks(text: String, repository: RepositoryService.RepositoryInfo, - issueIdPrefix: String = "#", escapeHtml: Boolean = true)(implicit context: Context): String = { + protected def convertRefsLinks( + text: String, + repository: RepositoryService.RepositoryInfo, + issueIdPrefix: String = "#", + escapeHtml: Boolean = true + )(implicit context: Context): String = { // escape HTML tags - val escaped = if(escapeHtml) text.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """) else text + val escaped = + if (escapeHtml) text.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """) + else text escaped - // convert username/project@SHA to link - .replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r){ m => + // convert username/project@SHA to link + .replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r) { m => getAccountByUserName(m.group(2)).map { _ => - s"""${m.group(2)}/${m.group(3)}@${m.group(4).substring(0, 7)}""" + s"""${m.group(2)}/${m.group( + 3 + )}@${m.group(4).substring(0, 7)}""" } } // convert username/project#Num to link - .replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r){ m => - getIssue(m.group(2), m.group(3), m.group(4)) match { - case Some(issue) if (issue.isPullRequest) => - Some(s"""${m.group(2)}/${m.group(3)}#${m.group(4)}""") - case Some(_) => - Some(s"""${m.group(2)}/${m.group(3)}#${m.group(4)}""") - case None => - Some(s"""${m.group(2)}/${m.group(3)}#${m.group(4)}""") - } + .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { + m => + getIssue(m.group(2), m.group(3), m.group(4)) match { + case Some(issue) if (issue.isPullRequest) => + Some(s"""${m.group(2)}/${m.group( + 3 + )}#${m.group(4)}""") + case Some(_) => + Some(s"""${m.group(2)}/${m + .group(3)}#${m.group(4)}""") + case None => + Some(s"""${m.group(2)}/${m.group(3)}#${m.group(4)}""") + } } // convert username@SHA to link - .replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r ) { m => + .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r) { m => getAccountByUserName(m.group(2)).map { _ => - s"""${m.group(2)}@${m.group(3).substring(0, 7)}""" + s"""${m.group(2)}@${m + .group(3) + .substring(0, 7)}""" } } // convert username#Num to link - .replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r ) { m => + .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { m => getIssue(m.group(2), repository.name, m.group(3)) match { - case Some(issue) if(issue.isPullRequest) => - Some(s"""${m.group(2)}#${m.group(3)}""") + case Some(issue) if (issue.isPullRequest) => + Some(s"""${m.group(2)}#${m + .group(3)}""") case Some(_) => - Some(s"""${m.group(2)}#${m.group(3)}""") + Some(s"""${m.group(2)}#${m + .group(3)}""") case None => Some(s"""${m.group(2)}#${m.group(3)}""") } } // convert issue id to link - .replaceBy(("(?<=(^|\\W))(GH-|(? - val prefix = if(m.group(2) == "issue:") "#" else m.group(2) + .replaceBy(("(?<=(^|\\W))(GH-|(? + val prefix = if (m.group(2) == "issue:") "#" else m.group(2) getIssue(repository.owner, repository.name, m.group(3)) match { - case Some(issue) if(issue.isPullRequest) => - Some(s"""${prefix}${m.group(3)}""") + case Some(issue) if (issue.isPullRequest) => + Some(s"""${prefix}${m + .group(3)}""") case Some(_) => - Some(s"""${prefix}${m.group(3)}""") + Some(s"""${prefix}${m + .group(3)}""") case None => Some(s"""${m.group(2)}${m.group(3)}""") } } // convert @username to link - .replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_\\.]+)(?=(\\W|$))".r){ m => + .replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_\\.]+)(?=(\\W|$))".r) { m => getAccountByUserName(m.group(2)).map { _ => s"""@${m.group(2)}""" } } // convert commit id to link - .replaceBy("(?<=(^|[^\\w/@]))([a-f0-9]{40})(?=(\\W|$))".r){ m => - Some(s"""${m.group(2).substring(0, 7)}""") + .replaceBy("(?<=(^|[^\\w/@]))([a-f0-9]{40})(?=(\\W|$))".r) { m => + Some(s"""${m.group(2).substring(0, 7)}""") } } } diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index a4d05b6..4eac137 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -24,24 +24,34 @@ * @param hasWritePermission true if user has writable to ths given repository * @param pages the list of existing Wiki pages */ - def toHtml(markdown: String, - repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, - enableRefsLink: Boolean, - enableAnchor: Boolean, - enableLineBreaks: Boolean, - enableTaskList: Boolean = false, - hasWritePermission: Boolean = false, - pages: List[String] = Nil)(implicit context: Context): String = { + def toHtml( + markdown: String, + repository: RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, + enableRefsLink: Boolean, + enableAnchor: Boolean, + enableLineBreaks: Boolean, + enableTaskList: Boolean = false, + hasWritePermission: Boolean = false, + pages: List[String] = Nil + )(implicit context: Context): String = { // escape task list - val source = if(enableTaskList) escapeTaskList(markdown) else markdown + val source = if (enableTaskList) escapeTaskList(markdown) else markdown val options = new Options() options.setBreaks(enableLineBreaks) - val renderer = new GitBucketMarkedRenderer(options, repository, - enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages) + val renderer = new GitBucketMarkedRenderer( + options, + repository, + enableWikiLink, + enableRefsLink, + enableAnchor, + enableTaskList, + hasWritePermission, + pages + ) Marked.marked(source, options, renderer) } @@ -49,25 +59,31 @@ /** * Extends markedj Renderer for GitBucket */ - class GitBucketMarkedRenderer(options: Options, - repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, - enableRefsLink: Boolean, - enableAnchor: Boolean, - enableTaskList: Boolean, - hasWritePermission: Boolean, - pages: List[String]) - (implicit val context: Context) extends Renderer(options) with LinkConverter with RequestCache { + class GitBucketMarkedRenderer( + options: Options, + repository: RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, + enableRefsLink: Boolean, + enableAnchor: Boolean, + enableTaskList: Boolean, + hasWritePermission: Boolean, + pages: List[String] + )(implicit val context: Context) + extends Renderer(options) + with LinkConverter + with RequestCache { - override def heading(text: String, level: Int, raw: String): String = { + override def heading(text: String, level: Int, raw: String): String = { val id = generateAnchorName(text) val out = new StringBuilder() out.append("") - out.append("") + out.append( + "" + ) out.append("") } else { out.append(">") @@ -79,8 +95,8 @@ } override def code(code: String, lang: String, escaped: Boolean): String = { - "
" +
-        (if(escaped) code else escape(code, true)) + "
" + "
" +
+        (if (escaped) code else escape(code, true)) + "
" } override def list(body: String, ordered: Boolean): String = { @@ -90,7 +106,7 @@ } else { listType = "ul" } - if(body.contains("""class="task-list-item-checkbox"""")){ + if (body.contains("""class="task-list-item-checkbox"""")) { "<" + listType + " class=\"task-list\">\n" + body + "\n" } else { "<" + listType + ">\n" + body + "\n" @@ -98,7 +114,7 @@ } override def listitem(text: String): String = { - if(text.contains("""class="task-list-item-checkbox" """)){ + if (text.contains("""class="task-list-item-checkbox" """)) { "
  • " + text + "
  • \n" } else { "
  • " + text + "
  • \n" @@ -107,9 +123,9 @@ override def text(text: String): String = { // convert commit id and username to link. - val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "#", false) else text + val t1 = if (enableRefsLink) convertRefsLinks(text, repository, "#", false) else text // convert task list to checkbox. - val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 + val t2 = if (enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 // decorate by TextDecorator plugins helpers.decorateHtml(t2, repository) } @@ -123,18 +139,20 @@ } override def nolink(text: String): String = { - if(enableWikiLink && text.startsWith("[[") && text.endsWith("]]")){ + if (enableWikiLink && text.startsWith("[[") && text.endsWith("]]")) { val link = text.replaceAll("(^\\[\\[|\\]\\]$)", "") - val (label, page) = if(link.contains('|')){ + val (label, page) = if (link.contains('|')) { val i = link.indexOf('|') (link.substring(0, i), link.substring(i + 1)) } else { (link, link) } - val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode(page) - if(pages.contains(page)){ + val url = repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + StringUtil.urlEncode( + page + ) + if (pages.contains(page)) { "" + escape(label) + "" } else { "" + escape(label) + "" @@ -145,22 +163,22 @@ } private def fixUrl(url: String, isImage: Boolean = false): String = { - lazy val urlWithRawParam: String = url + (if(isImage && !url.endsWith("?raw=true")) "?raw=true" else "") + lazy val urlWithRawParam: String = url + (if (isImage && !url.endsWith("?raw=true")) "?raw=true" else "") - if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("mailto:") || url.startsWith("/")){ + if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("mailto:") || url.startsWith("/")) { url - } else if(url.startsWith("#")){ + } else if (url.startsWith("#")) { ("#" + generateAnchorName(url.substring(1))) - } else if(!enableWikiLink){ - if(context.currentPath.contains("/blob/")){ + } else if (!enableWikiLink) { + if (context.currentPath.contains("/blob/")) { urlWithRawParam - } else if(context.currentPath.contains("/tree/")){ + } else if (context.currentPath.contains("/tree/")) { val paths = context.currentPath.split("/") - val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch + val branch = if (paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam } else { val paths = context.currentPath.split("/") - val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch + val branch = if (paths.length > 3) paths.last else repository.repository.defaultBranch repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam } } else { @@ -176,15 +194,18 @@ def generateAnchorName(text: String): String = { val normalized = Normalizer.normalize(text.replaceAll("<.*>", "").replaceAll("[\\s]", "-"), Normalizer.Form.NFD) - val encoded = StringUtil.urlEncode(normalized) + val encoded = StringUtil.urlEncode(normalized) encoded.toLowerCase(Locale.ENGLISH) } def convertCheckBox(text: String, hasWritePermission: Boolean): String = { val disabled = if (hasWritePermission) "" else "disabled" - text.replaceAll("task:x:", """") + text + .replaceAll( + "task:x:", + """" + ) .replaceAll("task: :", """") } } - diff --git a/src/main/scala/gitbucket/core/view/Pagination.scala b/src/main/scala/gitbucket/core/view/Pagination.scala index ad91368..c415132 100644 --- a/src/main/scala/gitbucket/core/view/Pagination.scala +++ b/src/main/scala/gitbucket/core/view/Pagination.scala @@ -9,7 +9,7 @@ * @param limit the limit record count per one page * @param width the width (number of cells) of the paginator */ -case class Pagination(page: Int, count: Int, limit: Int, width: Int){ +case class Pagination(page: Int, count: Int, limit: Int, width: Int) { /** * max page number @@ -19,7 +19,7 @@ /** * whether to omit the left side */ - val omitLeft = width / 2 < page + val omitLeft = width / 2 < page /** * whether to omit the right side @@ -30,15 +30,15 @@ * Returns true if given page number is visible. */ def visibleFor(i: Int): Boolean = { - if(i == 1 || i == max){ + if (i == 1 || i == max) { true } else { - val leftRange = page - width / 2 + (if(omitLeft) 2 else 0) - val rightRange = page + width / 2 - (if(omitRight) 2 else 0) + val leftRange = page - width / 2 + (if (omitLeft) 2 else 0) + val rightRange = page + width / 2 - (if (omitRight) 2 else 0) - val fixedRange = if(leftRange < 1){ + val fixedRange = if (leftRange < 1) { (1, rightRange + (leftRange * -1) + 1) - } else if(rightRange > max){ + } else if (rightRange > max) { (leftRange - (rightRange - max), max) } else { (leftRange, rightRange) diff --git a/src/main/scala/gitbucket/core/view/helpers.scala b/src/main/scala/gitbucket/core/view/helpers.scala index 41088aa..c5f1a40 100644 --- a/src/main/scala/gitbucket/core/view/helpers.scala +++ b/src/main/scala/gitbucket/core/view/helpers.scala @@ -51,7 +51,7 @@ val duration = new Date().getTime - date.getTime timeUnits.find(tuple => duration / tuple._1 > 0) match { case Some((_, "month")) => s"on ${new SimpleDateFormat("d MMM", Locale.ENGLISH).format(date)}" - case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}" + case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}" case Some((unitValue, unitString)) => val value = duration / unitValue s"${value} ${unitString}${if (value > 1) "s" else ""} ago" @@ -59,7 +59,6 @@ } } - /** * Format java.util.Date to "yyyy-MM-dd'T'hh:mm:ss'Z'". */ @@ -94,44 +93,56 @@ * If plural is not specified, returns singular + "s" as plural. */ def plural(count: Int, singular: String, plural: String = ""): String = - if(count == 1) singular else if(plural.isEmpty) singular + "s" else plural + if (count == 1) singular else if (plural.isEmpty) singular + "s" else plural /** * Converts Markdown of Wiki pages to HTML. */ - def markdown(markdown: String, - repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, - enableRefsLink: Boolean, - enableLineBreaks: Boolean, - enableAnchor: Boolean = true, - enableTaskList: Boolean = false, - hasWritePermission: Boolean = false, - pages: List[String] = Nil)(implicit context: Context): Html = - Html(Markdown.toHtml( - markdown = markdown, - repository = repository, - enableWikiLink = enableWikiLink, - enableRefsLink = enableRefsLink, - enableAnchor = enableAnchor, - enableLineBreaks = enableLineBreaks, - enableTaskList = enableTaskList, - hasWritePermission = hasWritePermission, - pages = pages - )) + def markdown( + markdown: String, + repository: RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, + enableRefsLink: Boolean, + enableLineBreaks: Boolean, + enableAnchor: Boolean = true, + enableTaskList: Boolean = false, + hasWritePermission: Boolean = false, + pages: List[String] = Nil + )(implicit context: Context): Html = + Html( + Markdown.toHtml( + markdown = markdown, + repository = repository, + enableWikiLink = enableWikiLink, + enableRefsLink = enableRefsLink, + enableAnchor = enableAnchor, + enableLineBreaks = enableLineBreaks, + enableTaskList = enableTaskList, + hasWritePermission = hasWritePermission, + pages = pages + ) + ) /** * Render the given source (only markdown is supported in default) as HTML. * You can test if a file is renderable in this method by [[isRenderable()]]. */ - def renderMarkup(filePath: List[String], fileContent: String, branch: String, - repository: RepositoryService.RepositoryInfo, - enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean)(implicit context: Context): Html = { + def renderMarkup( + filePath: List[String], + fileContent: String, + branch: String, + repository: RepositoryService.RepositoryInfo, + enableWikiLink: Boolean, + enableRefsLink: Boolean, + enableAnchor: Boolean + )(implicit context: Context): Html = { - val fileName = filePath.last.toLowerCase + val fileName = filePath.last.toLowerCase val extension = FileUtil.getExtension(fileName) - val renderer = PluginRegistry().getRenderer(extension) - renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context)) + val renderer = PluginRegistry().getRenderer(extension) + renderer.render( + RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context) + ) } /** @@ -152,7 +163,9 @@ * Returns <img> which displays the avatar icon for the given user name. * This method looks up Gravatar if avatar icon has not been configured in user settings. */ - def avatar(userName: String, size: Int, tooltip: Boolean = false, mailAddress: String = "")(implicit context: Context): Html = + def avatar(userName: String, size: Int, tooltip: Boolean = false, mailAddress: String = "")( + implicit context: Context + ): Html = getAvatarImageHtml(userName, size, mailAddress, tooltip) /** @@ -169,7 +182,7 @@ Html(decorateHtml(convertRefsLinks(value, repository), repository)) def cut(value: String, length: Int): String = - if(value.length > length){ + if (value.length > length) { value.substring(0, length) + "..." } else { value @@ -186,14 +199,36 @@ * Convert link notations in the activity message. */ def activityMessage(message: String)(implicit context: Context): Html = - Html(message - .replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]" , s"""$$1/$$2#$$3""") - .replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]" , s"""$$1/$$2#$$3""") - .replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]" , s"""$$1/$$2""") - .replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]" , (m: Match) => s"""${m.group(3)}""") - .replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]" , (m: Match) => s"""${m.group(3)}""") - .replaceAll("\\[user:([^\\s]+?)\\]" , (m: Match) => user(m.group(1)).body) - .replaceAll("\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]", (m: Match) => s"""${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}""") + Html( + message + .replaceAll( + "\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]", + s"""$$1/$$2#$$3""" + ) + .replaceAll( + "\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]", + s"""$$1/$$2#$$3""" + ) + .replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]", s"""$$1/$$2""") + .replaceAll( + "\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", + (m: Match) => + s"""${m + .group(3)}""" + ) + .replaceAll( + "\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", + (m: Match) => + s"""${m + .group(3)}""" + ) + .replaceAll("\\[user:([^\\s]+?)\\]", (m: Match) => user(m.group(1)).body) + .replaceAll( + "\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]", + (m: Match) => + s"""${m.group(1)}/${m + .group(2)}@${m.group(3).substring(0, 7)}""" + ) ) /** @@ -243,33 +278,38 @@ * Generates the avatar link to the account page. * If user does not exist or disabled, this method returns avatar image without link. */ - def avatarLink(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false, label: Boolean = false) - (implicit context: Context): Html = { + def avatarLink( + userName: String, + size: Int, + mailAddress: String = "", + tooltip: Boolean = false, + label: Boolean = false + )(implicit context: Context): Html = { val avatarHtml = avatar(userName, size, tooltip, mailAddress) - val contentHtml = if(label == true) Html(avatarHtml.body + " " + userName) else avatarHtml + val contentHtml = if (label == true) Html(avatarHtml.body + " " + userName) else avatarHtml userWithContent(userName, mailAddress)(contentHtml) } /** - * Generates the avatar link to the account page. - * If user does not exist or disabled, this method returns avatar image without link. - */ + * Generates the avatar link to the account page. + * If user does not exist or disabled, this method returns avatar image without link. + */ def avatarLink(commit: JGitUtil.CommitInfo, size: Int)(implicit context: Context): Html = userWithContent(commit.authorName, commit.authorEmailAddress)(avatar(commit, size)) - private def userWithContent(userName: String, mailAddress: String = "", styleClass: String = "")(content: Html) - (implicit context: Context): Html = - (if(mailAddress.isEmpty){ - getAccountByUserName(userName) - } else { - getAccountByMailAddress(mailAddress) - }).map { account => + private def userWithContent(userName: String, mailAddress: String = "", styleClass: String = "")( + content: Html + )(implicit context: Context): Html = + (if (mailAddress.isEmpty) { + getAccountByUserName(userName) + } else { + getAccountByMailAddress(mailAddress) + }).map { account => Html(s"""${content}""") } getOrElse content - /** * Test whether the given Date is past date. */ @@ -280,36 +320,36 @@ */ def editorType(fileName: String): String = { fileName.toLowerCase match { - case x if(x.endsWith(".bat")) => "batchfile" - case x if(x.endsWith(".java")) => "java" - case x if(x.endsWith(".scala")) => "scala" - case x if(x.endsWith(".js")) => "javascript" - case x if(x.endsWith(".css")) => "css" - case x if(x.endsWith(".md")) => "markdown" - case x if(x.endsWith(".html")) => "html" - case x if(x.endsWith(".xml")) => "xml" - case x if(x.endsWith(".c")) => "c_cpp" - case x if(x.endsWith(".cpp")) => "c_cpp" - case x if(x.endsWith(".coffee")) => "coffee" - case x if(x.endsWith(".ejs")) => "ejs" - case x if(x.endsWith(".hs")) => "haskell" - case x if(x.endsWith(".json")) => "json" - case x if(x.endsWith(".jsp")) => "jsp" - case x if(x.endsWith(".jsx")) => "jsx" - case x if(x.endsWith(".cl")) => "lisp" - case x if(x.endsWith(".clojure")) => "lisp" - case x if(x.endsWith(".lua")) => "lua" - case x if(x.endsWith(".php")) => "php" - case x if(x.endsWith(".py")) => "python" - case x if(x.endsWith(".rdoc")) => "rdoc" - case x if(x.endsWith(".rhtml")) => "rhtml" - case x if(x.endsWith(".ruby")) => "ruby" - case x if(x.endsWith(".sh")) => "sh" - case x if(x.endsWith(".sql")) => "sql" - case x if(x.endsWith(".tcl")) => "tcl" - case x if(x.endsWith(".vbs")) => "vbscript" - case x if(x.endsWith(".yml")) => "yaml" - case _ => "plain_text" + case x if (x.endsWith(".bat")) => "batchfile" + case x if (x.endsWith(".java")) => "java" + case x if (x.endsWith(".scala")) => "scala" + case x if (x.endsWith(".js")) => "javascript" + case x if (x.endsWith(".css")) => "css" + case x if (x.endsWith(".md")) => "markdown" + case x if (x.endsWith(".html")) => "html" + case x if (x.endsWith(".xml")) => "xml" + case x if (x.endsWith(".c")) => "c_cpp" + case x if (x.endsWith(".cpp")) => "c_cpp" + case x if (x.endsWith(".coffee")) => "coffee" + case x if (x.endsWith(".ejs")) => "ejs" + case x if (x.endsWith(".hs")) => "haskell" + case x if (x.endsWith(".json")) => "json" + case x if (x.endsWith(".jsp")) => "jsp" + case x if (x.endsWith(".jsx")) => "jsx" + case x if (x.endsWith(".cl")) => "lisp" + case x if (x.endsWith(".clojure")) => "lisp" + case x if (x.endsWith(".lua")) => "lua" + case x if (x.endsWith(".php")) => "php" + case x if (x.endsWith(".py")) => "python" + case x if (x.endsWith(".rdoc")) => "rdoc" + case x if (x.endsWith(".rhtml")) => "rhtml" + case x if (x.endsWith(".ruby")) => "ruby" + case x if (x.endsWith(".sh")) => "sh" + case x if (x.endsWith(".sql")) => "sql" + case x if (x.endsWith(".tcl")) => "tcl" + case x if (x.endsWith(".vbs")) => "vbscript" + case x if (x.endsWith(".yml")) => "yaml" + case _ => "plain_text" } } @@ -323,15 +363,20 @@ def mkHtml(separator: scala.xml.Elem) = Html(seq.mkString(separator.toString)) } - def commitStateIcon(state: CommitState) = Html(state match { - case CommitState.PENDING => """""" - case CommitState.SUCCESS => """""" - case CommitState.ERROR => """""" - case CommitState.FAILURE => """""" - }) + def commitStateIcon(state: CommitState) = + Html(state match { + case CommitState.PENDING => + """""" + case CommitState.SUCCESS => + """""" + case CommitState.ERROR => + """""" + case CommitState.FAILURE => + """""" + }) - def commitStateText(state: CommitState, commitId:String) = state match { - case CommitState.PENDING => "Waiting to hear about "+commitId.substring(0,8) + def commitStateText(state: CommitState, commitId: String) = state match { + case CommitState.PENDING => "Waiting to hear about " + commitId.substring(0, 8) case CommitState.SUCCESS => "All is well" case CommitState.ERROR => "Failed" case CommitState.FAILURE => "Failed" @@ -346,18 +391,23 @@ } // This pattern comes from: http://stackoverflow.com/a/4390768/1771641 (extract-url-from-string) - private[this] val urlRegex = """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,13}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r + private[this] val urlRegex = + """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,13}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r def urlLink(text: String): String = { val matches = urlRegex.findAllMatchIn(text).toSeq - val (x, pos) = matches.foldLeft((collection.immutable.Seq.empty[Html], 0)){ case ((x, pos), m) => - val url = m.group(0) - val href = url.replace("\"", """) - (x ++ (Seq( - if(pos < m.start) Some(HtmlFormat.escape(text.substring(pos, m.start))) else None, - Some(Html(s"""${url}""")) - ).flatten), m.end) + val (x, pos) = matches.foldLeft((collection.immutable.Seq.empty[Html], 0)) { + case ((x, pos), m) => + val url = m.group(0) + val href = url.replace("\"", """) + ( + x ++ (Seq( + if (pos < m.start) Some(HtmlFormat.escape(text.substring(pos, m.start))) else None, + Some(Html(s"""${url}""")) + ).flatten), + m.end + ) } // append rest fragment val out = if (pos < text.length) x :+ HtmlFormat.escape(text.substring(pos)) else x @@ -369,38 +419,39 @@ * TextDecorators are applied to only text parts of a given HTML. */ def decorateHtml(html: String, repository: RepositoryInfo)(implicit context: Context): String = { - PluginRegistry().getTextDecorators.foldLeft(html){ case (html, decorator) => - val text = new StringBuilder() - val result = new StringBuilder() - var tag = false + PluginRegistry().getTextDecorators.foldLeft(html) { + case (html, decorator) => + val text = new StringBuilder() + val result = new StringBuilder() + var tag = false - html.foreach { c => - c match { - case '<' if tag == false => { - tag = true - if(text.nonEmpty){ - result.append(decorator.decorate(text.toString, repository)) - text.setLength(0) + html.foreach { c => + c match { + case '<' if tag == false => { + tag = true + if (text.nonEmpty) { + result.append(decorator.decorate(text.toString, repository)) + text.setLength(0) + } + result.append(c) } - result.append(c) - } - case '>' if tag == true => { - tag = false - result.append(c) - } - case _ if tag == false => { - text.append(c) - } - case _ if tag == true => { - result.append(c) + case '>' if tag == true => { + tag = false + result.append(c) + } + case _ if tag == false => { + text.append(c) + } + case _ if tag == true => { + result.append(c) + } } } - } - if(text.nonEmpty){ - result.append(decorator.decorate(text.toString, repository)) - } + if (text.nonEmpty) { + result.append(decorator.decorate(text.toString, repository)) + } - result.toString + result.toString } }