diff --git a/README.md b/README.md index eb3b2601e..b1ab25ea1 100644 --- a/README.md +++ b/README.md @@ -24,35 +24,30 @@ Installation -------- GitBucket requires **Java8**. You have to install beforehand when it's not installed. -1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases). -2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher. -3. Access **http://[hostname]:[port]/gitbucket/** using your web browser and logged-in with **root** / **root**. +1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`. +2. Access `http://[hostname]:8080/` and logged in with **root** / **root**. -or you can start GitBucket by `java -jar gitbucket.war` without servlet container. In this case, GitBucket URL is **http://[hostname]:8080/**. You can specify following options. +You can specify following options: -- --port=[NUMBER] -- --prefix=[CONTEXTPATH] -- --host=[HOSTNAME] -- --gitbucket.home=[DATA_DIR] +- `--port=[NUMBER]` +- `--prefix=[CONTEXTPATH]` +- `--host=[HOSTNAME]` +- `--gitbucket.home=[DATA_DIR]` -To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk. +Of course, you can also deploy gitbucket.war to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc) About installation on Mac or Windows Server (with IIS), configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki). +To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. All GitBucket data is stored in `HOME/.gitbucket` in default. So if you want to back up GitBucket data, copy this directory to the other disk. + Plug-ins -------- -GitBucket has the plug-in system to extend GitBucket from outside of GitBucket. Some plug-ins are available now: +GitBucket has the plug-in system to extend GitBucket from outside of GitBucket. We are providing some official plug-ins: - [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) -- [gitbucket-announce-plugin](https://github.com/gitbucket-plugins/gitbucket-announce-plugin) -- [gitbucket-h2-backup-plugin](https://github.com/gitbucket-plugins/gitbucket-h2-backup-plugin) -- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin) -- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin) -- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin) -- [gitbucket-network-plugin](https://github.com/mrkm4ntr/gitbucket-network-plugin) - [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin) -You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/). +You can find more plugins made by community at [gitbucket community plugins](http://gitbucket-plugins.github.io/). Support -------- @@ -65,6 +60,24 @@ Support Release Notes ------------- +## 4.8 - 23 Dec 2016 +- Search for repository names from the global header +- Filter repositories on the sidebar of the dashboard +- Search issues and wiki +- Keep pull request comments after new commits are pushed +- New web API to get a single issue +- Performance improvement for the repository viewer + +### 4.7.1 - 28 Nov 2016 +- Bug fix: group repositories are not shown in the your repositories list on the sidebar +- Small performance improvement of the dashboard + +### 4.7 - 26 Nov 2016 +- New permission system +- Dropdown filter for issue labels, milestones and assignees +- Keep sidebar folding status +- Link from milestone label to the issue list + ### 4.6 - 29 Oct 2016 - Add disable option for forking - Add History button to wiki page @@ -101,7 +114,7 @@ Release Notes - [List group repositories](https://developer.github.com/v3/repos/#list-organization-repositories) - Add new extension points - `assetsMapping` : Supplies resources in plugin classpath as web assets - - `suggestionProvider` : Provides suggestion in the Markdown editing textarea + - `suggestionProvider` : Provides suggestion in the Markdown editing textarea - `textDecorator` : Decorate text nodes in HTML which is converted from Markdown ### 4.2.1 - 3 Jul 2016 diff --git a/build.sbt b/build.sbt index 4accb94d6..5ca085443 100644 --- a/build.sbt +++ b/build.sbt @@ -1,7 +1,7 @@ val Organization = "io.github.gitbucket" val Name = "gitbucket" -val GitBucketVersion = "4.6.0" -val ScalatraVersion = "2.5.0-RC1" +val GitBucketVersion = "4.8" +val ScalatraVersion = "2.5.0" val JettyVersion = "9.3.9.v20160517" lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin) @@ -15,6 +15,7 @@ scalaVersion := "2.12.0" // dependency settings resolvers ++= Seq( Classpaths.typesafeReleases, + Resolver.jcenterRepo, "amateras" at "http://amateras.sourceforge.jp/mvn/", "sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/", "amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/" @@ -46,7 +47,9 @@ libraryDependencies ++= Seq( "com.typesafe" % "config" % "1.3.0", "com.typesafe.akka" %% "akka-actor" % "2.4.12", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0", - "com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"), + "com.github.bkromhout" % "java-diff-utils" % "2.1.1", + "org.cache2k" % "cache2k-all" % "1.0.0.CR1", + "com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"), "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "junit" % "junit" % "4.12" % "test", diff --git a/src/main/resources/update/gitbucket-core_4.7.xml b/src/main/resources/update/gitbucket-core_4.7.xml index a1288002e..4eb2f0357 100644 --- a/src/main/resources/update/gitbucket-core_4.7.xml +++ b/src/main/resources/update/gitbucket-core_4.7.xml @@ -1,7 +1,7 @@ - + diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index c04a68787..3fadd2d7b 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -22,5 +22,7 @@ object GitBucketCoreModule extends Module("gitbucket-core", 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") ) diff --git a/src/main/scala/gitbucket/core/api/ApiPullRequest.scala b/src/main/scala/gitbucket/core/api/ApiPullRequest.scala index 52bb9a73a..3ae6ac03d 100644 --- a/src/main/scala/gitbucket/core/api/ApiPullRequest.scala +++ b/src/main/scala/gitbucket/core/api/ApiPullRequest.scala @@ -1,7 +1,6 @@ package gitbucket.core.api -import gitbucket.core.model.{Issue, PullRequest} - +import gitbucket.core.model.{Account, Issue, IssueComment, PullRequest} import java.util.Date @@ -15,6 +14,9 @@ case class ApiPullRequest( head: ApiPullRequest.Commit, base: ApiPullRequest.Commit, mergeable: Option[Boolean], + merged: Boolean, + merged_at: Option[Date], + merged_by: Option[ApiUser], title: String, body: String, user: ApiUser) { @@ -31,7 +33,14 @@ case class ApiPullRequest( } object ApiPullRequest{ - def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest = + def apply( + issue: Issue, + pullRequest: PullRequest, + headRepo: ApiRepository, + baseRepo: ApiRepository, + user: ApiUser, + mergedComment: Option[(IssueComment, Account)] + ): ApiPullRequest = ApiPullRequest( number = issue.issueId, updated_at = issue.updatedDate, @@ -45,6 +54,9 @@ object ApiPullRequest{ 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 diff --git a/src/main/scala/gitbucket/core/api/CreateARepository.scala b/src/main/scala/gitbucket/core/api/CreateARepository.scala index 7085559b3..7247c9feb 100644 --- a/src/main/scala/gitbucket/core/api/CreateARepository.scala +++ b/src/main/scala/gitbucket/core/api/CreateARepository.scala @@ -11,7 +11,7 @@ case class CreateARepository( auto_init: Boolean = false ) { def isValid: Boolean = { - name.length<=40 && + name.length <= 100 && name.matches("[a-zA-Z0-9\\-\\+_.]+") && !name.startsWith("_") && !name.startsWith("-") diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index eddd9411e..d118fa4ae 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -22,6 +22,7 @@ class ApiController extends ApiControllerBase with IssuesService with LabelsService with PullRequestService + with CommitsService with CommitStatusService with RepositoryCreationService with HandleCommentService @@ -102,7 +103,7 @@ trait ApiControllerBase extends ControllerBase { defaultBranch = repository.repository.defaultBranch, origin = repository.repository.originUserName.isEmpty ).map { br => - ApiBranchForList(br.name, ApiBranchCommit(br.commitId)) + ApiBranchForList(br.name, ApiBranchCommit(br.commitId)) }) }) @@ -132,9 +133,12 @@ trait ApiControllerBase extends ControllerBase { 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" => + case "application/vnd.github.v3.raw" => { + contentType = "application/vnd.github.v3.raw" content - case "application/vnd.github.v3.html" if isRenderable(f.name) => + } + case "application/vnd.github.v3.html" if isRenderable(f.name) => { + contentType = "application/vnd.github.v3.html" content.map(c => List( "
", "
", @@ -142,7 +146,9 @@ trait ApiControllerBase extends ControllerBase { "
", "
" ).mkString ) - case "application/vnd.github.v3.html" => + } + case "application/vnd.github.v3.html" => { + contentType = "application/vnd.github.v3.html" content.map(c => List( "
", "
", "
",
@@ -150,6 +156,7 @@ trait ApiControllerBase extends ControllerBase {
                   "
", "
", "
" ).mkString ) + } case _ => Some(JsonFormat(ApiContents(f, content))) } @@ -254,7 +261,7 @@ trait ApiControllerBase extends ControllerBase { patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository => import gitbucket.core.api._ (for{ - branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined + branch <- params.get("branch") if repository.branchList.find(_ == branch).isDefined protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection) } yield { if(protection.enabled){ @@ -276,13 +283,26 @@ trait ApiControllerBase extends ControllerBase { org.scalatra.NotFound(ApiError("Rate limiting is not enabled.")) } + /** + * 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 + issue <- getIssue(repository.owner, repository.name, issueId.toString) + openedUser <- getAccountByUserName(issue.openedUserName) + } yield { + JsonFormat(ApiIssue(issue, RepositoryName(repository), ApiUser(openedUser))) + }) getOrElse NotFound() + }) + /** * 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.toInt) + issueId <- params("id").toIntOpt + comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt) } yield { JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) }) }) getOrElse NotFound() @@ -363,12 +383,14 @@ trait ApiControllerBase extends ControllerBase { updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color) JsonFormat(ApiLabel( getLabel(repository.owner, repository.name, label.labelId).get, - RepositoryName(repository))) + 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"))) + Some("https://developer.github.com/v3/issues/labels/#create-a-label") + )) } } getOrElse NotFound() } @@ -407,11 +429,12 @@ trait ApiControllerBase extends ControllerBase { JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) => ApiPullRequest( - issue, - pullRequest, - ApiRepository(headRepo, ApiUser(headOwner)), - ApiRepository(repository, ApiUser(baseOwner)), - ApiUser(issueUser) + issue = issue, + pullRequest = pullRequest, + headRepo = ApiRepository(headRepo, ApiUser(headOwner)), + baseRepo = ApiRepository(repository, ApiUser(baseOwner)), + user = ApiUser(issueUser), + mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) ) }) }) @@ -421,20 +444,22 @@ trait ApiControllerBase extends ControllerBase { */ get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository => (for{ - issueId <- params("id").toIntOpt + issueId <- params("id").toIntOpt (issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) - users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set()) + 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) headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) } yield { JsonFormat(ApiPullRequest( - issue, - pullRequest, - ApiRepository(headRepo, ApiUser(headOwner)), - ApiRepository(repository, ApiUser(baseOwner)), - ApiUser(issueUser))) + issue = issue, + pullRequest = pullRequest, + headRepo = ApiRepository(headRepo, ApiUser(headOwner)), + baseRepo = ApiRepository(repository, ApiUser(baseOwner)), + user = ApiUser(issueUser), + mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) + )) }) getOrElse NotFound() }) @@ -450,7 +475,7 @@ trait ApiControllerBase extends ControllerBase { 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 + val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map { c => ApiCommitListItem(new CommitInfo(c), repoFullName) }.toList JsonFormat(commits) } } @@ -469,14 +494,14 @@ trait ApiControllerBase extends ControllerBase { */ 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) + 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() @@ -514,9 +539,9 @@ trait ApiControllerBase extends ControllerBase { */ get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository => (for{ - ref <- params.get("ref") + 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))) @@ -524,7 +549,7 @@ trait ApiControllerBase extends ControllerBase { }) private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean = - hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName + hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName } diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index ef1a634b0..eeedd1c09 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -191,6 +191,7 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: case agent if agent.contains("Win") => "windows" case _ => null } + val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null /** * Get object from cache. diff --git a/src/main/scala/gitbucket/core/controller/DashboardController.scala b/src/main/scala/gitbucket/core/controller/DashboardController.scala index 1227f24f5..81e3085d7 100644 --- a/src/main/scala/gitbucket/core/controller/DashboardController.scala +++ b/src/main/scala/gitbucket/core/controller/DashboardController.scala @@ -1,13 +1,13 @@ package gitbucket.core.controller import gitbucket.core.dashboard.html -import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService} -import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator} +import gitbucket.core.service._ +import gitbucket.core.util.{Keys, UsersAuthenticator} import gitbucket.core.util.Implicits._ import gitbucket.core.service.IssuesService._ class DashboardController extends DashboardControllerBase - with IssuesService with PullRequestService with RepositoryService with AccountService + with IssuesService with PullRequestService with RepositoryService with AccountService with CommitsService with UsersAuthenticator trait DashboardControllerBase extends ControllerBase { @@ -76,7 +76,7 @@ trait DashboardControllerBase extends ControllerBase { }, filter, getGroupNames(userName), - getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true), + Nil, getUserRepositories(userName, withoutPhysicalInfo = true)) } @@ -101,7 +101,7 @@ trait DashboardControllerBase extends ControllerBase { }, filter, getGroupNames(userName), - getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true), + Nil, getUserRepositories(userName, withoutPhysicalInfo = true)) } diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index 0d69ebe21..6b4db67f2 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -5,9 +5,9 @@ import gitbucket.core.model.Account import gitbucket.core.service._ import gitbucket.core.util.Implicits._ import gitbucket.core.util.ControlUtil._ -import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil} - +import gitbucket.core.util.{Keys, LDAPUtil, ReferrerAuthenticator, StringUtil, UsersAuthenticator} import io.github.gitbucket.scalatra.forms._ +import org.scalatra.Ok class IndexController extends IndexControllerBase @@ -36,23 +36,11 @@ trait IndexControllerBase extends ControllerBase { get("/"){ - val loginAccount = context.loginAccount - if(loginAccount.isEmpty) { - gitbucket.core.html.index(getRecentActivities(), - getVisibleRepositories(loginAccount, withoutPhysicalInfo = true), - loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil) - ) - } else { - val loginUserName = loginAccount.get.userName - val loginUserGroups = getGroupsByUserName(loginUserName) - var visibleOwnerSet : Set[String] = Set(loginUserName) - - visibleOwnerSet ++= loginUserGroups - - gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet), - getVisibleRepositories(loginAccount, withoutPhysicalInfo = true), - loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil) - ) + 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)) + }.getOrElse { + gitbucket.core.html.index(getRecentActivities(), getVisibleRepositories(None, withoutPhysicalInfo = true), Nil) } } @@ -81,6 +69,15 @@ trait IndexControllerBase extends ControllerBase { xml.feed(getRecentActivities()) } + get("/sidebar-collapse"){ + if(params("collapse") == "true"){ + session.setAttribute("sidebar-collapse", "true") + } else { + session.setAttribute("sidebar-collapse", null) + } + Ok() + } + /** * Set account information into HttpSession and redirect. */ @@ -133,15 +130,10 @@ trait IndexControllerBase extends ControllerBase { } getOrElse "" }) - // TODO Move to RepositoryViwerController? - post("/search", searchForm){ form => - redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}") - } - // TODO Move to RepositoryViwerController? get("/:owner/:repository/search")(referrersOnly { repository => - defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) => - val page = try { + 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 { @@ -150,23 +142,31 @@ trait IndexControllerBase extends ControllerBase { target.toLowerCase match { case "issue" => gitbucket.core.search.html.issues( - countFiles(repository.owner, repository.name, query), - searchIssues(repository.owner, repository.name, query), - countWikiPages(repository.owner, repository.name, query), + if(query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil, query, page, repository) case "wiki" => gitbucket.core.search.html.wiki( - countFiles(repository.owner, repository.name, query), - countIssues(repository.owner, repository.name, query), - searchWikiPages(repository.owner, repository.name, query), + if(query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil, query, page, repository) case _ => gitbucket.core.search.html.code( - searchFiles(repository.owner, repository.name, query), - countIssues(repository.owner, repository.name, query), - countWikiPages(repository.owner, repository.name, query), + if(query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil, query, page, repository) } } }) + + get("/search"){ + val query = params.getOrElse("query", "").trim.toLowerCase + val visibleRepositories = getVisibleRepositories(context.loginAccount, None) + 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) + } + } + } diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 90aca139e..4222dba92 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -14,12 +14,33 @@ import org.scalatra.Ok class IssuesController extends IssuesControllerBase - with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService - with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with WebHookIssueCommentService + with IssuesService + with RepositoryService + with AccountService + with LabelsService + with MilestonesService + with ActivityService + with HandleCommentService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator + with PullRequestService + with WebHookIssueCommentService + with CommitsService trait IssuesControllerBase extends ControllerBase { - self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService - with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with WebHookIssueCommentService => + self: IssuesService + with RepositoryService + with AccountService + with LabelsService + with MilestonesService + with ActivityService + with HandleCommentService + with ReadableUsersAuthenticator + with ReferrerAuthenticator + with WritableUsersAuthenticator + with PullRequestService + with WebHookIssueCommentService => case class IssueCreateForm(title: String, content: Option[String], assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String]) @@ -84,7 +105,7 @@ trait IssuesControllerBase extends ControllerBase { getAssignableUserNames(owner, name), getMilestones(owner, name), getLabels(owner, name), - hasWritePermission(owner, name, context.loginAccount), + isManageable(repository), repository) } } else Unauthorized() @@ -386,7 +407,7 @@ trait IssuesControllerBase extends ControllerBase { * Tests whether an logged-in user can manage issues. */ private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = { - hasWritePermission(repository.owner, repository.name, context.loginAccount) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) } /** @@ -394,8 +415,9 @@ trait IssuesControllerBase extends ControllerBase { */ private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { repository.repository.options.issuesOption match { - case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) - case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) + case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined + case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount) + case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount) case "DISABLE" => false } } @@ -404,7 +426,7 @@ trait IssuesControllerBase extends ControllerBase { * 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 = { - hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName + 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 ab658eb11..08c0aaa02 100644 --- a/src/main/scala/gitbucket/core/controller/LabelsController.scala +++ b/src/main/scala/gitbucket/core/controller/LabelsController.scala @@ -29,7 +29,7 @@ trait LabelsControllerBase extends ControllerBase { getLabels(repository.owner, repository.name), countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasWritePermission(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) }) ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository => @@ -43,7 +43,7 @@ trait LabelsControllerBase extends ControllerBase { // TODO futility countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasWritePermission(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) }) ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository => @@ -59,7 +59,7 @@ trait LabelsControllerBase extends ControllerBase { // TODO futility countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), repository, - hasWritePermission(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) }) ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository => diff --git a/src/main/scala/gitbucket/core/controller/MilestonesController.scala b/src/main/scala/gitbucket/core/controller/MilestonesController.scala index f75ea607e..de81c7330 100644 --- a/src/main/scala/gitbucket/core/controller/MilestonesController.scala +++ b/src/main/scala/gitbucket/core/controller/MilestonesController.scala @@ -27,7 +27,7 @@ trait MilestonesControllerBase extends ControllerBase { params.getOrElse("state", "open"), getMilestonesWithIssueCount(repository.owner, repository.name), repository, - hasWritePermission(repository.owner, repository.name, context.loginAccount)) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) }) get("/:owner/:repository/issues/milestones/new")(writableUsersOnly { diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 548da4208..671d9f156 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -11,10 +11,7 @@ import gitbucket.core.service._ import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.Directory._ import gitbucket.core.util.Implicits._ -import gitbucket.core.util.JGitUtil._ import gitbucket.core.util._ -import gitbucket.core.view -import gitbucket.core.view.helpers import io.github.gitbucket.scalatra.forms._ import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.PersonIdent @@ -115,7 +112,7 @@ trait PullRequestsControllerBase extends ControllerBase { val hasConflict = LockUtil.lock(s"${owner}/${name}"){ checkConflict(owner, name, pullreq.branch, issueId) } - val hasMergePermission = hasWritePermission(owner, name, context.loginAccount) + val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount) val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch) val mergeStatus = PullRequestService.MergeStatus( hasConflict = hasConflict, @@ -125,7 +122,7 @@ trait PullRequestsControllerBase extends ControllerBase { needStatusCheck = context.loginAccount.map{ u => branchProtection.needStatusCheck(u.userName) }.getOrElse(true), - hasUpdatePermission = hasWritePermission(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) && + hasUpdatePermission = hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) && context.loginAccount.map{ u => !getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch).needStatusCheck(u.userName) }.getOrElse(false), @@ -156,24 +153,24 @@ trait PullRequestsControllerBase extends ControllerBase { } getOrElse NotFound() }) - post("/:owner/:repository/pull/:id/update_branch")(referrersOnly { baseRepository => + post("/:owner/:repository/pull/:id/update_branch")(writableUsersOnly { baseRepository => (for { issueId <- params("id").toIntOpt loginAccount <- context.loginAccount (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId) owner = pullreq.requestUserName name = pullreq.requestRepositoryName - if hasWritePermission(owner, name, context.loginAccount) + 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)){ flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check." } else { - val repository = getRepository(owner, name).get LockUtil.lock(s"${owner}/${name}"){ val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){ pullreq.branch - }else{ + } else { s"${pullreq.userName}:${pullreq.branch}" } val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet @@ -187,11 +184,10 @@ trait PullRequestsControllerBase extends ControllerBase { 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 => + commits.foreach { commit => if(!existIds.contains(commit.id)){ createIssueComment(owner, name, commit) } @@ -220,8 +216,9 @@ trait PullRequestsControllerBase extends ControllerBase { flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}" } } - redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") } + redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") + }) getOrElse NotFound() }) @@ -374,7 +371,7 @@ trait PullRequestsControllerBase extends ControllerBase { forkedRepository, originRepository, forkedRepository, - hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount), + hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount), getAssignableUserNames(originRepository.owner, originRepository.name), getMilestones(originRepository.owner, originRepository.name), getLabels(originRepository.owner, originRepository.name) @@ -389,7 +386,7 @@ trait PullRequestsControllerBase extends ControllerBase { }) getOrElse NotFound() }) - ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(writableUsersOnly { forkedRepository => + ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository => val Seq(origin, forked) = multiParams("splat") val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner) val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner) @@ -498,26 +495,6 @@ trait PullRequestsControllerBase extends ControllerBase { (defaultOwner, value) } - private 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) => - 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 diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true) - - (commits, diffs) - } - private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = defining(repository.owner, repository.name){ case (owner, repoName) => val page = IssueSearchCondition.page(request) @@ -544,7 +521,7 @@ trait PullRequestsControllerBase extends ControllerBase { * Tests whether an logged-in user can manage pull requests. */ private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = { - hasWritePermission(repository.owner, repository.name, context.loginAccount) + hasDeveloperRole(repository.owner, repository.name, context.loginAccount) } /** @@ -552,8 +529,9 @@ trait PullRequestsControllerBase extends ControllerBase { */ private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { repository.repository.options.issuesOption match { - case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) - case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) + case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined + case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount) + case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount) case "DISABLE" => false } } diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index 5a2ca91a7..b0b30f5d8 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -39,7 +39,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { ) val optionsForm = mapping( - "repositoryName" -> trim(label("Repository Name" , text(required, maxlength(40), identifier, renameRepositoryName))), + "repositoryName" -> trim(label("Repository Name" , text(required, maxlength(100), identifier, renameRepositoryName))), "description" -> trim(label("Description" , optional(text()))), "isPrivate" -> trim(label("Repository Type" , boolean())), "issuesOption" -> trim(label("Issues Option" , text(required, featureOption))), @@ -179,8 +179,8 @@ trait RepositorySettingsControllerBase extends ControllerBase { val collaborators = params("collaborators") removeCollaborators(repository.owner, repository.name) collaborators.split(",").withFilter(_.nonEmpty).map { collaborator => - val userName :: permission :: Nil = collaborator.split(":").toList - addCollaborator(repository.owner, repository.name, userName, permission) + val userName :: role :: Nil = collaborator.split(":").toList + addCollaborator(repository.owner, repository.name, userName, role) } redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") }) @@ -238,7 +238,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token) val dummyPayload = { val ownerAccount = getAccountByUserName(repository.owner).get - val commits = if(repository.commitCount == 0) List.empty else git.log + 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 @@ -416,7 +416,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { */ private def featureOption: Constraint = new Constraint(){ override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = - if(Seq("DISABLE", "PRIVATE", "PUBLIC").contains(value)) None else Some("Option is invalid.") + if(Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.") } diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index ad8b091b4..d08c0feb9 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -102,16 +102,28 @@ trait RepositoryViewerControllerBase extends ControllerBase { */ post("/:owner/:repository/_preview")(referrersOnly { repository => contentType = "text/html" - helpers.markdown( - markdown = params("content"), - repository = repository, - enableWikiLink = params("enableWikiLink").toBoolean, - enableRefsLink = params("enableRefsLink").toBoolean, - enableLineBreaks = params("enableLineBreaks").toBoolean, - enableTaskList = params("enableTaskList").toBoolean, - enableAnchor = false, - hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount) - ) + val filename = params.get("filename") + filename match { + case Some(f) => helpers.renderMarkup( + filePath = List(f), + fileContent = params("content"), + branch = "master", + repository = repository, + enableWikiLink = params("enableWikiLink").toBoolean, + enableRefsLink = params("enableRefsLink").toBoolean, + enableAnchor = false + ) + case None => helpers.markdown( + markdown = params("content"), + repository = repository, + enableWikiLink = params("enableWikiLink").toBoolean, + enableRefsLink = params("enableRefsLink").toBoolean, + enableLineBreaks = params("enableLineBreaks").toBoolean, + enableTaskList = params("enableTaskList").toBoolean, + enableAnchor = false, + hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount) + ) + } }) /** @@ -151,7 +163,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { 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, hasWritePermission(repository.owner, repository.name, context.loginAccount)) + }, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) case Left(_) => NotFound() } } @@ -275,7 +287,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId), new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)), - hasWritePermission(repository.owner, repository.name, context.loginAccount), + hasDeveloperRole(repository.owner, repository.name, context.loginAccount), request.paths(2) == "blame") } } getOrElse NotFound() @@ -328,8 +340,8 @@ trait RepositoryViewerControllerBase extends ControllerBase { html.commit(id, new JGitUtil.CommitInfo(revCommit), JGitUtil.getBranchesOfCommit(git, revCommit.getName), JGitUtil.getTagsOfCommit(git, revCommit.getName), - getCommitComments(repository.owner, repository.name, id, false), - repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount)) + getCommitComments(repository.owner, repository.name, id, true), + repository, diffs, oldCommitId, hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) } } } @@ -358,7 +370,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { html.commentform( commitId = id, fileName, oldLineNumber, newLineNumber, issueId, - hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount), + hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository = repository ) }) @@ -374,7 +386,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get) case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content) } - helper.html.commitcomment(comment, hasWritePermission(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 => @@ -393,7 +405,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { enableRefsLink = true, enableAnchor = true, enableLineBreaks = true, - hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName) + hasWritePermission = true ) )) } @@ -437,7 +449,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { .map(br => (br, getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId), protectedBranches.contains(br.name))) .reverse - html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository) + html.branches(branches, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository) }) /** @@ -546,10 +558,10 @@ trait RepositoryViewerControllerBase extends ControllerBase { * @return HTML of the file list */ private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = { - if(repository.commitCount == 0){ - html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount)) - } else { - using(Git.open(getRepositoryDir(repository.owner, repository.name))){ 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 => @@ -569,9 +581,14 @@ trait RepositoryViewerControllerBase extends ControllerBase { html.files(revision, repository, if(path == ".") Nil else path.split("/").toList, // current path new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit - files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount), + JGitUtil.getCommitCount(repository.owner, repository.name, revision), + files, + readme, + hasDeveloperRole(repository.owner, repository.name, context.loginAccount), getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch), - flash.get("info"), flash.get("error")) + flash.get("info"), + flash.get("error") + ) } } getOrElse NotFound() } @@ -691,7 +708,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { } private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean = - hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName + hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = { e.printStackTrace() diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index 64d0b27fb..f46b4be8d 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -242,9 +242,9 @@ trait WikiControllerBase extends ControllerBase { private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { repository.repository.options.wikiOption match { -// case "ALL" => repository.repository.isPrivate == false || hasReadPermission(repository.owner, repository.name, context.loginAccount) - case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) - case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) + case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined + case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount) + case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount) case "DISABLE" => false } } diff --git a/src/main/scala/gitbucket/core/model/Collaborator.scala b/src/main/scala/gitbucket/core/model/Collaborator.scala index 08381651c..3cee46be3 100644 --- a/src/main/scala/gitbucket/core/model/Collaborator.scala +++ b/src/main/scala/gitbucket/core/model/Collaborator.scala @@ -7,8 +7,8 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile => class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate { val collaboratorName = column[String]("COLLABORATOR_NAME") - val permission = column[String]("PERMISSION") - def * = (userName, repositoryName, collaboratorName, permission) <> (Collaborator.tupled, Collaborator.unapply) + val role = column[String]("ROLE") + def * = (userName, repositoryName, collaboratorName, role) <> (Collaborator.tupled, Collaborator.unapply) def byPrimaryKey(owner: String, repository: String, collaborator: String) = byRepository(owner, repository) && (collaboratorName === collaborator.bind) @@ -19,15 +19,15 @@ case class Collaborator( userName: String, repositoryName: String, collaboratorName: String, - permission: String + role: String ) -sealed abstract class Permission(val name: String) +sealed abstract class Role(val name: String) -object Permission { - object ADMIN extends Permission("ADMIN") - object WRITE extends Permission("WRITE") - object READ extends Permission("READ") +object Role { + object ADMIN extends Role("ADMIN") + object DEVELOPER extends Role("DEVELOPER") + object GUEST extends Role("GUEST") // val values: Vector[Permission] = Vector(ADMIN, WRITE, READ) // diff --git a/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala b/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala index 95b9bbc6c..3aafa11d3 100644 --- a/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala +++ b/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala @@ -23,5 +23,5 @@ class UserNameSuggestionProvider extends SuggestionProvider { override def values(repository: RepositoryInfo): Seq[String] = Nil override def template(implicit context: Context): String = "'@' + value" override def additionalScript(implicit context: Context): String = - s"""$$.get('${context.path}/_user/proposals', { query: '' }, function (data) { user = data.options; });""" + s"""$$.get('${context.path}/_user/proposals', { query: '', user: true, group: false }, function (data) { user = data.options; });""" } \ No newline at end of file diff --git a/src/main/scala/gitbucket/core/service/CommitsService.scala b/src/main/scala/gitbucket/core/service/CommitsService.scala index b9aaea073..747f3d204 100644 --- a/src/main/scala/gitbucket/core/service/CommitsService.scala +++ b/src/main/scala/gitbucket/core/service/CommitsService.scala @@ -37,6 +37,12 @@ trait CommitsService { updatedDate = currentDate, issueId = issueId) + 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) + def updateCommitComment(commentId: Int, content: String)(implicit s: Session) = { CommitComments .filter (_.byPrimaryKey(commentId)) diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index 69d6f2374..97bbc6c59 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -10,6 +10,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.dateColumnType + trait IssuesService { self: AccountService with RepositoryService => import IssuesService._ @@ -31,6 +32,10 @@ trait IssuesService { .map{ case ((t1, t2), t3) => (t1, t2, t3) } .list + def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = { + getCommentsForApi(owner, repository, issueId).collectFirst { case (comment, account, _) if comment.action == "merged" => (comment, account) } + } + def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) = if (commentId forall (_.isDigit)) IssueComments filter { t => @@ -388,8 +393,8 @@ trait IssuesService { } def getAssignableUserNames(owner: String, repository: String)(implicit s: Session): List[String] = { - (getCollaboratorUserNames(owner, repository, Seq(Permission.ADMIN, Permission.WRITE)) ::: - (if (getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).sorted + (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)) ::: + (if (getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).distinct.sorted } } diff --git a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala index 3552762f1..6b9e98d0a 100644 --- a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala +++ b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala @@ -87,7 +87,7 @@ object ProtectedBranchService { def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)(implicit session: Session): Option[String] = { if(enabled){ command.getType() match { - case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards => + 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) => unSuccessedContexts(command.getNewId.name) match { @@ -99,7 +99,7 @@ object ProtectedBranchService { Some("Cannot delete a protected branch") case _ => None } - }else{ + } else { None } } diff --git a/src/main/scala/gitbucket/core/service/PullRequestService.scala b/src/main/scala/gitbucket/core/service/PullRequestService.scala index e5b3e23f3..d9acb3cf5 100644 --- a/src/main/scala/gitbucket/core/service/PullRequestService.scala +++ b/src/main/scala/gitbucket/core/service/PullRequestService.scala @@ -1,12 +1,24 @@ package gitbucket.core.service import gitbucket.core.model.{Issue, PullRequest, CommitStatus, CommitState} -import gitbucket.core.util.JGitUtil import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile.blockingApi._ +import difflib.{Delta, DiffUtils} +import gitbucket.core.model.{Session => _, _} +import gitbucket.core.model.Profile._ +import gitbucket.core.util.ControlUtil._ +import gitbucket.core.util.Directory._ +import gitbucket.core.util.Implicits._ +import gitbucket.core.util.JGitUtil +import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo} +import gitbucket.core.view +import gitbucket.core.view.helpers +import org.eclipse.jgit.api.Git +import scala.collection.JavaConverters._ -trait PullRequestService { self: IssuesService => + +trait PullRequestService { self: IssuesService with CommitsService => import PullRequestService._ def getPullRequest(owner: String, repository: String, issueId: Int) @@ -111,10 +123,26 @@ trait PullRequestService { self: IssuesService => def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit = getPullRequestsByRequest(owner, repository, branch, 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) + + // 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)) + } + .groupBy { case (file, _, _) => file } + .map { case (file, comments) => file -> + comments.map { case (_, commentId, lineNumber) => (commentId, lineNumber) } + } + + // Update comments position + 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) } } @@ -138,6 +166,78 @@ trait PullRequestService { self: IssuesService => .firstOption } } + + 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)) + } + case _ => + 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 + } + } + 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)) + }} + } + } + } + + 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) => + 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 diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true) + + (commits, diffs) + } + } object PullRequestService { diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 1b1fcada8..dfb0f713e 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -2,7 +2,7 @@ package gitbucket.core.service import gitbucket.core.controller.Context import gitbucket.core.util.JGitUtil -import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account, Permission} +import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account, Role} import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile.blockingApi._ @@ -230,7 +230,7 @@ trait RepositoryService { self: AccountService => } /** - * Returns the repositories without private repository that user does not have access right. + * Returns the repositories except private repository that user does not have access right. * Include public repository, private own repository and private but collaborator repository. * * @param userName the user name of collaborator @@ -239,8 +239,10 @@ trait RepositoryService { self: AccountService => def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = { Repositories.filter { t1 => (t1.isPrivate === false.bind) || - (t1.userName === userName.bind) || - (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists) + (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 @@ -248,8 +250,10 @@ trait RepositoryService { self: AccountService => def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)(implicit s: Session): List[RepositoryInfo] = { Repositories.filter { t1 => - (t1.userName === userName.bind) || - (Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists) + (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){ @@ -284,8 +288,13 @@ trait RepositoryService { self: AccountService => case Some(x) if(x.isAdmin) => Repositories // for Normal Users case Some(x) if(!x.isAdmin) => - Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) || - (Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists) + 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))) + } exists) } // for Guests case None => Repositories filter(_.isPrivate === false.bind) @@ -342,8 +351,8 @@ trait RepositoryService { self: AccountService => /** * Add collaborator (user or group) to the repository. */ - def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, permission: String)(implicit s: Session): Unit = - Collaborators insert Collaborator(userName, repositoryName, collaboratorName, permission) + def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, role: String)(implicit s: Session): Unit = + Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role) /** * Remove all collaborators from the repository. @@ -366,38 +375,38 @@ trait RepositoryService { self: AccountService => * 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[Permission] = 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) } .filter { case (t1, t2) => t1.byRepository(userName, repositoryName) } - .map { case (t1, t2) => (t1.collaboratorName, t1.permission) } + .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 } .filter { case ((t1, t2), t3) => t1.byRepository(userName, repositoryName) } - .map { case ((t1, t2), t3) => (t3.userName, t1.permission) } + .map { case ((t1, t2), t3) => (t3.userName, t1.role) } q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1) } - def hasWritePermission(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(Permission.ADMIN, Permission.WRITE)).contains(a.userName)) => true + case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName)) => true case _ => false } } - def hasReadPermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { + 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(Permission.ADMIN, Permission.WRITE, Permission.READ)).contains(a.userName)) => true + case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST)).contains(a.userName)) => true case _ => false } } @@ -419,26 +428,20 @@ trait RepositoryService { self: AccountService => object RepositoryService { case class RepositoryInfo(owner: String, name: String, repository: Repository, - issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int, + 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]) = - this( - repo.owner, repo.name, model, - issueCount, pullCount, repo.commitCount, forkedCount, - repo.branchList, repo.tags, managers) + this(repo.owner, repo.name, model, issueCount, pullCount, forkedCount, repo.branchList, repo.tags, managers) /** * Creates instance without issue count and pull request count. */ def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) = - this( - repo.owner, repo.name, model, - 0, 0, repo.commitCount, forkedCount, - repo.branchList, repo.tags, managers) + this(repo.owner, repo.name, model, 0, 0, forkedCount, repo.branchList, repo.tags, managers) def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name) def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name) @@ -452,7 +455,6 @@ object RepositoryService { (id, path.substring(id.length).stripPrefix("/")) } - } def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git" diff --git a/src/main/scala/gitbucket/core/service/WebHookService.scala b/src/main/scala/gitbucket/core/service/WebHookService.scala index d8ca928a1..1083868b7 100644 --- a/src/main/scala/gitbucket/core/service/WebHookService.scala +++ b/src/main/scala/gitbucket/core/service/WebHookService.scala @@ -1,9 +1,11 @@ package gitbucket.core.service +import java.util.Date + import fr.brouillard.oss.security.xhub.XHub -import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter} +import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest} import gitbucket.core.api._ -import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent, CommitComment} +import gitbucket.core.model.{Account, CommitComment, Issue, IssueComment, PullRequest, WebHook, WebHookEvent} import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile.blockingApi._ @@ -17,6 +19,7 @@ import org.apache.http.message.BasicNameValuePair import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.ObjectId import org.slf4j.LoggerFactory + import scala.concurrent._ import scala.util.{Success, Failure} import org.apache.http.HttpRequest @@ -35,14 +38,14 @@ trait WebHookService { def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(WebHook, Set[WebHook.Event])] = WebHooks.filter(_.byRepository(owner, repository)) .join(WebHookEvents).on { (w, t) => t.byWebHook(w) } - .map{ case (w,t) => w -> t.event } + .map { case (w, t) => w -> t.event } .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[WebHook] = WebHooks.filter(_.byRepository(owner, repository)) .join(WebHookEvents).on { (wh, whe) => whe.byWebHook(wh) } - .filter{ case (wh, whe) => whe.event === event.bind} + .filter { case (wh, whe) => whe.event === event.bind} .map{ case (wh, whe) => wh } .list.distinct @@ -51,12 +54,12 @@ trait WebHookService { WebHooks .filter(_.byPrimaryKey(owner, repository, url)) .join(WebHookEvents).on { (w, t) => t.byWebHook(w) } - .map{ case (w,t) => w -> t.event } + .map { case (w, t) => w -> t.event } .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 = { WebHooks insert WebHook(owner, repository, url, ctype, token) - events.toSet.map { event: WebHook.Event => + events.map { event: WebHook.Event => WebHookEvents insert WebHookEvent(owner, repository, url, event) } } @@ -64,7 +67,7 @@ trait WebHookService { def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = { WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token)) WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete - events.toSet.map { event: WebHook.Event => + events.map { event: WebHook.Event => WebHookEvents insert WebHookEvent(owner, repository, url, event) } } @@ -83,7 +86,7 @@ trait WebHookService { 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 + 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 @@ -93,7 +96,7 @@ trait WebHookService { webHooks.map { webHook => val reqPromise = Promise[HttpRequest] val f = Future { - val itcp = new org.apache.http.HttpRequestInterceptor{ + val itcp = new org.apache.http.HttpRequestInterceptor { def process(res: HttpRequest, ctx: HttpContext): Unit = { reqPromise.success(res) } @@ -132,7 +135,7 @@ trait WebHookService { logger.debug(s"end web hook invocation for ${webHook}") res } catch { - case e:Throwable => { + case e: Throwable => { if(!reqPromise.isCompleted){ reqPromise.failure(e) } @@ -168,11 +171,11 @@ trait WebHookPullRequestService extends WebHookService { issueUser <- users.get(issue.openedUserName) } yield { WebHookIssuesPayload( - action = action, - number = issue.issueId, - repository = ApiRepository(repository, ApiUser(repoOwner)), - issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)), - sender = ApiUser(sender)) + action = action, + number = issue.issueId, + repository = ApiRepository(repository, ApiUser(repoOwner)), + issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)), + sender = ApiUser(sender)) } } } @@ -198,7 +201,9 @@ trait WebHookPullRequestService extends WebHookService { headOwner = headOwner, baseRepository = repository, baseOwner = baseOwner, - sender = sender) + sender = sender, + mergedComment = getMergedComment(repository.owner, repository.name, issueId) + ) } } } @@ -237,7 +242,10 @@ trait WebHookPullRequestService extends WebHookService { headOwner = headOwner, baseRepository = baseRepo, baseOwner = baseOwner, - sender = sender) + sender = sender, + mergedComment = getMergedComment(baseRepo.owner, baseRepo.name, issue.issueId) + ) + callWebHook(WebHook.PullRequest, webHooks, payload) } } @@ -267,7 +275,9 @@ trait WebHookPullRequestReviewCommentService extends WebHookService { headOwner = headOwner, baseRepository = repository, baseOwner = baseOwner, - sender = sender) + sender = sender, + mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId) + ) } } } @@ -365,11 +375,21 @@ object WebHookService { headOwner: Account, baseRepository: RepositoryInfo, baseOwner: Account, - sender: Account): WebHookPullRequestPayload = { + 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, pullRequest, headRepoPayload, baseRepoPayload, ApiUser(issueUser)) + val pr = ApiPullRequest( + issue = issue, + pullRequest = pullRequest, + headRepo = headRepoPayload, + baseRepo = baseRepoPayload, + user = ApiUser(issueUser), + mergedComment = mergedComment + ) + WebHookPullRequestPayload( action = action, number = issue.issueId, @@ -389,7 +409,7 @@ object WebHookService { sender: ApiUser ) extends WebHookPayload - object WebHookIssueCommentPayload{ + object WebHookIssueCommentPayload { def apply( issue: Issue, issueUser: Account, @@ -415,28 +435,42 @@ object WebHookService { sender: ApiUser ) extends WebHookPayload - object WebHookPullRequestReviewCommentPayload{ + object WebHookPullRequestReviewCommentPayload { def apply( - action: String, - comment: CommitComment, - issue: Issue, - issueUser: Account, - pullRequest: PullRequest, - headRepository: RepositoryInfo, - headOwner: Account, - baseRepository: RepositoryInfo, - baseOwner: Account, - sender: Account - ) : WebHookPullRequestReviewCommentPayload = { + action: String, + comment: CommitComment, + issue: Issue, + issueUser: Account, + pullRequest: PullRequest, + headRepository: RepositoryInfo, + headOwner: Account, + baseRepository: RepositoryInfo, + baseOwner: Account, + sender: Account, + mergedComment: Option[(IssueComment, Account)] + ) : WebHookPullRequestReviewCommentPayload = { val headRepoPayload = ApiRepository(headRepository, headOwner) val baseRepoPayload = ApiRepository(baseRepository, baseOwner) val senderPayload = ApiUser(sender) + WebHookPullRequestReviewCommentPayload( - action = action, - comment = ApiPullRequestReviewComment(comment, senderPayload, RepositoryName(baseRepository), issue.issueId), - pull_request = ApiPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, ApiUser(issueUser)), - repository = baseRepoPayload, - sender = senderPayload) + action = action, + comment = ApiPullRequestReviewComment( + comment = comment, + commentedUser = senderPayload, + repositoryName = RepositoryName(baseRepository), + issueId = issue.issueId + ), + pull_request = ApiPullRequest( + issue = issue, + pullRequest = pullRequest, + headRepo = headRepoPayload, + baseRepo = baseRepoPayload, + user = ApiUser(issueUser), + mergedComment = mergedComment + ), + repository = baseRepoPayload, + sender = senderPayload) } } } diff --git a/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala b/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala index c079eb880..d0b92bdc2 100644 --- a/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala +++ b/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala @@ -84,7 +84,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) account <- authenticate(settings, username, password) } yield if(isUpdating || repository.repository.isPrivate){ - if(hasWritePermission(repository.owner, repository.name, Some(account))){ + if(hasDeveloperRole(repository.owner, repository.name, Some(account))){ request.setAttribute(Keys.Request.UserName, account.userName) true } else false diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 1ceb76acf..e85804eae 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -110,7 +110,7 @@ import scala.collection.JavaConverters._ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session) extends PostReceiveHook with PreReceiveHook with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService - with WebHookPullRequestService with ProtectedBranchService { + with WebHookPullRequestService with CommitsService { private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) private var existIds: Seq[String] = Nil @@ -139,6 +139,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = { try { using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => + JGitUtil.removeCache(git) + val pushedIds = scala.collection.mutable.Set[String]() commands.asScala.foreach { command => logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}") diff --git a/src/main/scala/gitbucket/core/ssh/GitCommand.scala b/src/main/scala/gitbucket/core/ssh/GitCommand.scala index c7b427778..8897eb69e 100644 --- a/src/main/scala/gitbucket/core/ssh/GitCommand.scala +++ b/src/main/scala/gitbucket/core/ssh/GitCommand.scala @@ -93,7 +93,7 @@ abstract class DefaultGitCommand(val owner: String, val repoName: String) extend protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo) (implicit session: Session): Boolean = getAccountByUserName(username) match { - case Some(account) => hasWritePermission(repositoryInfo.owner, repositoryInfo.name, Some(account)) + case Some(account) => hasDeveloperRole(repositoryInfo.owner, repositoryInfo.name, Some(account)) case None => false } diff --git a/src/main/scala/gitbucket/core/util/Authenticator.scala b/src/main/scala/gitbucket/core/util/Authenticator.scala index b4cfef5d0..bbcb3398c 100644 --- a/src/main/scala/gitbucket/core/util/Authenticator.scala +++ b/src/main/scala/gitbucket/core/util/Authenticator.scala @@ -2,13 +2,11 @@ package gitbucket.core.util import gitbucket.core.controller.ControllerBase import gitbucket.core.service.{AccountService, RepositoryService} -import gitbucket.core.model.Permission +import gitbucket.core.model.Role import RepositoryService.RepositoryInfo import Implicits._ import ControlUtil._ -import scala.collection.Searching.search - /** * Allows only oneself and administrators. */ @@ -45,7 +43,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco 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(Permission.ADMIN)).contains(x.userName)) => action(repository) + case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq(Role.ADMIN)).contains(x.userName)) => action(repository) case _ => Unauthorized() } } getOrElse NotFound() @@ -156,7 +154,7 @@ trait WritableUsersAuthenticator { self: ControllerBase with RepositoryService w 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(Permission.ADMIN, Permission.WRITE)).contains(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() diff --git a/src/main/scala/gitbucket/core/util/JDBCUtil.scala b/src/main/scala/gitbucket/core/util/JDBCUtil.scala index 34bee29df..edc41ec40 100644 --- a/src/main/scala/gitbucket/core/util/JDBCUtil.scala +++ b/src/main/scala/gitbucket/core/util/JDBCUtil.scala @@ -9,7 +9,10 @@ import scala.collection.mutable.ListBuffer /** * Provides implicit class which extends java.sql.Connection. - * This is used in automatic migration in [[servlet.AutoUpdateListener]]. + * This is used in following points: + * + * - Automatic migration in [[gitbucket.core.servlet.InitializeListener]] + * - Data importing / exporting in [[gitbucket.core.controller.SystemSettingsController]] and [[gitbucket.core.controller.FileUploadController]] */ object JDBCUtil { @@ -71,8 +74,6 @@ object JDBCUtil { val bytes = new scala.Array[Byte](1024 * 8) var stringLiteral = false - var count = 0 - while({ length = in.read(bytes); length != -1 }){ for(i <- 0 to length - 1){ val c = bytes(i) @@ -81,13 +82,19 @@ object JDBCUtil { } if(c == ';' && !stringLiteral){ val sql = new String(out.toByteArray, "UTF-8") - conn.update(sql) + conn.update(sql.trim) out = new ByteArrayOutputStream() } else { out.write(c) } } } + + val remain = out.toByteArray + if(remain.length != 0){ + val sql = new String(remain, "UTF-8") + conn.update(sql.trim) + } } conn.commit() diff --git a/src/main/scala/gitbucket/core/util/JGitUtil.scala b/src/main/scala/gitbucket/core/util/JGitUtil.scala index 7c5f68764..786edca7d 100644 --- a/src/main/scala/gitbucket/core/util/JGitUtil.scala +++ b/src/main/scala/gitbucket/core/util/JGitUtil.scala @@ -5,6 +5,7 @@ import org.eclipse.jgit.api.Git import Directory._ import StringUtil._ import ControlUtil._ + import scala.annotation.tailrec import scala.collection.JavaConverters._ import org.eclipse.jgit.lib._ @@ -16,7 +17,11 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException} import org.eclipse.jgit.transport.RefSpec import java.util.Date -import org.eclipse.jgit.api.errors.{JGitInternalException, InvalidRefNameException, RefAlreadyExistsException, NoHeadException} +import java.util.concurrent.TimeUnit +import java.util.function.Consumer + +import org.cache2k.{Cache2kBuilder, CacheEntry} +import org.eclipse.jgit.api.errors.{InvalidRefNameException, JGitInternalException, NoHeadException, RefAlreadyExistsException} import org.eclipse.jgit.dircache.DirCacheEntry import org.slf4j.LoggerFactory @@ -32,14 +37,11 @@ object JGitUtil { * * @param owner the user name of the repository owner * @param name the repository name - * @param commitCount the commit count. If the repository has over 1000 commits then this property is 1001. * @param branchList the list of branch names * @param tags the list of tags */ - case class RepositoryInfo(owner: String, name: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){ - def this(owner: String, name: String) = { - this(owner, name, 0, Nil, Nil) - } + case class RepositoryInfo(owner: String, name: String, branchList: List[String], tags: List[TagInfo]){ + def this(owner: String, name: String) = this(owner, name, Nil, Nil) } /** @@ -169,20 +171,54 @@ object JGitUtil { revWalk.dispose revCommit } - + + private val cache = new Cache2kBuilder[String, Int]() {} + .name("commit-count") + .expireAfterWrite(24, TimeUnit.HOURS) + .entryCapacity(10000) + .build() + + def removeCache(git: Git): Unit = { + val dir = git.getRepository.getDirectory + val keyPrefix = dir.getAbsolutePath + "@" + + cache.forEach(new Consumer[CacheEntry[String, Int]] { + override def accept(entry: CacheEntry[String, Int]): Unit = { + if(entry.getKey.startsWith(keyPrefix)){ + cache.remove(entry.getKey) + } + } + }) + } + + /** + * Returns the number of commits in the specified branch or commit. + * If the specified branch has over 10000 commits, this method returns 100001. + */ + def getCommitCount(owner: String, repository: String, branch: String): Int = { + val dir = getRepositoryDir(owner, repository) + val key = dir.getAbsolutePath + "@" + branch + val entry = cache.getEntry(key) + + 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 + cache.put(key, commitCount) + commitCount + } + } else { + entry.getValue + } + } + /** * 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 => try { - // get commit count - val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10001).sum - - RepositoryInfo( - owner, repository, - // commit count - commitCount, + RepositoryInfo(owner, repository, // branches git.branchList.call.asScala.map { ref => ref.getName.stripPrefix("refs/heads/") @@ -195,9 +231,7 @@ object JGitUtil { ) } catch { // not initialized - case e: NoHeadException => RepositoryInfo( - owner, repository, 0, Nil, Nil) - + case e: NoHeadException => RepositoryInfo(owner, repository, Nil, Nil) } } } @@ -212,8 +246,8 @@ object JGitUtil { */ def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = { using(new RevWalk(git.getRepository)){ revWalk => - val objectId = git.getRepository.resolve(revision) - if(objectId==null) return Nil + val objectId = git.getRepository.resolve(revision) + if(objectId == null) return Nil val revCommit = revWalk.parseCommit(objectId) def useTreeWalk(rev:RevCommit)(f:TreeWalk => Any): Unit = if (path == ".") { @@ -255,14 +289,14 @@ object JGitUtil { revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, Option[String], RevCommit)] ={ if(restList.isEmpty){ result - }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{ + } 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) } + val (thisTimeChecks,skips) = restList.partition { case (tuple, parentsMap) => parentsMap.contains(newCommit) } if(thisTimeChecks.isEmpty){ findLastCommits(result, restList, revIterator) - }else{ + } else { var nextRest = skips var nextResult = result // Map[(name, oid), (tuple, parentsMap)] @@ -270,20 +304,20 @@ object JGitUtil { lazy val newParentsMap = newCommit.getParents.map(_ -> newCommit).toMap useTreeWalk(newCommit){ walk => while(walk.next){ - rest.remove(walk.getNameString -> walk.getObjectId(0)).map{ case (tuple, _) => + rest.remove(walk.getNameString -> walk.getObjectId(0)).map { case (tuple, _) => if(newParentsMap.isEmpty){ nextResult +:= tupleAdd(tuple, newCommit) - }else{ + } else { nextRest +:= tuple -> newParentsMap } } } } - rest.values.map{ case (tuple, parentsMap) => + rest.values.map { case (tuple, parentsMap) => val restParentsMap = parentsMap - newCommit if(restParentsMap.isEmpty){ nextResult +:= tupleAdd(tuple, parentsMap(newCommit)) - }else{ + } else { nextRest +:= tuple -> restParentsMap } } @@ -295,7 +329,7 @@ object JGitUtil { var fileList: List[(ObjectId, FileMode, String, Option[String])] = Nil useTreeWalk(revCommit){ treeWalk => while (treeWalk.next()) { - val linkUrl =if (treeWalk.getFileMode(0) == FileMode.GITLINK) { + val linkUrl = if (treeWalk.getFileMode(0) == FileMode.GITLINK) { getSubmodules(git, revCommit.getTree).find(_.path == treeWalk.getPathString).map(_.url) } else None fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getNameString, linkUrl) @@ -345,7 +379,7 @@ object JGitUtil { 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 + if(objectId == null) return None val revCommit = revWalk.parseCommit(objectId) Some(revCommit.getTree.name) } @@ -357,7 +391,7 @@ object JGitUtil { 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 + if(objectId == null) return Nil using(new TreeWalk(git.getRepository)){ treeWalk => treeWalk.addTree(objectId) treeWalk.setRecursive(true) @@ -705,6 +739,8 @@ object JGitUtil { refUpdate.setNewObjectId(newHeadId) refUpdate.update() + removeCache(git) + newHeadId } @@ -877,6 +913,7 @@ object JGitUtil { /** * Returns the last modified commit of specified path + * * @param git the Git object * @param startCommit the search base commit id * @param path the path of target file or directory @@ -959,6 +996,7 @@ object JGitUtil { /** * Returns sha1 + * * @param owner repository owner * @param name repository name * @param revstr A git object references expression diff --git a/src/main/scala/gitbucket/core/util/Notifier.scala b/src/main/scala/gitbucket/core/util/Notifier.scala index 398002950..58b9d3230 100644 --- a/src/main/scala/gitbucket/core/util/Notifier.scala +++ b/src/main/scala/gitbucket/core/util/Notifier.scala @@ -109,6 +109,9 @@ class Mailer(private val smtp: Smtp) extends Notifier { } smtp.ssl.foreach { ssl => email.setSSLOnConnect(ssl) + if(ssl == true) { + email.setSslSmtpPort(smtp.port.get.toString) + } } smtp.fromAddress .map (_ -> smtp.fromName.getOrElse(context.loginAccount.get.userName)) diff --git a/src/main/scala/gitbucket/core/view/LinkConverter.scala b/src/main/scala/gitbucket/core/view/LinkConverter.scala index 996fac25e..00d4e88e6 100644 --- a/src/main/scala/gitbucket/core/view/LinkConverter.scala +++ b/src/main/scala/gitbucket/core/view/LinkConverter.scala @@ -73,7 +73,7 @@ trait LinkConverter { self: RequestCache => } // convert issue id to link - .replaceBy(("(?<=(^|\\W))(GH-|" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r){ m => + .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) => diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 6eb3641c5..46b39b26a 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -44,7 +44,8 @@ object Markdown { val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages) - helpers.decorateHtml(Marked.marked(source, options, renderer), repository) + //helpers.decorateHtml(Marked.marked(source, options, renderer), repository) + Marked.marked(source, options, renderer) } /** @@ -109,11 +110,10 @@ object Markdown { override def text(text: String): String = { // convert commit id and username to link. val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "#", false) else text - // convert task list to checkbox. val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 - - t2 + // decorate by TextDecorator plugins + helpers.decorateHtml(t2, repository) } override def link(href: String, title: String, text: String): String = { diff --git a/src/main/twirl/gitbucket/core/account/application.scala.html b/src/main/twirl/gitbucket/core/account/application.scala.html index 22ff20024..fd3b8e730 100644 --- a/src/main/twirl/gitbucket/core/account/application.scala.html +++ b/src/main/twirl/gitbucket/core/account/application.scala.html @@ -19,8 +19,8 @@ Delete
- @gitbucket.core.helper.html.copy("generated-token-copy", tokenString){ - + @gitbucket.core.helper.html.copy("generated-token", "generated-token-copy", tokenString){ + }

diff --git a/src/main/twirl/gitbucket/core/account/group.scala.html b/src/main/twirl/gitbucket/core/account/group.scala.html index 797c6690c..6be84c9d8 100644 --- a/src/main/twirl/gitbucket/core/account/group.scala.html +++ b/src/main/twirl/gitbucket/core/account/group.scala.html @@ -43,10 +43,10 @@
@if(account.isDefined){ } - + @if(account.isDefined){ Cancel } @@ -136,4 +136,4 @@ $(function(){ $('#members').val(members); } }); - \ No newline at end of file + diff --git a/src/main/twirl/gitbucket/core/account/main.scala.html b/src/main/twirl/gitbucket/core/account/main.scala.html index 69cc13363..8d683999b 100644 --- a/src/main/twirl/gitbucket/core/account/main.scala.html +++ b/src/main/twirl/gitbucket/core/account/main.scala.html @@ -38,7 +38,7 @@ @if(account.isGroupAccount){ Members } else { - Public Activity + Public activity } @gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab => @tab(account, context).map { link => @@ -48,14 +48,14 @@ @if(context.loginAccount.isDefined && context.loginAccount.get.userName == account.userName){
  • } @if(context.loginAccount.isDefined && account.isGroupAccount && isGroupManager){
  • } diff --git a/src/main/twirl/gitbucket/core/admin/menu.scala.html b/src/main/twirl/gitbucket/core/admin/menu.scala.html index 15eb6d0ac..c7e3ab04e 100644 --- a/src/main/twirl/gitbucket/core/admin/menu.scala.html +++ b/src/main/twirl/gitbucket/core/admin/menu.scala.html @@ -3,10 +3,10 @@
    - + Cancel
    @@ -127,4 +127,4 @@ $(function(){ $('#members').val(members); } }); - \ No newline at end of file + diff --git a/src/main/twirl/gitbucket/core/admin/userlist.scala.html b/src/main/twirl/gitbucket/core/admin/userlist.scala.html index 7e4786a76..71d52c1de 100644 --- a/src/main/twirl/gitbucket/core/admin/userlist.scala.html +++ b/src/main/twirl/gitbucket/core/admin/userlist.scala.html @@ -3,8 +3,8 @@ @gitbucket.core.html.main("Manage Users"){ @gitbucket.core.admin.html.menu("users"){