diff --git a/build.sbt b/build.sbt index 59d9921..fb2cb6d 100644 --- a/build.sbt +++ b/build.sbt @@ -30,8 +30,8 @@ ) libraryDependencies ++= Seq( - "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.11.0.201803080745-r", - "org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.11.0.201803080745-r", + "org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "5.0.0.201806131550-r", + "org.eclipse.jgit" % "org.eclipse.jgit.archive" % "5.0.0.201806131550-r", "org.scalatra" %% "scalatra" % ScalatraVersion, "org.scalatra" %% "scalatra-json" % ScalatraVersion, "org.scalatra" %% "scalatra-forms" % ScalatraVersion, @@ -47,7 +47,7 @@ "com.github.takezoe" %% "blocking-slick-32" % "0.0.10", "com.novell.ldap" % "jldap" % "2009-10-07", "com.h2database" % "h2" % "1.4.196", - "org.mariadb.jdbc" % "mariadb-java-client" % "2.2.4", + "org.mariadb.jdbc" % "mariadb-java-client" % "2.2.5", "org.postgresql" % "postgresql" % "42.1.4", "ch.qos.logback" % "logback-classic" % "1.2.3", "com.zaxxer" % "HikariCP" % "2.7.4", diff --git a/project/build.properties b/project/build.properties index 7c81737..d6e3507 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.5 +sbt.version=1.1.6 diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 84686e5..7663ba9 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -713,7 +713,7 @@ } helper.html.forkrepository( repository, - (groups zip managerPermissions).toMap + (groups zip managerPermissions).sortBy(_._1) ) case _ => redirect(s"/${loginUserName}") } diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 784d025..fda5fdc 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -811,7 +811,7 @@ ApiCommits( repositoryName = RepositoryName(repository), commitInfo = commitInfo, - diffs = JGitUtil.getDiffs(git, Some(commitInfo.parents.head), commitInfo.id, false, true), + diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true), author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress), committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress), commentCount = getCommitComment(repository.owner, repository.name, sha).size diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index d1b034e..107f4f1 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -254,7 +254,7 @@ repository: RepositoryService.RepositoryInfo ): Unit = { JGitUtil.getObjectLoaderFromId(git, objectId) { loader => - contentType = FileUtil.getMimeType(path) + contentType = FileUtil.getSafeMimeType(path) if (loader.isLarge) { response.setContentLength(loader.getSize.toInt) diff --git a/src/main/scala/gitbucket/core/controller/DashboardController.scala b/src/main/scala/gitbucket/core/controller/DashboardController.scala index 2c8a982..ce0de3a 100644 --- a/src/main/scala/gitbucket/core/controller/DashboardController.scala +++ b/src/main/scala/gitbucket/core/controller/DashboardController.scala @@ -21,6 +21,16 @@ trait DashboardControllerBase extends ControllerBase { self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator => + get("/dashboard/repos")(usersOnly { + val userName = context.loginAccount.get.userName + + html.repos( + getGroupNames(userName), + getVisibleRepositories(None, withoutPhysicalInfo = true), + getUserRepositories(userName, withoutPhysicalInfo = true) + ) + }) + get("/dashboard/issues")(usersOnly { searchIssues("created_by") }) @@ -83,8 +93,7 @@ }, filter, getGroupNames(userName), - Nil, - getUserRepositories(userName, withoutPhysicalInfo = true) + getVisibleRepositories(None, withoutPhysicalInfo = true) ) } @@ -109,8 +118,7 @@ }, filter, getGroupNames(userName), - Nil, - getUserRepositories(userName, withoutPhysicalInfo = true) + getVisibleRepositories(None, withoutPhysicalInfo = true) ) } diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index ed9042d..d7b3f5a 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -64,8 +64,7 @@ val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName) gitbucket.core.html.index( getRecentActivitiesByOwners(visibleOwnerSet), - Nil, - getUserRepositories(account.userName, withoutPhysicalInfo = true), + getVisibleRepositories(None, withoutPhysicalInfo = true), showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken( account.userName ) @@ -75,7 +74,6 @@ gitbucket.core.html.index( getRecentActivities(), getVisibleRepositories(None, withoutPhysicalInfo = true), - Nil, showBannerToCreatePersonalAccessToken = false ) } @@ -273,18 +271,7 @@ 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) - } + gitbucket.core.search.html.repositories(query, repositories, visibleRepositories) } } diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 7c93fa5..90676e6 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -406,7 +406,7 @@ case dir if (dir.exists && dir.isDirectory) => dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file => response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""") - RawData(FileUtil.getMimeType(file.getName), file) + RawData(FileUtil.getSafeMimeType(file.getName), file) } case _ => None }) getOrElse NotFound() diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 2f93024..72bedbd 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -353,7 +353,16 @@ // close issue by commit message if (pullreq.requestBranch == repository.repository.defaultBranch) { commits.map { commit => - closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) + closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name).foreach { + issueId => + getIssue(repository.owner, repository.name, issueId.toString).map { issue => + callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount) + PluginRegistry().getIssueHooks + .foreach( + _.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount) + ) + } + } } } @@ -458,15 +467,35 @@ val defaultBranch = getRepository(owner, name).get.repository.defaultBranch if (pullreq.branch == defaultBranch) { commits.flatten.foreach { commit => - closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name) + closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name).foreach { + issueId => + getIssue(owner, name, issueId.toString).map { issue => + callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount) + PluginRegistry().getIssueHooks + .foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount)) + } + } } + val issueContent = issue.title + " " + issue.content.getOrElse("") closeIssuesFromMessage( - issue.title + " " + issue.content.getOrElse(""), + issueContent, loginAccount.userName, owner, name - ) - closeIssuesFromMessage(form.message, loginAccount.userName, owner, name) + ).foreach { issueId => + getIssue(owner, name, issueId.toString).map { issue => + callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount) + PluginRegistry().getIssueHooks + .foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount)) + } + } + closeIssuesFromMessage(form.message, loginAccount.userName, owner, name).foreach { issueId => + getIssue(owner, name, issueId.toString).map { issue => + callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount) + PluginRegistry().getIssueHooks + .foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount)) + } + } } updatePullRequests(owner, name, pullreq.branch) @@ -752,6 +781,10 @@ }) ajaxGet("/:owner/:repository/pulls/proposals")(readableUsersOnly { repository => + val thresholdTime = System.currentTimeMillis() - (1000 * 60 * 60) + val mailAddresses = + context.loginAccount.map(x => Seq(x.mailAddress) ++ getAccountExtraMailAddresses(x.userName)).getOrElse(Nil) + val branches = JGitUtil .getBranches( owner = repository.owner, @@ -759,8 +792,14 @@ defaultBranch = repository.repository.defaultBranch, origin = repository.repository.originUserName.isEmpty ) - .filter(x => x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0) - .sortBy(br => (br.mergeInfo.isEmpty, br.commitTime)) + .filter { x => + x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 && + x.commitTime.getTime > thresholdTime && + mailAddresses.contains(x.committerEmailAddress) + } + .sortBy { br => + (br.mergeInfo.isEmpty, br.commitTime) + } .map(_.name) .reverse diff --git a/src/main/scala/gitbucket/core/controller/ReleasesController.scala b/src/main/scala/gitbucket/core/controller/ReleasesController.scala index 2b37a49..ed44860 100644 --- a/src/main/scala/gitbucket/core/controller/ReleasesController.scala +++ b/src/main/scala/gitbucket/core/controller/ReleasesController.scala @@ -79,7 +79,7 @@ } yield { response.setHeader("Content-Disposition", s"attachment; filename=${asset.label}") RawData( - FileUtil.getMimeType(asset.label), + FileUtil.getSafeMimeType(asset.label), new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(tagName + "/" + fileId)) ) }).getOrElse(NotFound()) diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 61642c7..ce03dc8 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -628,7 +628,8 @@ newLineNumber, issueId, hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount), - repository = repository + repository = repository, + focus = true ) }) @@ -822,12 +823,35 @@ }) /** - * Download repository contents as an archive as compatible URL. + * Download repository contents as a zip archive as compatible URL. */ - get("/:owner/:repository/archive/:branch.:suffix")(referrersOnly { repository => + get("/:owner/:repository/archive/:branch.zip")(referrersOnly { repository => val branch = params("branch") - val suffix = params("suffix") - archiveRepository(branch, branch + "." + suffix, repository, "") + archiveRepository(branch, branch + ".zip", repository, "") + }) + + /** + * Download repository contents as a tar.gz archive as compatible URL. + */ + get("/:owner/:repository/archive/:branch.tar.gz")(referrersOnly { repository => + val branch = params("branch") + archiveRepository(branch, branch + ".tar.gz", repository, "") + }) + + /** + * Download repository contents as a tar.bz2 archive as compatible URL. + */ + get("/:owner/:repository/archive/:branch.tar.bz2")(referrersOnly { repository => + val branch = params("branch") + archiveRepository(branch, branch + ".tar.bz2", repository, "") + }) + + /** + * Download repository contents as a tar.xz archive as compatible URL. + */ + get("/:owner/:repository/archive/:branch.tar.xz")(referrersOnly { repository => + val branch = params("branch") + archiveRepository(branch, branch + ".tar.xz", repository, "") }) /** @@ -1040,7 +1064,14 @@ // close issue by commit message if (branch == repository.repository.defaultBranch) { - closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name) + closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach { + issueId => + getIssue(repository.owner, repository.name, issueId.toString).map { issue => + callIssuesWebHook("closed", repository, issue, baseUrl, loginAccount) + PluginRegistry().getIssueHooks + .foreach(_.closedByCommitComment(issue, repository, message, loginAccount)) + } + } } // call post commit hook @@ -1134,7 +1165,7 @@ } } - def archiveRepository( + private def archiveRepository( revision: String, filename: String, repository: RepositoryService.RepositoryInfo, @@ -1176,7 +1207,8 @@ } } - val suffix = path.split("/").lastOption.map("-" + _).getOrElse("") + val suffix = + path.split("/").lastOption.collect { case x if x.length > 0 => "-" + x.replace('/', '_') }.getOrElse("") val zipRe = """(.+)\.zip$""".r val tarRe = """(.+)\.tar\.(gz|bz2|xz)$""".r diff --git a/src/main/scala/gitbucket/core/plugin/IssueHook.scala b/src/main/scala/gitbucket/core/plugin/IssueHook.scala index 12d050a..1b22971 100644 --- a/src/main/scala/gitbucket/core/plugin/IssueHook.scala +++ b/src/main/scala/gitbucket/core/plugin/IssueHook.scala @@ -1,7 +1,7 @@ package gitbucket.core.plugin import gitbucket.core.controller.Context -import gitbucket.core.model.Issue +import gitbucket.core.model.{Account, Issue} import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.model.Profile._ import profile.api._ @@ -15,6 +15,19 @@ ): Unit = () def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () def reopened(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = () + def assigned( + issue: Issue, + repository: RepositoryInfo, + assigner: Option[String], + assigned: Option[String], + oldAssigned: Option[String] + )( + implicit session: Session, + context: Context + ): Unit = () + def closedByCommitComment(issue: Issue, repository: RepositoryInfo, message: String, pusher: Account)( + implicit session: Session + ): Unit = () } diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index c9854ea..05255c4 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -6,20 +6,21 @@ import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.controller.Context import gitbucket.core.model.{ + Account, + CommitState, Issue, - PullRequest, IssueComment, IssueLabel, Label, - Account, + PullRequest, Repository, - CommitState, Role } import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.dateColumnType +import gitbucket.core.plugin.PluginRegistry trait IssuesService { self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService => @@ -511,20 +512,24 @@ assignedUserName: Option[String], insertComment: Boolean = false )(implicit context: Context, s: Session): Int = { + val oldAssigned = getIssue(owner, repository, s"${issueId}").get.assignedUserName + val assigned = assignedUserName + val assigner = context.loginAccount.map(_.userName) if (insertComment) { - val oldAssigned = getIssue(owner, repository, s"${issueId}").get.assignedUserName.getOrElse("Not assigned") - val assigned = assignedUserName.getOrElse("Not assigned") IssueComments insert IssueComment( userName = owner, repositoryName = repository, issueId = issueId, action = "assign", - commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"), - content = s"${oldAssigned}:${assigned}", + commentedUserName = assigner.getOrElse("Unknown user"), + content = s"""${oldAssigned.getOrElse("Not assigned")}:${assigned.getOrElse("Not assigned")}""", registeredDate = currentDate, updatedDate = currentDate ) } + for (issue <- getIssue(owner, repository, issueId.toString); repo <- getRepository(owner, repository)) { + PluginRegistry().getIssueHooks.foreach(_.assigned(issue, repo, assigner, assigned, oldAssigned)) + } Issues .filter(_.byPrimaryKey(owner, repository, issueId)) .map(t => (t.assignedUserName ?, t.updatedDate)) diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 0ff127f..60f2e53 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -494,12 +494,13 @@ /** * TODO It seems to be able to improve performance. For example, RequestCache can be used for getAccountByUserName call. */ - private def getRepositoryManagers(userName: String, repositoryName: String)(implicit s: Session): Seq[String] = - if (getAccountByUserName(userName).exists(_.isGroupAccount)) { - getGroupMembers(userName).collect { case x if (x.isManager) => x.userName } - } else { - Seq(userName) - } ++ getCollaboratorUserNames(userName, repositoryName, Seq(Role.ADMIN)) + private def getRepositoryManagers(userName: String, repositoryName: String)(implicit s: Session): Seq[String] = { + (if (getAccountByUserName(userName).exists(_.isGroupAccount)) { + getGroupMembers(userName).collect { case x if (x.isManager) => x.userName } + } else { + Seq(userName) + }) ++ getCollaboratorUserNames(userName, repositoryName, Seq(Role.ADMIN)) + } /** * Updates the last activity date of the repository. diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index cfc5f3b..bbfc469 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -300,6 +300,8 @@ closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId => getIssue(owner, repository, issueId.toString).map { issue => callIssuesWebHook("closed", repositoryInfo, issue, baseUrl, pusherAccount) + PluginRegistry().getIssueHooks + .foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount)) } } } diff --git a/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala b/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala index 016d2ea..3e891af 100644 --- a/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/PluginAssetsServlet.scala @@ -26,7 +26,7 @@ try { val bytes = IOUtils.toByteArray(in) resp.setContentLength(bytes.length) - resp.setContentType(FileUtil.getContentType(path, bytes)) + resp.setContentType(FileUtil.getMimeType(path, bytes)) resp.setHeader("Cache-Control", "max-age=3600") resp.getOutputStream.write(bytes) } finally { diff --git a/src/main/scala/gitbucket/core/util/FileUtil.scala b/src/main/scala/gitbucket/core/util/FileUtil.scala index 0a668fe..ab42edd 100644 --- a/src/main/scala/gitbucket/core/util/FileUtil.scala +++ b/src/main/scala/gitbucket/core/util/FileUtil.scala @@ -16,7 +16,7 @@ } } - def getContentType(name: String, bytes: Array[Byte]): String = { + def getMimeType(name: String, bytes: Array[Byte]): String = { defining(getMimeType(name)) { mimeType => if (mimeType == "application/octet-stream" && isText(bytes)) { "text/plain" @@ -26,6 +26,10 @@ } } + def getSafeMimeType(name: String): String = { + getMimeType(name).replace("text/html", "text/plain") + } + def isImage(name: String): Boolean = getMimeType(name).startsWith("image/") def isLarge(size: Long): Boolean = (size > 1024 * 1000) diff --git a/src/main/twirl/gitbucket/core/account/newrepo.scala.html b/src/main/twirl/gitbucket/core/account/newrepo.scala.html index fa0f307..0c853bd 100644 --- a/src/main/twirl/gitbucket/core/account/newrepo.scala.html +++ b/src/main/twirl/gitbucket/core/account/newrepo.scala.html @@ -32,7 +32,7 @@
Repository name
- +
diff --git a/src/main/twirl/gitbucket/core/dashboard/issues.scala.html b/src/main/twirl/gitbucket/core/dashboard/issues.scala.html index f23e576..c2e92ed 100644 --- a/src/main/twirl/gitbucket/core/dashboard/issues.scala.html +++ b/src/main/twirl/gitbucket/core/dashboard/issues.scala.html @@ -5,10 +5,9 @@ condition: gitbucket.core.service.IssuesService.IssueSearchCondition, filter: String, groups: List[String], - recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], - userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context) + recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context) @gitbucket.core.html.main("Issues"){ - @gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){ + @gitbucket.core.dashboard.html.sidebar(recentRepositories){ @gitbucket.core.dashboard.html.tab("issues")
@gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition) diff --git a/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html b/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html index 3d05a84..b3cf909 100644 --- a/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html +++ b/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html @@ -5,10 +5,9 @@ condition: gitbucket.core.service.IssuesService.IssueSearchCondition, filter: String, groups: List[String], - recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], - userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context) + recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context) @gitbucket.core.html.main("Pull requests"){ - @gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){ + @gitbucket.core.dashboard.html.sidebar(recentRepositories){ @gitbucket.core.dashboard.html.tab("pulls")
@gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition) diff --git a/src/main/twirl/gitbucket/core/dashboard/repos.scala.html b/src/main/twirl/gitbucket/core/dashboard/repos.scala.html new file mode 100644 index 0000000..a3ac8c6 --- /dev/null +++ b/src/main/twirl/gitbucket/core/dashboard/repos.scala.html @@ -0,0 +1,71 @@ +@(groups: List[String], + recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], + userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context) +@import gitbucket.core.view.helpers +@gitbucket.core.html.main("Repositories"){ + @gitbucket.core.dashboard.html.sidebar(recentRepositories){ + @gitbucket.core.dashboard.html.tab("repos") +
+
+ + +
+ @if(userRepositories.isEmpty){ + No repositories + } else { + @userRepositories.map { repository => +
+
+ @gitbucket.core.helper.html.repositoryicon(repository, true) +
+
+
+ @repository.owner/@repository.name + @if(repository.repository.isPrivate){ + + } +
+ @if(repository.repository.originUserName.isDefined){ + + } + @if(repository.repository.description.isDefined){ +
@repository.repository.description
+ } +
Updated @gitbucket.core.helper.html.datetimeago(repository.repository.lastActivityDate)
+
+
+ } + } +
+ } +} + diff --git a/src/main/twirl/gitbucket/core/dashboard/sidebar.scala.html b/src/main/twirl/gitbucket/core/dashboard/sidebar.scala.html index 9f59a2f..ee7feb0 100644 --- a/src/main/twirl/gitbucket/core/dashboard/sidebar.scala.html +++ b/src/main/twirl/gitbucket/core/dashboard/sidebar.scala.html @@ -1,29 +1,8 @@ -@(recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], - userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(body: Html)(implicit context: gitbucket.core.controller.Context) +@(recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(body: Html)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers
diff --git a/src/main/twirl/gitbucket/core/dashboard/tab.scala.html b/src/main/twirl/gitbucket/core/dashboard/tab.scala.html index 597509f..ee2c17d 100644 --- a/src/main/twirl/gitbucket/core/dashboard/tab.scala.html +++ b/src/main/twirl/gitbucket/core/dashboard/tab.scala.html @@ -2,6 +2,7 @@
+ +@if(!latestCommitId.contains(comments.comments.head.commitId)) { + +} diff --git a/src/main/twirl/gitbucket/core/helper/diff.scala.html b/src/main/twirl/gitbucket/core/helper/diff.scala.html index ce58086..2dbe74c 100644 --- a/src/main/twirl/gitbucket/core/helper/diff.scala.html +++ b/src/main/twirl/gitbucket/core/helper/diff.scala.html @@ -16,9 +16,9 @@ @if(oldCommitId.isDefined && newCommitId.isDefined) { Patch } -
- - +
+ Unified + Split
Showing @diffs.size changed @helpers.plural(diffs.size, "file") @@ -151,29 +151,22 @@ }); } + window.params = {}; + // Render diffs as unified mode initially if(("&" + location.search.substring(1)).indexOf("&w=1") != -1){ - $('.ignore-whitespace').prop('checked',true); + $('.ignore-whitespace').prop('checked', true); + window.params['w'] = 1; } - window.viewType = 1; if(("&" + location.search.substring(1)).indexOf("&diff=split") != -1){ window.viewType = 0; - } - renderDiffs(); - - $('#btn-unified').click(function(){ + $('#btn-split').addClass('active'); + window.params['diff'] = 'split'; + } else { window.viewType = 1; $('#btn-unified').addClass('active'); - $('#btn-split').removeClass('active'); - renderDiffs(); - }); - - $('#btn-split').click(function(){ - window.viewType = 0; - $('#btn-unified').removeClass('active'); - $('#btn-split').addClass('active'); - renderDiffs(); - }); + } + renderDiffs(); $('.toggle-notes').change(function() { if (!$(this).prop('checked')) { @@ -183,14 +176,14 @@ }); $('.ignore-whitespace').change(function() { - renderOneDiff($(this).closest("table").find(".diffText"), viewType); + renderOneDiff($(this).closest("table").find(".diffText"), window.viewType); }); function getInlineContainer(where) { - if (viewType == 0) { + if (window.viewType == 0) { if (where === 'new') { return $(''); - } else if (where === 'old') { + } else { return $(''); } } @@ -245,7 +238,7 @@ var commitId = $this.closest('.table-bordered').attr('commitId'), fileName = $this.closest('.table-bordered').attr('fileName'), oldLineNumber, newLineNumber; - if (viewType == 0) { + if (window.viewType == 0) { oldLineNumber = $this.parent().prev('.oldline').attr('line-number'); newLineNumber = $this.parent().prev('.newline').attr('line-number'); } else { @@ -274,7 +267,7 @@ }); function renderOneCommitCommentIntoDiff($v, diff){ - var filename = $v.attr('filename'); + //var filename = $v.attr('filename'); var oldline = $v.attr('oldline'); var newline = $v.attr('newline'); var tmp; @@ -335,7 +328,11 @@ .append(renderStatBar(add, del).attr('title', (add + del) + " lines changed").tooltip()); @if(hasWritePermission) { - diffText.find('.body').each(function(){ $('+').prependTo(this); }); + diffText.find('.body').filter(function(i, e) { + return $(e).has('span').length > 0; + }).each(function(){ + $('+').prependTo(this); + }); } @if(showLineNotes){ var fileName = table.attr('filename'); @@ -365,8 +362,8 @@ }); for(var key in elements){ filename = elements[key]['filename']; - oldline = elements[key]['oldline'] ? elements[key]['oldline'] : ''; - newline = elements[key]['newline'] ? elements[key]['newline'] : ''; + oldline = elements[key]['oldline']; //? elements[key]['oldline'] : ''; + newline = elements[key]['newline']; //? elements[key]['newline'] : ''; var $v = $('
') .append($('@helpers.avatar(groupName, 100)@@@groupName
+
+ @helpers.avatar(groupName, 24) @@@groupName +
} } - - \ No newline at end of file + diff --git a/src/main/twirl/gitbucket/core/helper/preview.scala.html b/src/main/twirl/gitbucket/core/helper/preview.scala.html index 8049513..5b3122b 100644 --- a/src/main/twirl/gitbucket/core/helper/preview.scala.html +++ b/src/main/twirl/gitbucket/core/helper/preview.scala.html @@ -10,12 +10,13 @@ styleClass: String = "", placeholder: String = "Leave a comment", elastic: Boolean = false, + focus: Boolean = false, tabIndex: Int = -2, uid: Long = new java.util.Date().getTime())(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers
@@ -46,6 +47,13 @@ $('#content@uid').elastic(); $('#content@uid').trigger('blur'); } + @if(focus){ + $('#content@uid').trigger('focus'); + } + + $('#write@uid').on('shown.bs.tab', function(){ + $('#content@uid').trigger('focus'); + }); $('#preview@uid').click(function(){ $('#preview-area@uid').html(' Previewing...'); diff --git a/src/main/twirl/gitbucket/core/index.scala.html b/src/main/twirl/gitbucket/core/index.scala.html index 83ca41c..2552a01 100644 --- a/src/main/twirl/gitbucket/core/index.scala.html +++ b/src/main/twirl/gitbucket/core/index.scala.html @@ -1,10 +1,9 @@ @(activities: List[gitbucket.core.model.Activity], recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], - userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], showBannerToCreatePersonalAccessToken: Boolean)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @gitbucket.core.html.main("GitBucket"){ - @gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){ + @gitbucket.core.dashboard.html.sidebar(recentRepositories){ @context.settings.information.map { information =>
diff --git a/src/main/twirl/gitbucket/core/issues/commentlist.scala.html b/src/main/twirl/gitbucket/core/issues/commentlist.scala.html index 6fdecdd..cfb809f 100644 --- a/src/main/twirl/gitbucket/core/issues/commentlist.scala.html +++ b/src/main/twirl/gitbucket/core/issues/commentlist.scala.html @@ -3,7 +3,8 @@ isManageable: Boolean, repository: gitbucket.core.service.RepositoryService.RepositoryInfo, pullreq: Option[gitbucket.core.model.PullRequest] = None, - commitId: Option[String] = None)(implicit context: gitbucket.core.controller.Context) + commitId: Option[String] = None, + renderScript: Boolean = true)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @issueOrPullRequest()={ @if(issue.exists(_.isPullRequest))( "pull request" )else( "issue" ) } @showFormattedComment(comment: gitbucket.core.model.IssueComment)={ @@ -279,6 +280,7 @@
} } +@if(renderScript){ +} diff --git a/src/main/twirl/gitbucket/core/issues/editcomment.scala.html b/src/main/twirl/gitbucket/core/issues/editcomment.scala.html index 6a75dec..02df795 100644 --- a/src/main/twirl/gitbucket/core/issues/editcomment.scala.html +++ b/src/main/twirl/gitbucket/core/issues/editcomment.scala.html @@ -12,6 +12,7 @@ completionContext = "issues", style = "", elastic = true, + focus = true, tabIndex = 1 )
diff --git a/src/main/twirl/gitbucket/core/issues/editissue.scala.html b/src/main/twirl/gitbucket/core/issues/editissue.scala.html index 08d3ffe..81e0b3c 100644 --- a/src/main/twirl/gitbucket/core/issues/editissue.scala.html +++ b/src/main/twirl/gitbucket/core/issues/editissue.scala.html @@ -11,6 +11,7 @@ completionContext = "issues", style = "", elastic = true, + focus = true, tabIndex = 1 )
diff --git a/src/main/twirl/gitbucket/core/repo/commentform.scala.html b/src/main/twirl/gitbucket/core/repo/commentform.scala.html index 0febbd3..e9c46d0 100644 --- a/src/main/twirl/gitbucket/core/repo/commentform.scala.html +++ b/src/main/twirl/gitbucket/core/repo/commentform.scala.html @@ -5,6 +5,7 @@ issueId: Option[Int] = None, hasWritePermission: Boolean, repository: gitbucket.core.service.RepositoryService.RepositoryInfo, + focus: Boolean = false, uid: Long = new java.util.Date().getTime())(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @if(context.loginAccount.isDefined){ @@ -23,15 +24,16 @@ completionContext = "issues", style = "height: 100px; max-height: 150px;", elastic = true, + focus = focus, uid = uid ) + @if(fileName.isDefined){ +
+ + +
+ }
- @if(fileName.isDefined){ -
- - -
- }
@if(!fileName.isDefined){
@@ -112,8 +114,18 @@ .data('filename', '@fileName') .data('newline', @newLineNumber.getOrElse("undefined")) .data('oldline', @oldLineNumber.getOrElse("undefined"))); - var tmp = getInlineContainer(); - tmp.children('td:last').html($v) + + @if(oldLineNumber.isDefined){ + @if(newLineNumber.isDefined){ + var tmp = getInlineContainer(); + } else { + var tmp = getInlineContainer('old'); + } + tmp.children('td:first').html($v); + } else { + var tmp = getInlineContainer('new'); + tmp.children('td:last').html($v); + } $tr.after(tmp); } @@ -122,7 +134,7 @@ // $('#comment-list').children('.inline-comment').hide(); // } }).fail(function(req) { - $('.btn-inline-comment').removeAttr('disabled'); + $(e.target).removeAttr('disabled'); $('#error-content', $form).html($.parseJSON(req.responseText).content); }); }); @@ -140,6 +152,14 @@ } function getDiffData(tr){ + if(window.viewType === 0) { + return getDiffDataOnSplitMode(tr); + } else { + return getDiffDataOnUnifiedMode(tr); + } + } + + function getDiffDataOnUnifiedMode(tr) { var result = []; var count = 0; @@ -147,16 +167,19 @@ var oldTh = tr.find('th.oldline'); var newTh = tr.find('th.newline'); - if(!oldTh.attr('line-number') && !newTh.attr('line-number')){ + var oldLineNumber = oldTh.attr('line-number'); + var newLineNumber = newTh.attr('line-number'); + + if(!oldLineNumber && !newLineNumber){ break; } result.unshift({ - 'oldLine': oldTh.attr('line-number'), - 'newLine': newTh.attr('line-number'), + 'oldLine': oldLineNumber, + 'newLine': newLineNumber, 'type': tr.has('td.insert').length > 0 ? 'insert' : tr.has('td.delete').length > 0 ? 'delete' : 'equal', - 'text': tr.find('td>span').text() - }) + 'text': tr.find('td span').text() + }); tr = tr.prev('tr:has(th.line-num)'); count++; @@ -164,5 +187,55 @@ return result; } + + function getDiffDataOnSplitMode(tr) { + var result = []; + var count = 0; + + while(tr && count < 4){ + var oldTh = tr.find('th.oldline'); + var newTh = tr.find('th.newline'); + + var oldLineNumber = oldTh.attr('line-number'); + var newLineNumber = newTh.attr('line-number'); + + if(!oldLineNumber && !newLineNumber){ + break; + } + + var oldTd = oldTh.next(); + var newTd = newTh.next(); + + if (oldTd.hasClass('equal') && newTd.hasClass('equal')) { + result.unshift({ + 'oldLine': oldLineNumber, + 'newLine': newLineNumber, + 'type': 'equal', + 'text': newTd.find('span').text() + }); + count++; + } else { + if(newLineNumber) { + result.unshift({ + 'newLine': newLineNumber, + 'type': 'insert', + 'text': newTd.find('span').text() + }); + count++; + } + if(oldLineNumber) { + result.unshift({ + 'oldLine': oldLineNumber, + 'type': 'delete', + 'text': oldTd.find('span').text() + }); + count++; + } + } + tr = tr.prev('tr:has(th.line-num)'); + } + + return result; + } } diff --git a/src/main/twirl/gitbucket/core/repo/commit.scala.html b/src/main/twirl/gitbucket/core/repo/commit.scala.html index 4b630fd..065a001 100644 --- a/src/main/twirl/gitbucket/core/repo/commit.scala.html +++ b/src/main/twirl/gitbucket/core/repo/commit.scala.html @@ -1,4 +1,3 @@ -@import gitbucket.core.model.CommitComment @(commitId: String, commit: gitbucket.core.util.JGitUtil.CommitInfo, branches: List[String], @@ -26,7 +25,7 @@
@if(branches.nonEmpty){ - + @branches.zipWithIndex.map { case (branch, i) => @branch } @@ -34,7 +33,7 @@ } @if(tags.nonEmpty){ - + @tags.zipWithIndex.map { case (tag, i) => @tag } @@ -90,15 +89,16 @@
@gitbucket.core.issues.html.commentlist( - None, - comments.filter(_.asInstanceOf[gitbucket.core.model.CommitComment].fileName.isEmpty), - hasWritePermission, - repository, - None) + issue = None, + comments = comments.filter(_.asInstanceOf[CommitComment].fileName.isEmpty), + isManageable = hasWritePermission, + repository = repository, + pullreq = None, + renderScript = false)
@gitbucket.core.issues.html.commentlist( - None, - comments.map(_.asInstanceOf[CommitComment]).filter(_.fileName.isDefined).groupBy(_.fileName).map { case (fileName, comments) => + issue = None, + comments = comments.map(_.asInstanceOf[CommitComment]).filter(_.fileName.isDefined).groupBy(_.fileName).map { case (fileName, comments) => CommitComments( fileName = fileName.get, commentedUserName = comments.head.commentedUserName, @@ -107,10 +107,10 @@ diff = None ) }.toList, - hasWritePermission, - repository, - None, - Some(commitId)) + isManageable = hasWritePermission, + repository = repository, + pullreq = None, + commitId = Some(commitId))
@gitbucket.core.repo.html.commentform(commitId = commitId, hasWritePermission = hasWritePermission, repository = repository) diff --git a/src/main/twirl/gitbucket/core/repo/editcomment.scala.html b/src/main/twirl/gitbucket/core/repo/editcomment.scala.html index 9ae5965..91e97f5 100644 --- a/src/main/twirl/gitbucket/core/repo/editcomment.scala.html +++ b/src/main/twirl/gitbucket/core/repo/editcomment.scala.html @@ -12,6 +12,7 @@ completionContext = "issues", style = "", elastic = true, + focus = true, tabIndex = 1 )
diff --git a/src/main/twirl/gitbucket/core/repo/editor.scala.html b/src/main/twirl/gitbucket/core/repo/editor.scala.html index 0dbf47d..35720c8 100644 --- a/src/main/twirl/gitbucket/core/repo/editor.scala.html +++ b/src/main/twirl/gitbucket/core/repo/editor.scala.html @@ -19,7 +19,7 @@ @pathList.zipWithIndex.map { case (section, i) => @section / } - + diff --git a/src/main/twirl/gitbucket/core/repo/find.scala.html b/src/main/twirl/gitbucket/core/repo/find.scala.html index 0d536d9..d8bf046 100644 --- a/src/main/twirl/gitbucket/core/repo/find.scala.html +++ b/src/main/twirl/gitbucket/core/repo/find.scala.html @@ -10,7 +10,7 @@
diff --git a/src/main/twirl/gitbucket/core/repo/forked.scala.html b/src/main/twirl/gitbucket/core/repo/forked.scala.html index 601b031..1bd9988 100644 --- a/src/main/twirl/gitbucket/core/repo/forked.scala.html +++ b/src/main/twirl/gitbucket/core/repo/forked.scala.html @@ -54,11 +54,9 @@ }); $(document).on("click", ".js-fork-owner-select-target", function() { - if (!$(this).hasClass("disabled")) { - var account = $(this).text().replace("@@", ""); - $("#account").val(account); - $("#fork").submit(); - } + var account = $(this).text().replace("@@", ""); + $("#account").val(account); + $("#fork").submit(); }); @if(context.loginAccount.isDefined){ diff --git a/src/main/twirl/gitbucket/core/search/repositories.scala.html b/src/main/twirl/gitbucket/core/search/repositories.scala.html index eb450ce..04ed4df 100644 --- a/src/main/twirl/gitbucket/core/search/repositories.scala.html +++ b/src/main/twirl/gitbucket/core/search/repositories.scala.html @@ -1,10 +1,9 @@ @(query: String, repositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], - recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo], - userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context) + recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @gitbucket.core.html.main("GitBucket"){ - @gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){ + @gitbucket.core.dashboard.html.sidebar(recentRepositories){
diff --git a/src/main/twirl/gitbucket/core/settings/menu.scala.html b/src/main/twirl/gitbucket/core/settings/menu.scala.html index b81bcd3..2bcc10e 100644 --- a/src/main/twirl/gitbucket/core/settings/menu.scala.html +++ b/src/main/twirl/gitbucket/core/settings/menu.scala.html @@ -8,15 +8,15 @@ Collaborators @if(!repository.branchList.isEmpty){ - - Branches - + + Branches + } Service Hooks - Deploy Keys + Deploy Keys Danger Zone diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index a85da90..b476414 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -89,5 +89,10 @@ PATH_TO_DATADIR --> + + + svg + image/svg+xml + diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index 8e3cc6f..96aa821 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -254,9 +254,11 @@ margin-right: 0px; } +/* ul.nav-tabs { height: 42px; } +*/ ul.nav-pills .link { color: #4078c0; diff --git a/src/main/webapp/assets/common/js/gitbucket.js b/src/main/webapp/assets/common/js/gitbucket.js index d726b53..cd18f3b 100644 --- a/src/main/webapp/assets/common/js/gitbucket.js +++ b/src/main/webapp/assets/common/js/gitbucket.js @@ -76,7 +76,7 @@ * @param ignoreSpace {Number} 0: include, 1: ignore */ function diffUsingJS(oldTextId, newTextId, outputId, viewType, ignoreSpace) { - var old = $('#'+oldTextId), head = $('#'+newTextId); + var old = $('#'+oldTextId), head = $('#' + newTextId); var render = new JsDiffRender({ oldText: old.attr('data-val'), oldTextName: old.attr('data-file-name'), @@ -85,8 +85,13 @@ ignoreSpace: ignoreSpace, contextSize: 4 }); - var diff = render[viewType==1 ? "unified" : "split"](); - diff.appendTo($('#'+outputId).html("")); + var diff = render[viewType == 1 ? "unified" : "split"](); + if(viewType == 1){ + diff.find('tr:last').after($('')); + } else { + diff.find('tr:last').after($('')); + } + diff.appendTo($('#' + outputId).html("")); } diff --git a/src/main/webapp/assets/vendors/facebox/facebox.css b/src/main/webapp/assets/vendors/facebox/facebox.css index 9f4aaa8..d4fac8a 100644 --- a/src/main/webapp/assets/vendors/facebox/facebox.css +++ b/src/main/webapp/assets/vendors/facebox/facebox.css @@ -80,63 +80,9 @@ } .facebox-header { - margin: -15px -15px 15px; + margin: -15px -15px 0px -15px; padding: 15px; border-bottom: 1px solid #e5e5e5; font-size: 18px; font-weight: normal; } - -.owner-select-grid { - margin-left: -8px; - overflow: hidden; -} - -.owner-select-target { - float: left; - padding: 10px; - margin: 0 8px 10px; - text-align: center; - background-color: #f2f2f2; - border-radius: 3px; -} - -.owner-select-target.enabled { - font-weight: bold; - cursor: pointer; -} - -.owner-select-target.disabled { - color: #999; - cursor: not-allowed; -} - -.owner-select-grid .avatar { - display: block; - overflow: hidden; - line-height: 1; - vertical-align: middle; - border-radius: 3px; -} - -.owner-select-target.enabled .avatar { - opacity: 1; -} - -.owner-select-target.disabled .avatar { - margin-bottom: 9px; - opacity: 0.3; -} - -.css-truncate { - display: inline-block; - max-width: 125px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - vertical-align: top; -} - -.owner-select-target .css-truncate { - max-width: 90px; -} \ No newline at end of file