Merge branch 'master'

Conflicts:
	build.sbt
	src/main/scala/gitbucket/core/service/CommitsService.scala
	src/main/scala/gitbucket/core/service/PullRequestService.scala
	src/main/scala/gitbucket/core/service/RepositoryService.scala
	src/main/scala/gitbucket/core/service/WebHookService.scala
This commit is contained in:
Naoki Takezoe
2016-12-28 01:57:58 +09:00
145 changed files with 1034 additions and 11388 deletions

View File

@@ -24,35 +24,30 @@ Installation
-------- --------
GitBucket requires **Java8**. You have to install beforehand when it's not installed. 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). 1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher. 2. Access `http://[hostname]:8080/` and logged in with **root** / **root**.
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser 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] - `--port=[NUMBER]`
- --prefix=[CONTEXTPATH] - `--prefix=[CONTEXTPATH]`
- --host=[HOSTNAME] - `--host=[HOSTNAME]`
- --gitbucket.home=[DATA_DIR] - `--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). 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 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-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) - [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 Support
-------- --------
@@ -65,6 +60,24 @@ Support
Release Notes 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 ### 4.6 - 29 Oct 2016
- Add disable option for forking - Add disable option for forking
- Add History button to wiki page - Add History button to wiki page
@@ -101,7 +114,7 @@ Release Notes
- [List group repositories](https://developer.github.com/v3/repos/#list-organization-repositories) - [List group repositories](https://developer.github.com/v3/repos/#list-organization-repositories)
- Add new extension points - Add new extension points
- `assetsMapping` : Supplies resources in plugin classpath as web assets - `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 - `textDecorator` : Decorate text nodes in HTML which is converted from Markdown
### 4.2.1 - 3 Jul 2016 ### 4.2.1 - 3 Jul 2016

View File

@@ -1,7 +1,7 @@
val Organization = "io.github.gitbucket" val Organization = "io.github.gitbucket"
val Name = "gitbucket" val Name = "gitbucket"
val GitBucketVersion = "4.6.0" val GitBucketVersion = "4.8"
val ScalatraVersion = "2.5.0-RC1" val ScalatraVersion = "2.5.0"
val JettyVersion = "9.3.9.v20160517" val JettyVersion = "9.3.9.v20160517"
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin) lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
@@ -15,6 +15,7 @@ scalaVersion := "2.12.0"
// dependency settings // dependency settings
resolvers ++= Seq( resolvers ++= Seq(
Classpaths.typesafeReleases, Classpaths.typesafeReleases,
Resolver.jcenterRepo,
"amateras" at "http://amateras.sourceforge.jp/mvn/", "amateras" at "http://amateras.sourceforge.jp/mvn/",
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/", "sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/" "amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
@@ -46,7 +47,9 @@ libraryDependencies ++= Seq(
"com.typesafe" % "config" % "1.3.0", "com.typesafe" % "config" % "1.3.0",
"com.typesafe.akka" %% "akka-actor" % "2.4.12", "com.typesafe.akka" %% "akka-actor" % "2.4.12",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0", "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", "org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.12" % "test", "junit" % "junit" % "4.12" % "test",

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<changeSet> <changeSet>
<addColumn tableName="COLLABORATOR"> <addColumn tableName="COLLABORATOR">
<column name="PERMISSION" type="varchar(10)" nullable="false" defaultValue="ADMIN"/> <column name="ROLE" type="varchar(10)" nullable="false" defaultValue="ADMIN"/>
</addColumn> </addColumn>
<addColumn tableName="REPOSITORY"> <addColumn tableName="REPOSITORY">
<column name="WIKI_OPTION" type="varchar(10)" nullable="false" defaultValue="DISABLE"/> <column name="WIKI_OPTION" type="varchar(10)" nullable="false" defaultValue="DISABLE"/>

View File

@@ -22,5 +22,7 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new Version("4.7.0", new Version("4.7.0",
new LiquibaseMigration("update/gitbucket-core_4.7.xml"), new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
new SqlMigration("update/gitbucket-core_4.7.sql") new SqlMigration("update/gitbucket-core_4.7.sql")
) ),
new Version("4.7.1"),
new Version("4.8")
) )

View File

@@ -1,7 +1,6 @@
package gitbucket.core.api package gitbucket.core.api
import gitbucket.core.model.{Issue, PullRequest} import gitbucket.core.model.{Account, Issue, IssueComment, PullRequest}
import java.util.Date import java.util.Date
@@ -15,6 +14,9 @@ case class ApiPullRequest(
head: ApiPullRequest.Commit, head: ApiPullRequest.Commit,
base: ApiPullRequest.Commit, base: ApiPullRequest.Commit,
mergeable: Option[Boolean], mergeable: Option[Boolean],
merged: Boolean,
merged_at: Option[Date],
merged_by: Option[ApiUser],
title: String, title: String,
body: String, body: String,
user: ApiUser) { user: ApiUser) {
@@ -31,7 +33,14 @@ case class ApiPullRequest(
} }
object 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( ApiPullRequest(
number = issue.issueId, number = issue.issueId,
updated_at = issue.updatedDate, updated_at = issue.updatedDate,
@@ -45,6 +54,9 @@ object ApiPullRequest{
ref = pullRequest.branch, ref = pullRequest.branch,
repo = baseRepo)(issue.userName), repo = baseRepo)(issue.userName),
mergeable = None, // TODO: need check mergeable. 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, title = issue.title,
body = issue.content.getOrElse(""), body = issue.content.getOrElse(""),
user = user user = user

View File

@@ -11,7 +11,7 @@ case class CreateARepository(
auto_init: Boolean = false auto_init: Boolean = false
) { ) {
def isValid: Boolean = { def isValid: Boolean = {
name.length<=40 && name.length <= 100 &&
name.matches("[a-zA-Z0-9\\-\\+_.]+") && name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
!name.startsWith("_") && !name.startsWith("_") &&
!name.startsWith("-") !name.startsWith("-")

View File

@@ -22,6 +22,7 @@ class ApiController extends ApiControllerBase
with IssuesService with IssuesService
with LabelsService with LabelsService
with PullRequestService with PullRequestService
with CommitsService
with CommitStatusService with CommitStatusService
with RepositoryCreationService with RepositoryCreationService
with HandleCommentService with HandleCommentService
@@ -102,7 +103,7 @@ trait ApiControllerBase extends ControllerBase {
defaultBranch = repository.repository.defaultBranch, defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty origin = repository.repository.originUserName.isEmpty
).map { br => ).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 largeFile = params.get("large_file").exists(s => s.equals("true"))
val content = getContentFromId(git, f.id, largeFile) val content = getContentFromId(git, f.id, largeFile)
request.getHeader("Accept") match { request.getHeader("Accept") match {
case "application/vnd.github.v3.raw" => case "application/vnd.github.v3.raw" => {
contentType = "application/vnd.github.v3.raw"
content 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 => content.map(c =>
List( List(
"<div data-path=\"", path, "\" id=\"file\">", "<article>", "<div data-path=\"", path, "\" id=\"file\">", "<article>",
@@ -142,7 +146,9 @@ trait ApiControllerBase extends ControllerBase {
"</article>", "</div>" "</article>", "</div>"
).mkString ).mkString
) )
case "application/vnd.github.v3.html" => }
case "application/vnd.github.v3.html" => {
contentType = "application/vnd.github.v3.html"
content.map(c => content.map(c =>
List( List(
"<div data-path=\"", path, "\" id=\"file\">", "<div class=\"plain\">", "<pre>", "<div data-path=\"", path, "\" id=\"file\">", "<div class=\"plain\">", "<pre>",
@@ -150,6 +156,7 @@ trait ApiControllerBase extends ControllerBase {
"</pre>", "</div>", "</div>" "</pre>", "</div>", "</div>"
).mkString ).mkString
) )
}
case _ => case _ =>
Some(JsonFormat(ApiContents(f, content))) Some(JsonFormat(ApiContents(f, content)))
} }
@@ -254,7 +261,7 @@ trait ApiControllerBase extends ControllerBase {
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository => patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
import gitbucket.core.api._ import gitbucket.core.api._
(for{ (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) protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
} yield { } yield {
if(protection.enabled){ if(protection.enabled){
@@ -276,13 +283,26 @@ trait ApiControllerBase extends ControllerBase {
org.scalatra.NotFound(ApiError("Rate limiting is not enabled.")) 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 * https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
*/ */
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository => get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for{ (for{
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt) comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
} yield { } yield {
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) }) JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}) getOrElse NotFound() }) getOrElse NotFound()
@@ -363,12 +383,14 @@ trait ApiControllerBase extends ControllerBase {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color) updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat(ApiLabel( JsonFormat(ApiLabel(
getLabel(repository.owner, repository.name, label.labelId).get, getLabel(repository.owner, repository.name, label.labelId).get,
RepositoryName(repository))) RepositoryName(repository)
))
} else { } else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API // TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(ApiError( UnprocessableEntity(ApiError(
"Validation Failed", "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() } getOrElse NotFound()
} }
@@ -407,11 +429,12 @@ trait ApiControllerBase extends ControllerBase {
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) => JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
ApiPullRequest( ApiPullRequest(
issue, issue = issue,
pullRequest, pullRequest = pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)), headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)), baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser) 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 => get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
(for{ (for{
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId) (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) baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName) headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName) issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName) headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield { } yield {
JsonFormat(ApiPullRequest( JsonFormat(ApiPullRequest(
issue, issue = issue,
pullRequest, pullRequest = pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)), headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)), baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser))) user = ApiUser(issueUser),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
))
}) getOrElse NotFound() }) getOrElse NotFound()
}) })
@@ -450,7 +475,7 @@ trait ApiControllerBase extends ControllerBase {
val oldId = git.getRepository.resolve(pullreq.commitIdFrom) val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo) val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository) 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) JsonFormat(commits)
} }
} }
@@ -469,14 +494,14 @@ trait ApiControllerBase extends ControllerBase {
*/ */
post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository => post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository =>
(for{ (for{
ref <- params.get("sha") ref <- params.get("sha")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
data <- extractFromJsonBody[CreateAStatus] if data.isValid data <- extractFromJsonBody[CreateAStatus] if data.isValid
creator <- context.loginAccount creator <- context.loginAccount
state <- CommitState.valueOf(data.state) state <- CommitState.valueOf(data.state)
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"), statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
state, data.target_url, data.description, new java.util.Date(), creator) state, data.target_url, data.description, new java.util.Date(), creator)
status <- getCommitStatus(repository.owner, repository.name, statusId) status <- getCommitStatus(repository.owner, repository.name, statusId)
} yield { } yield {
JsonFormat(ApiCommitStatus(status, ApiUser(creator))) JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
}) getOrElse NotFound() }) getOrElse NotFound()
@@ -514,9 +539,9 @@ trait ApiControllerBase extends ControllerBase {
*/ */
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository => get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
(for{ (for{
ref <- params.get("ref") ref <- params.get("ref")
owner <- getAccountByUserName(repository.owner) owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref) sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield { } yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha) val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner))) 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 = 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
} }

View File

@@ -191,6 +191,7 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
case agent if agent.contains("Win") => "windows" case agent if agent.contains("Win") => "windows"
case _ => null case _ => null
} }
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
/** /**
* Get object from cache. * Get object from cache.

View File

@@ -1,13 +1,13 @@
package gitbucket.core.controller package gitbucket.core.controller
import gitbucket.core.dashboard.html import gitbucket.core.dashboard.html
import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService} import gitbucket.core.service._
import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator} import gitbucket.core.util.{Keys, UsersAuthenticator}
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.service.IssuesService._ import gitbucket.core.service.IssuesService._
class DashboardController extends DashboardControllerBase class DashboardController extends DashboardControllerBase
with IssuesService with PullRequestService with RepositoryService with AccountService with IssuesService with PullRequestService with RepositoryService with AccountService with CommitsService
with UsersAuthenticator with UsersAuthenticator
trait DashboardControllerBase extends ControllerBase { trait DashboardControllerBase extends ControllerBase {
@@ -76,7 +76,7 @@ trait DashboardControllerBase extends ControllerBase {
}, },
filter, filter,
getGroupNames(userName), getGroupNames(userName),
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true), Nil,
getUserRepositories(userName, withoutPhysicalInfo = true)) getUserRepositories(userName, withoutPhysicalInfo = true))
} }
@@ -101,7 +101,7 @@ trait DashboardControllerBase extends ControllerBase {
}, },
filter, filter,
getGroupNames(userName), getGroupNames(userName),
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true), Nil,
getUserRepositories(userName, withoutPhysicalInfo = true)) getUserRepositories(userName, withoutPhysicalInfo = true))
} }

View File

@@ -5,9 +5,9 @@ import gitbucket.core.model.Account
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.ControlUtil._ 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 io.github.gitbucket.scalatra.forms._
import org.scalatra.Ok
class IndexController extends IndexControllerBase class IndexController extends IndexControllerBase
@@ -36,23 +36,11 @@ trait IndexControllerBase extends ControllerBase {
get("/"){ get("/"){
val loginAccount = context.loginAccount context.loginAccount.map { account =>
if(loginAccount.isEmpty) { val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
gitbucket.core.html.index(getRecentActivities(), gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet), Nil, getUserRepositories(account.userName, withoutPhysicalInfo = true))
getVisibleRepositories(loginAccount, withoutPhysicalInfo = true), }.getOrElse {
loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil) gitbucket.core.html.index(getRecentActivities(), getVisibleRepositories(None, withoutPhysicalInfo = true), 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)
)
} }
} }
@@ -81,6 +69,15 @@ trait IndexControllerBase extends ControllerBase {
xml.feed(getRecentActivities()) 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. * Set account information into HttpSession and redirect.
*/ */
@@ -133,15 +130,10 @@ trait IndexControllerBase extends ControllerBase {
} getOrElse "" } 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? // TODO Move to RepositoryViwerController?
get("/:owner/:repository/search")(referrersOnly { repository => get("/:owner/:repository/search")(referrersOnly { repository =>
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) => defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")){ case (query, target) =>
val page = try { val page = try {
val i = params.getOrElse("page", "1").toInt val i = params.getOrElse("page", "1").toInt
if(i <= 0) 1 else i if(i <= 0) 1 else i
} catch { } catch {
@@ -150,23 +142,31 @@ trait IndexControllerBase extends ControllerBase {
target.toLowerCase match { target.toLowerCase match {
case "issue" => gitbucket.core.search.html.issues( case "issue" => gitbucket.core.search.html.issues(
countFiles(repository.owner, repository.name, query), if(query.nonEmpty) searchIssues(repository.owner, repository.name, query) else Nil,
searchIssues(repository.owner, repository.name, query),
countWikiPages(repository.owner, repository.name, query),
query, page, repository) query, page, repository)
case "wiki" => gitbucket.core.search.html.wiki( case "wiki" => gitbucket.core.search.html.wiki(
countFiles(repository.owner, repository.name, query), if(query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
countIssues(repository.owner, repository.name, query),
searchWikiPages(repository.owner, repository.name, query),
query, page, repository) query, page, repository)
case _ => gitbucket.core.search.html.code( case _ => gitbucket.core.search.html.code(
searchFiles(repository.owner, repository.name, query), if(query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
countIssues(repository.owner, repository.name, query),
countWikiPages(repository.owner, repository.name, query),
query, page, repository) 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)
}
}
} }

View File

@@ -14,12 +14,33 @@ import org.scalatra.Ok
class IssuesController extends IssuesControllerBase class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService with IssuesService
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with WebHookIssueCommentService 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 { trait IssuesControllerBase extends ControllerBase {
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService self: IssuesService
with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with WebHookIssueCommentService => 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], case class IssueCreateForm(title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String]) assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
@@ -84,7 +105,7 @@ trait IssuesControllerBase extends ControllerBase {
getAssignableUserNames(owner, name), getAssignableUserNames(owner, name),
getMilestones(owner, name), getMilestones(owner, name),
getLabels(owner, name), getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount), isManageable(repository),
repository) repository)
} }
} else Unauthorized() } else Unauthorized()
@@ -386,7 +407,7 @@ trait IssuesControllerBase extends ControllerBase {
* Tests whether an logged-in user can manage issues. * Tests whether an logged-in user can manage issues.
*/ */
private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = { 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 = { private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
repository.repository.options.issuesOption match { repository.repository.options.issuesOption match {
case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
case "DISABLE" => false 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. * Tests whether an issue or a comment is editable by a logged-in user.
*/ */
private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = { private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = {
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
} }
} }

View File

@@ -29,7 +29,7 @@ trait LabelsControllerBase extends ControllerBase {
getLabels(repository.owner, repository.name), getLabels(repository.owner, repository.name),
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository, repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount)) hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
}) })
ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository => ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository =>
@@ -43,7 +43,7 @@ trait LabelsControllerBase extends ControllerBase {
// TODO futility // TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository, repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount)) hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
}) })
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository => ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository =>
@@ -59,7 +59,7 @@ trait LabelsControllerBase extends ControllerBase {
// TODO futility // TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty), countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository, repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount)) hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
}) })
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository => ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository =>

View File

@@ -27,7 +27,7 @@ trait MilestonesControllerBase extends ControllerBase {
params.getOrElse("state", "open"), params.getOrElse("state", "open"),
getMilestonesWithIssueCount(repository.owner, repository.name), getMilestonesWithIssueCount(repository.owner, repository.name),
repository, repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount)) hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
}) })
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly { get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {

View File

@@ -11,10 +11,7 @@ import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.view
import gitbucket.core.view.helpers
import io.github.gitbucket.scalatra.forms._ import io.github.gitbucket.scalatra.forms._
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.PersonIdent import org.eclipse.jgit.lib.PersonIdent
@@ -115,7 +112,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val hasConflict = LockUtil.lock(s"${owner}/${name}"){ val hasConflict = LockUtil.lock(s"${owner}/${name}"){
checkConflict(owner, name, pullreq.branch, issueId) 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 branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
val mergeStatus = PullRequestService.MergeStatus( val mergeStatus = PullRequestService.MergeStatus(
hasConflict = hasConflict, hasConflict = hasConflict,
@@ -125,7 +122,7 @@ trait PullRequestsControllerBase extends ControllerBase {
needStatusCheck = context.loginAccount.map{ u => needStatusCheck = context.loginAccount.map{ u =>
branchProtection.needStatusCheck(u.userName) branchProtection.needStatusCheck(u.userName)
}.getOrElse(true), }.getOrElse(true),
hasUpdatePermission = hasWritePermission(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) && hasUpdatePermission = hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount) &&
context.loginAccount.map{ u => context.loginAccount.map{ u =>
!getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch).needStatusCheck(u.userName) !getProtectedBranchInfo(pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch).needStatusCheck(u.userName)
}.getOrElse(false), }.getOrElse(false),
@@ -156,24 +153,24 @@ trait PullRequestsControllerBase extends ControllerBase {
} getOrElse NotFound() } getOrElse NotFound()
}) })
post("/:owner/:repository/pull/:id/update_branch")(referrersOnly { baseRepository => post("/:owner/:repository/pull/:id/update_branch")(writableUsersOnly { baseRepository =>
(for { (for {
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
loginAccount <- context.loginAccount loginAccount <- context.loginAccount
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId) (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
owner = pullreq.requestUserName owner = pullreq.requestUserName
name = pullreq.requestRepositoryName name = pullreq.requestRepositoryName
if hasWritePermission(owner, name, context.loginAccount) if hasDeveloperRole(owner, name, context.loginAccount)
} yield { } yield {
val repository = getRepository(owner, name).get
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch) val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
if(branchProtection.needStatusCheck(loginAccount.userName)){ if(branchProtection.needStatusCheck(loginAccount.userName)){
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check." flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
} else { } else {
val repository = getRepository(owner, name).get
LockUtil.lock(s"${owner}/${name}"){ LockUtil.lock(s"${owner}/${name}"){
val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){ val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){
pullreq.branch pullreq.branch
}else{ } else {
s"${pullreq.userName}:${pullreq.branch}" s"${pullreq.userName}:${pullreq.branch}"
} }
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git => JGitUtil.getAllCommitIds(git) }.toSet 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 => using(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
// after update branch // after update branch
val newCommitId = git.getRepository.resolve(s"refs/heads/${pullreq.requestBranch}") 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 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)){ if(!existIds.contains(commit.id)){
createIssueComment(owner, name, commit) createIssueComment(owner, name, commit)
} }
@@ -220,8 +216,9 @@ trait PullRequestsControllerBase extends ControllerBase {
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}" 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() }) getOrElse NotFound()
}) })
@@ -374,7 +371,7 @@ trait PullRequestsControllerBase extends ControllerBase {
forkedRepository, forkedRepository,
originRepository, originRepository,
forkedRepository, forkedRepository,
hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount), hasDeveloperRole(originRepository.owner, originRepository.name, context.loginAccount),
getAssignableUserNames(originRepository.owner, originRepository.name), getAssignableUserNames(originRepository.owner, originRepository.name),
getMilestones(originRepository.owner, originRepository.name), getMilestones(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name) getLabels(originRepository.owner, originRepository.name)
@@ -389,7 +386,7 @@ trait PullRequestsControllerBase extends ControllerBase {
}) getOrElse NotFound() }) getOrElse NotFound()
}) })
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(writableUsersOnly { forkedRepository => ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(readableUsersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat") val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner) val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner) val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
@@ -498,26 +495,6 @@ trait PullRequestsControllerBase extends ControllerBase {
(defaultOwner, value) (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) = private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
defining(repository.owner, repository.name){ case (owner, repoName) => defining(repository.owner, repository.name){ case (owner, repoName) =>
val page = IssueSearchCondition.page(request) val page = IssueSearchCondition.page(request)
@@ -544,7 +521,7 @@ trait PullRequestsControllerBase extends ControllerBase {
* Tests whether an logged-in user can manage pull requests. * Tests whether an logged-in user can manage pull requests.
*/ */
private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = { 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 = { private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
repository.repository.options.issuesOption match { repository.repository.options.issuesOption match {
case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
case "DISABLE" => false case "DISABLE" => false
} }
} }

View File

@@ -39,7 +39,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
) )
val optionsForm = mapping( 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()))), "description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type" , boolean())), "isPrivate" -> trim(label("Repository Type" , boolean())),
"issuesOption" -> trim(label("Issues Option" , text(required, featureOption))), "issuesOption" -> trim(label("Issues Option" , text(required, featureOption))),
@@ -179,8 +179,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
val collaborators = params("collaborators") val collaborators = params("collaborators")
removeCollaborators(repository.owner, repository.name) removeCollaborators(repository.owner, repository.name)
collaborators.split(",").withFilter(_.nonEmpty).map { collaborator => collaborators.split(",").withFilter(_.nonEmpty).map { collaborator =>
val userName :: permission :: Nil = collaborator.split(":").toList val userName :: role :: Nil = collaborator.split(":").toList
addCollaborator(repository.owner, repository.name, userName, permission) addCollaborator(repository.owner, repository.name, userName, role)
} }
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") 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 dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token)
val dummyPayload = { val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get 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)) .add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(4) .setMaxCount(4)
.call.iterator.asScala.map(new CommitInfo(_)).toList .call.iterator.asScala.map(new CommitInfo(_)).toList
@@ -416,7 +416,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
*/ */
private def featureOption: Constraint = new Constraint(){ private def featureOption: Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = 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.")
} }

View File

@@ -102,16 +102,28 @@ trait RepositoryViewerControllerBase extends ControllerBase {
*/ */
post("/:owner/:repository/_preview")(referrersOnly { repository => post("/:owner/:repository/_preview")(referrersOnly { repository =>
contentType = "text/html" contentType = "text/html"
helpers.markdown( val filename = params.get("filename")
markdown = params("content"), filename match {
repository = repository, case Some(f) => helpers.renderMarkup(
enableWikiLink = params("enableWikiLink").toBoolean, filePath = List(f),
enableRefsLink = params("enableRefsLink").toBoolean, fileContent = params("content"),
enableLineBreaks = params("enableLineBreaks").toBoolean, branch = "master",
enableTaskList = params("enableTaskList").toBoolean, repository = repository,
enableAnchor = false, enableWikiLink = params("enableWikiLink").toBoolean,
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount) 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, html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) => logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) 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() case Left(_) => NotFound()
} }
} }
@@ -275,7 +287,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.blob(id, repository, path.split("/").toList, html.blob(id, repository, path.split("/").toList,
JGitUtil.getContentInfo(git, path, objectId), JGitUtil.getContentInfo(git, path, objectId),
new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)), 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") request.paths(2) == "blame")
} }
} getOrElse NotFound() } getOrElse NotFound()
@@ -328,8 +340,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.commit(id, new JGitUtil.CommitInfo(revCommit), html.commit(id, new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName), JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName), JGitUtil.getTagsOfCommit(git, revCommit.getName),
getCommitComments(repository.owner, repository.name, id, false), getCommitComments(repository.owner, repository.name, id, true),
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount)) repository, diffs, oldCommitId, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
} }
} }
} }
@@ -358,7 +370,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.commentform( html.commentform(
commitId = id, commitId = id,
fileName, oldLineNumber, newLineNumber, issueId, fileName, oldLineNumber, newLineNumber, issueId,
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount), hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
repository = repository repository = repository
) )
}) })
@@ -374,7 +386,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get) callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get)
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content) 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 => ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
@@ -393,7 +405,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
enableRefsLink = true, enableRefsLink = true,
enableAnchor = true, enableAnchor = true,
enableLineBreaks = 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))) .map(br => (br, getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId), protectedBranches.contains(br.name)))
.reverse .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 * @return HTML of the file list
*/ */
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = { private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
if(repository.commitCount == 0){ using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount)) if(JGitUtil.isEmpty(git)){
} else { html.guide(repository, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => } else {
// get specified commit // get specified commit
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) => JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit => defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
@@ -569,9 +581,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.files(revision, repository, html.files(revision, repository,
if(path == ".") Nil else path.split("/").toList, // current path if(path == ".") Nil else path.split("/").toList, // current path
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit 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), getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),
flash.get("info"), flash.get("error")) flash.get("info"),
flash.get("error")
)
} }
} getOrElse NotFound() } getOrElse NotFound()
} }
@@ -691,7 +708,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean = 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 = { override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
e.printStackTrace() e.printStackTrace()

View File

@@ -242,9 +242,9 @@ trait WikiControllerBase extends ControllerBase {
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
repository.repository.options.wikiOption match { repository.repository.options.wikiOption match {
// case "ALL" => repository.repository.isPrivate == false || hasReadPermission(repository.owner, repository.name, context.loginAccount) case "ALL" => !repository.repository.isPrivate && context.loginAccount.isDefined
case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) case "PUBLIC" => hasGuestRole(repository.owner, repository.name, context.loginAccount)
case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) case "PRIVATE" => hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
case "DISABLE" => false case "DISABLE" => false
} }
} }

View File

@@ -7,8 +7,8 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate { class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
val collaboratorName = column[String]("COLLABORATOR_NAME") val collaboratorName = column[String]("COLLABORATOR_NAME")
val permission = column[String]("PERMISSION") val role = column[String]("ROLE")
def * = (userName, repositoryName, collaboratorName, permission) <> (Collaborator.tupled, Collaborator.unapply) def * = (userName, repositoryName, collaboratorName, role) <> (Collaborator.tupled, Collaborator.unapply)
def byPrimaryKey(owner: String, repository: String, collaborator: String) = def byPrimaryKey(owner: String, repository: String, collaborator: String) =
byRepository(owner, repository) && (collaboratorName === collaborator.bind) byRepository(owner, repository) && (collaboratorName === collaborator.bind)
@@ -19,15 +19,15 @@ case class Collaborator(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
collaboratorName: String, collaboratorName: String,
permission: String role: String
) )
sealed abstract class Permission(val name: String) sealed abstract class Role(val name: String)
object Permission { object Role {
object ADMIN extends Permission("ADMIN") object ADMIN extends Role("ADMIN")
object WRITE extends Permission("WRITE") object DEVELOPER extends Role("DEVELOPER")
object READ extends Permission("READ") object GUEST extends Role("GUEST")
// val values: Vector[Permission] = Vector(ADMIN, WRITE, READ) // val values: Vector[Permission] = Vector(ADMIN, WRITE, READ)
// //

View File

@@ -23,5 +23,5 @@ class UserNameSuggestionProvider extends SuggestionProvider {
override def values(repository: RepositoryInfo): Seq[String] = Nil override def values(repository: RepositoryInfo): Seq[String] = Nil
override def template(implicit context: Context): String = "'@' + value" override def template(implicit context: Context): String = "'@' + value"
override def additionalScript(implicit context: Context): String = 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; });"""
} }

View File

@@ -37,6 +37,12 @@ trait CommitsService {
updatedDate = currentDate, updatedDate = currentDate,
issueId = issueId) 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) = { def updateCommitComment(commentId: Int, content: String)(implicit s: Session) = {
CommitComments CommitComments
.filter (_.byPrimaryKey(commentId)) .filter (_.byPrimaryKey(commentId))

View File

@@ -10,6 +10,7 @@ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.model.Profile.dateColumnType
trait IssuesService { trait IssuesService {
self: AccountService with RepositoryService => self: AccountService with RepositoryService =>
import IssuesService._ import IssuesService._
@@ -31,6 +32,10 @@ trait IssuesService {
.map{ case ((t1, t2), t3) => (t1, t2, t3) } .map{ case ((t1, t2), t3) => (t1, t2, t3) }
.list .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) = def getComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
if (commentId forall (_.isDigit)) if (commentId forall (_.isDigit))
IssueComments filter { t => IssueComments filter { t =>
@@ -388,8 +393,8 @@ trait IssuesService {
} }
def getAssignableUserNames(owner: String, repository: String)(implicit s: Session): List[String] = { def getAssignableUserNames(owner: String, repository: String)(implicit s: Session): List[String] = {
(getCollaboratorUserNames(owner, repository, Seq(Permission.ADMIN, Permission.WRITE)) ::: (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)) :::
(if (getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).sorted (if (getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).distinct.sorted
} }
} }

View File

@@ -87,7 +87,7 @@ object ProtectedBranchService {
def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)(implicit session: Session): Option[String] = { def getStopReason(isAllowNonFastForwards: Boolean, command: ReceiveCommand, pusher: String)(implicit session: Session): Option[String] = {
if(enabled){ if(enabled){
command.getType() match { 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") Some("Cannot force-push to a protected branch")
case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) => case ReceiveCommand.Type.UPDATE|ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
unSuccessedContexts(command.getNewId.name) match { unSuccessedContexts(command.getNewId.name) match {
@@ -99,7 +99,7 @@ object ProtectedBranchService {
Some("Cannot delete a protected branch") Some("Cannot delete a protected branch")
case _ => None case _ => None
} }
}else{ } else {
None None
} }
} }

View File

@@ -1,12 +1,24 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.{Issue, PullRequest, CommitStatus, CommitState} import gitbucket.core.model.{Issue, PullRequest, CommitStatus, CommitState}
import gitbucket.core.util.JGitUtil
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._ 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._ import PullRequestService._
def getPullRequest(owner: String, repository: String, issueId: Int) 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 = def updatePullRequests(owner: String, repository: String, branch: String)(implicit s: Session): Unit =
getPullRequestsByRequest(owner, repository, branch, false).foreach { pullreq => 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){
//if(Repositories.filter(_.byRepository(pullreq.userName, pullreq.repositoryName)).exists.run){ // Update the git repository
val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest( val (commitIdTo, commitIdFrom) = JGitUtil.updatePullRequest(
pullreq.userName, pullreq.repositoryName, pullreq.branch, pullreq.issueId, pullreq.userName, pullreq.repositoryName, pullreq.branch, pullreq.issueId,
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.requestBranch) 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) updateCommitId(pullreq.userName, pullreq.repositoryName, pullreq.issueId, commitIdTo, commitIdFrom)
} }
} }
@@ -138,6 +166,78 @@ trait PullRequestService { self: IssuesService =>
.firstOption .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 { object PullRequestService {

View File

@@ -2,7 +2,7 @@ package gitbucket.core.service
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.util.JGitUtil 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._
import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._ 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. * Include public repository, private own repository and private but collaborator repository.
* *
* @param userName the user name of collaborator * @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)] = { def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = {
Repositories.filter { t1 => Repositories.filter { t1 =>
(t1.isPrivate === false.bind) || (t1.isPrivate === false.bind) ||
(t1.userName === userName.bind) || (t1.userName === userName.bind) || (t1.userName in (GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) ||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists) (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 => }.sortBy(_.lastActivityDate desc).map{ t =>
(t.userName, t.repositoryName) (t.userName, t.repositoryName)
}.list }.list
@@ -248,8 +250,10 @@ trait RepositoryService { self: AccountService =>
def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)(implicit s: Session): List[RepositoryInfo] = { def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)(implicit s: Session): List[RepositoryInfo] = {
Repositories.filter { t1 => Repositories.filter { t1 =>
(t1.userName === userName.bind) || (t1.userName === userName.bind) || (t1.userName in (GroupMembers.filter(_.userName === userName.bind).map(_.groupName))) ||
(Collaborators.filter { t2 => t2.byRepository(t1.userName, t1.repositoryName) && (t2.collaboratorName === userName.bind)} exists) (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 => }.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo( new RepositoryInfo(
if(withoutPhysicalInfo){ if(withoutPhysicalInfo){
@@ -284,8 +288,13 @@ trait RepositoryService { self: AccountService =>
case Some(x) if(x.isAdmin) => Repositories case Some(x) if(x.isAdmin) => Repositories
// for Normal Users // for Normal Users
case Some(x) if(!x.isAdmin) => case Some(x) if(!x.isAdmin) =>
Repositories filter { t => (t.isPrivate === false.bind) || (t.userName === x.userName) || Repositories filter { t =>
(Collaborators.filter { t2 => t2.byRepository(t.userName, t.repositoryName) && (t2.collaboratorName === x.userName.bind)} exists) (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 // for Guests
case None => Repositories filter(_.isPrivate === false.bind) case None => Repositories filter(_.isPrivate === false.bind)
@@ -342,8 +351,8 @@ trait RepositoryService { self: AccountService =>
/** /**
* Add collaborator (user or group) to the repository. * Add collaborator (user or group) to the repository.
*/ */
def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, permission: String)(implicit s: Session): Unit = def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, role: String)(implicit s: Session): Unit =
Collaborators insert Collaborator(userName, repositoryName, collaboratorName, permission) Collaborators insert Collaborator(userName, repositoryName, collaboratorName, role)
/** /**
* Remove all collaborators from the repository. * 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. * 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. * 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 val q1 = Collaborators
.join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) } .join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) }
.filter { case (t1, t2) => t1.byRepository(userName, repositoryName) } .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 val q2 = Collaborators
.join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) } .join(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) }
.join(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName } .join(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName }
.filter { case ((t1, t2), t3) => t1.byRepository(userName, repositoryName) } .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) 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 { loginAccount match {
case Some(a) if(a.isAdmin) => true case Some(a) if(a.isAdmin) => true
case Some(a) if(a.userName == owner) => true case Some(a) if(a.userName == owner) => true
case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => 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 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 { loginAccount match {
case Some(a) if(a.isAdmin) => true case Some(a) if(a.isAdmin) => true
case Some(a) if(a.userName == owner) => true case Some(a) if(a.userName == owner) => true
case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => 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 case _ => false
} }
} }
@@ -419,26 +428,20 @@ trait RepositoryService { self: AccountService =>
object RepositoryService { object RepositoryService {
case class RepositoryInfo(owner: String, name: String, repository: Repository, 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]) { branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]) {
/** /**
* Creates instance with issue count and pull request count. * Creates instance with issue count and pull request count.
*/ */
def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) = def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) =
this( this(repo.owner, repo.name, model, issueCount, pullCount, forkedCount, repo.branchList, repo.tags, managers)
repo.owner, repo.name, model,
issueCount, pullCount, repo.commitCount, forkedCount,
repo.branchList, repo.tags, managers)
/** /**
* Creates instance without issue count and pull request count. * Creates instance without issue count and pull request count.
*/ */
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) = def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) =
this( this(repo.owner, repo.name, model, 0, 0, forkedCount, repo.branchList, repo.tags, managers)
repo.owner, repo.name, model,
0, 0, repo.commitCount, forkedCount,
repo.branchList, repo.tags, managers)
def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name) def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name)
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(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("/")) (id, path.substring(id.length).stripPrefix("/"))
} }
} }
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git" def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"

View File

@@ -1,9 +1,11 @@
package gitbucket.core.service package gitbucket.core.service
import java.util.Date
import fr.brouillard.oss.security.xhub.XHub 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.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._
import gitbucket.core.model.Profile.profile._ import gitbucket.core.model.Profile.profile._
import gitbucket.core.model.Profile.profile.blockingApi._ 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.api.Git
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import scala.concurrent._ import scala.concurrent._
import scala.util.{Success, Failure} import scala.util.{Success, Failure}
import org.apache.http.HttpRequest 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])] = def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(WebHook, Set[WebHook.Event])] =
WebHooks.filter(_.byRepository(owner, repository)) WebHooks.filter(_.byRepository(owner, repository))
.join(WebHookEvents).on { (w, t) => t.byWebHook(w) } .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) .list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url)
/** get All WebHook informations of repository event */ /** get All WebHook informations of repository event */
def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] = def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] =
WebHooks.filter(_.byRepository(owner, repository)) WebHooks.filter(_.byRepository(owner, repository))
.join(WebHookEvents).on { (wh, whe) => whe.byWebHook(wh) } .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 } .map{ case (wh, whe) => wh }
.list.distinct .list.distinct
@@ -51,12 +54,12 @@ trait WebHookService {
WebHooks WebHooks
.filter(_.byPrimaryKey(owner, repository, url)) .filter(_.byPrimaryKey(owner, repository, url))
.join(WebHookEvents).on { (w, t) => t.byWebHook(w) } .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 .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) 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) 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 = { 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)) WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token))
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete 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) WebHookEvents insert WebHookEvent(owner, repository, url, event)
} }
} }
@@ -83,7 +86,7 @@ trait WebHookService {
def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload) def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload)
(implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = { (implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = {
import org.apache.http.impl.client.HttpClientBuilder 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.protocol.HttpContext
import org.apache.http.client.methods.HttpPost import org.apache.http.client.methods.HttpPost
@@ -93,7 +96,7 @@ trait WebHookService {
webHooks.map { webHook => webHooks.map { webHook =>
val reqPromise = Promise[HttpRequest] val reqPromise = Promise[HttpRequest]
val f = Future { val f = Future {
val itcp = new org.apache.http.HttpRequestInterceptor{ val itcp = new org.apache.http.HttpRequestInterceptor {
def process(res: HttpRequest, ctx: HttpContext): Unit = { def process(res: HttpRequest, ctx: HttpContext): Unit = {
reqPromise.success(res) reqPromise.success(res)
} }
@@ -132,7 +135,7 @@ trait WebHookService {
logger.debug(s"end web hook invocation for ${webHook}") logger.debug(s"end web hook invocation for ${webHook}")
res res
} catch { } catch {
case e:Throwable => { case e: Throwable => {
if(!reqPromise.isCompleted){ if(!reqPromise.isCompleted){
reqPromise.failure(e) reqPromise.failure(e)
} }
@@ -168,11 +171,11 @@ trait WebHookPullRequestService extends WebHookService {
issueUser <- users.get(issue.openedUserName) issueUser <- users.get(issue.openedUserName)
} yield { } yield {
WebHookIssuesPayload( WebHookIssuesPayload(
action = action, action = action,
number = issue.issueId, number = issue.issueId,
repository = ApiRepository(repository, ApiUser(repoOwner)), repository = ApiRepository(repository, ApiUser(repoOwner)),
issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)), issue = ApiIssue(issue, RepositoryName(repository), ApiUser(issueUser)),
sender = ApiUser(sender)) sender = ApiUser(sender))
} }
} }
} }
@@ -198,7 +201,9 @@ trait WebHookPullRequestService extends WebHookService {
headOwner = headOwner, headOwner = headOwner,
baseRepository = repository, baseRepository = repository,
baseOwner = baseOwner, baseOwner = baseOwner,
sender = sender) sender = sender,
mergedComment = getMergedComment(repository.owner, repository.name, issueId)
)
} }
} }
} }
@@ -237,7 +242,10 @@ trait WebHookPullRequestService extends WebHookService {
headOwner = headOwner, headOwner = headOwner,
baseRepository = baseRepo, baseRepository = baseRepo,
baseOwner = baseOwner, baseOwner = baseOwner,
sender = sender) sender = sender,
mergedComment = getMergedComment(baseRepo.owner, baseRepo.name, issue.issueId)
)
callWebHook(WebHook.PullRequest, webHooks, payload) callWebHook(WebHook.PullRequest, webHooks, payload)
} }
} }
@@ -267,7 +275,9 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
headOwner = headOwner, headOwner = headOwner,
baseRepository = repository, baseRepository = repository,
baseOwner = baseOwner, baseOwner = baseOwner,
sender = sender) sender = sender,
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
} }
} }
} }
@@ -365,11 +375,21 @@ object WebHookService {
headOwner: Account, headOwner: Account,
baseRepository: RepositoryInfo, baseRepository: RepositoryInfo,
baseOwner: Account, baseOwner: Account,
sender: Account): WebHookPullRequestPayload = { sender: Account,
mergedComment: Option[(IssueComment, Account)]): WebHookPullRequestPayload = {
val headRepoPayload = ApiRepository(headRepository, headOwner) val headRepoPayload = ApiRepository(headRepository, headOwner)
val baseRepoPayload = ApiRepository(baseRepository, baseOwner) val baseRepoPayload = ApiRepository(baseRepository, baseOwner)
val senderPayload = ApiUser(sender) 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( WebHookPullRequestPayload(
action = action, action = action,
number = issue.issueId, number = issue.issueId,
@@ -389,7 +409,7 @@ object WebHookService {
sender: ApiUser sender: ApiUser
) extends WebHookPayload ) extends WebHookPayload
object WebHookIssueCommentPayload{ object WebHookIssueCommentPayload {
def apply( def apply(
issue: Issue, issue: Issue,
issueUser: Account, issueUser: Account,
@@ -415,28 +435,42 @@ object WebHookService {
sender: ApiUser sender: ApiUser
) extends WebHookPayload ) extends WebHookPayload
object WebHookPullRequestReviewCommentPayload{ object WebHookPullRequestReviewCommentPayload {
def apply( def apply(
action: String, action: String,
comment: CommitComment, comment: CommitComment,
issue: Issue, issue: Issue,
issueUser: Account, issueUser: Account,
pullRequest: PullRequest, pullRequest: PullRequest,
headRepository: RepositoryInfo, headRepository: RepositoryInfo,
headOwner: Account, headOwner: Account,
baseRepository: RepositoryInfo, baseRepository: RepositoryInfo,
baseOwner: Account, baseOwner: Account,
sender: Account sender: Account,
) : WebHookPullRequestReviewCommentPayload = { mergedComment: Option[(IssueComment, Account)]
) : WebHookPullRequestReviewCommentPayload = {
val headRepoPayload = ApiRepository(headRepository, headOwner) val headRepoPayload = ApiRepository(headRepository, headOwner)
val baseRepoPayload = ApiRepository(baseRepository, baseOwner) val baseRepoPayload = ApiRepository(baseRepository, baseOwner)
val senderPayload = ApiUser(sender) val senderPayload = ApiUser(sender)
WebHookPullRequestReviewCommentPayload( WebHookPullRequestReviewCommentPayload(
action = action, action = action,
comment = ApiPullRequestReviewComment(comment, senderPayload, RepositoryName(baseRepository), issue.issueId), comment = ApiPullRequestReviewComment(
pull_request = ApiPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, ApiUser(issueUser)), comment = comment,
repository = baseRepoPayload, commentedUser = senderPayload,
sender = 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)
} }
} }
} }

View File

@@ -84,7 +84,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password) account <- authenticate(settings, username, password)
} yield if(isUpdating || repository.repository.isPrivate){ } 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) request.setAttribute(Keys.Request.UserName, account.userName)
true true
} else false } else false

View File

@@ -110,7 +110,7 @@ import scala.collection.JavaConverters._
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session) class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String)(implicit session: Session)
extends PostReceiveHook with PreReceiveHook extends PostReceiveHook with PreReceiveHook
with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService 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 val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
private var existIds: Seq[String] = Nil 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 = { def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
try { try {
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git => using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
JGitUtil.removeCache(git)
val pushedIds = scala.collection.mutable.Set[String]() val pushedIds = scala.collection.mutable.Set[String]()
commands.asScala.foreach { command => commands.asScala.foreach { command =>
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}") logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")

View File

@@ -93,7 +93,7 @@ abstract class DefaultGitCommand(val owner: String, val repoName: String) extend
protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo) protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo)
(implicit session: Session): Boolean = (implicit session: Session): Boolean =
getAccountByUserName(username) match { 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 case None => false
} }

View File

@@ -2,13 +2,11 @@ package gitbucket.core.util
import gitbucket.core.controller.ControllerBase import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{AccountService, RepositoryService} import gitbucket.core.service.{AccountService, RepositoryService}
import gitbucket.core.model.Permission import gitbucket.core.model.Role
import RepositoryService.RepositoryInfo import RepositoryService.RepositoryInfo
import Implicits._ import Implicits._
import ControlUtil._ import ControlUtil._
import scala.collection.Searching.search
/** /**
* Allows only oneself and administrators. * 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) case Some(x) if(repository.owner == x.userName) => action(repository)
// TODO Repository management is allowed for only group managers? // 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(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() case _ => Unauthorized()
} }
} getOrElse NotFound() } 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(x.isAdmin) => action(repository)
case Some(x) if(paths(0) == x.userName) => 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(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() case _ => Unauthorized()
} }
} getOrElse NotFound() } getOrElse NotFound()

View File

@@ -9,7 +9,10 @@ import scala.collection.mutable.ListBuffer
/** /**
* Provides implicit class which extends java.sql.Connection. * 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 { object JDBCUtil {
@@ -71,8 +74,6 @@ object JDBCUtil {
val bytes = new scala.Array[Byte](1024 * 8) val bytes = new scala.Array[Byte](1024 * 8)
var stringLiteral = false var stringLiteral = false
var count = 0
while({ length = in.read(bytes); length != -1 }){ while({ length = in.read(bytes); length != -1 }){
for(i <- 0 to length - 1){ for(i <- 0 to length - 1){
val c = bytes(i) val c = bytes(i)
@@ -81,13 +82,19 @@ object JDBCUtil {
} }
if(c == ';' && !stringLiteral){ if(c == ';' && !stringLiteral){
val sql = new String(out.toByteArray, "UTF-8") val sql = new String(out.toByteArray, "UTF-8")
conn.update(sql) conn.update(sql.trim)
out = new ByteArrayOutputStream() out = new ByteArrayOutputStream()
} else { } else {
out.write(c) out.write(c)
} }
} }
} }
val remain = out.toByteArray
if(remain.length != 0){
val sql = new String(remain, "UTF-8")
conn.update(sql.trim)
}
} }
conn.commit() conn.commit()

View File

@@ -5,6 +5,7 @@ import org.eclipse.jgit.api.Git
import Directory._ import Directory._
import StringUtil._ import StringUtil._
import ControlUtil._ import ControlUtil._
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
import org.eclipse.jgit.lib._ 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.errors.{ConfigInvalidException, MissingObjectException}
import org.eclipse.jgit.transport.RefSpec import org.eclipse.jgit.transport.RefSpec
import java.util.Date 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.eclipse.jgit.dircache.DirCacheEntry
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@@ -32,14 +37,11 @@ object JGitUtil {
* *
* @param owner the user name of the repository owner * @param owner the user name of the repository owner
* @param name the repository name * @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 branchList the list of branch names
* @param tags the list of tags * @param tags the list of tags
*/ */
case class RepositoryInfo(owner: String, name: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){ case class RepositoryInfo(owner: String, name: String, branchList: List[String], tags: List[TagInfo]){
def this(owner: String, name: String) = { def this(owner: String, name: String) = this(owner, name, Nil, Nil)
this(owner, name, 0, Nil, Nil)
}
} }
/** /**
@@ -169,20 +171,54 @@ object JGitUtil {
revWalk.dispose revWalk.dispose
revCommit 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. * Returns the repository information. It contains branch names and tag names.
*/ */
def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = { def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = {
using(Git.open(getRepositoryDir(owner, repository))){ git => using(Git.open(getRepositoryDir(owner, repository))){ git =>
try { try {
// get commit count RepositoryInfo(owner, repository,
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10001).sum
RepositoryInfo(
owner, repository,
// commit count
commitCount,
// branches // branches
git.branchList.call.asScala.map { ref => git.branchList.call.asScala.map { ref =>
ref.getName.stripPrefix("refs/heads/") ref.getName.stripPrefix("refs/heads/")
@@ -195,9 +231,7 @@ object JGitUtil {
) )
} catch { } catch {
// not initialized // not initialized
case e: NoHeadException => RepositoryInfo( case e: NoHeadException => RepositoryInfo(owner, repository, Nil, Nil)
owner, repository, 0, Nil, Nil)
} }
} }
} }
@@ -212,8 +246,8 @@ object JGitUtil {
*/ */
def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = { def getFileList(git: Git, revision: String, path: String = "."): List[FileInfo] = {
using(new RevWalk(git.getRepository)){ revWalk => using(new RevWalk(git.getRepository)){ revWalk =>
val objectId = git.getRepository.resolve(revision) val objectId = git.getRepository.resolve(revision)
if(objectId==null) return Nil if(objectId == null) return Nil
val revCommit = revWalk.parseCommit(objectId) val revCommit = revWalk.parseCommit(objectId)
def useTreeWalk(rev:RevCommit)(f:TreeWalk => Any): Unit = if (path == ".") { 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)] ={ revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, Option[String], RevCommit)] ={
if(restList.isEmpty){ if(restList.isEmpty){
result result
}else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty } else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty
result ++ restList.map{ case (tuple, map) => tupleAdd(tuple, map.values.headOption.getOrElse(revCommit)) } result ++ restList.map { case (tuple, map) => tupleAdd(tuple, map.values.headOption.getOrElse(revCommit)) }
}else{ } else {
val newCommit = revIterator.next 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){ if(thisTimeChecks.isEmpty){
findLastCommits(result, restList, revIterator) findLastCommits(result, restList, revIterator)
}else{ } else {
var nextRest = skips var nextRest = skips
var nextResult = result var nextResult = result
// Map[(name, oid), (tuple, parentsMap)] // Map[(name, oid), (tuple, parentsMap)]
@@ -270,20 +304,20 @@ object JGitUtil {
lazy val newParentsMap = newCommit.getParents.map(_ -> newCommit).toMap lazy val newParentsMap = newCommit.getParents.map(_ -> newCommit).toMap
useTreeWalk(newCommit){ walk => useTreeWalk(newCommit){ walk =>
while(walk.next){ 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){ if(newParentsMap.isEmpty){
nextResult +:= tupleAdd(tuple, newCommit) nextResult +:= tupleAdd(tuple, newCommit)
}else{ } else {
nextRest +:= tuple -> newParentsMap nextRest +:= tuple -> newParentsMap
} }
} }
} }
} }
rest.values.map{ case (tuple, parentsMap) => rest.values.map { case (tuple, parentsMap) =>
val restParentsMap = parentsMap - newCommit val restParentsMap = parentsMap - newCommit
if(restParentsMap.isEmpty){ if(restParentsMap.isEmpty){
nextResult +:= tupleAdd(tuple, parentsMap(newCommit)) nextResult +:= tupleAdd(tuple, parentsMap(newCommit))
}else{ } else {
nextRest +:= tuple -> restParentsMap nextRest +:= tuple -> restParentsMap
} }
} }
@@ -295,7 +329,7 @@ object JGitUtil {
var fileList: List[(ObjectId, FileMode, String, Option[String])] = Nil var fileList: List[(ObjectId, FileMode, String, Option[String])] = Nil
useTreeWalk(revCommit){ treeWalk => useTreeWalk(revCommit){ treeWalk =>
while (treeWalk.next()) { 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) getSubmodules(git, revCommit.getTree).find(_.path == treeWalk.getPathString).map(_.url)
} else None } else None
fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getNameString, linkUrl) fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(0), treeWalk.getNameString, linkUrl)
@@ -345,7 +379,7 @@ object JGitUtil {
def getTreeId(git: Git, revision: String): Option[String] = { def getTreeId(git: Git, revision: String): Option[String] = {
using(new RevWalk(git.getRepository)){ revWalk => using(new RevWalk(git.getRepository)){ revWalk =>
val objectId = git.getRepository.resolve(revision) val objectId = git.getRepository.resolve(revision)
if(objectId==null) return None if(objectId == null) return None
val revCommit = revWalk.parseCommit(objectId) val revCommit = revWalk.parseCommit(objectId)
Some(revCommit.getTree.name) Some(revCommit.getTree.name)
} }
@@ -357,7 +391,7 @@ object JGitUtil {
def getAllFileListByTreeId(git: Git, treeId: String): List[String] = { def getAllFileListByTreeId(git: Git, treeId: String): List[String] = {
using(new RevWalk(git.getRepository)){ revWalk => using(new RevWalk(git.getRepository)){ revWalk =>
val objectId = git.getRepository.resolve(treeId+"^{tree}") val objectId = git.getRepository.resolve(treeId+"^{tree}")
if(objectId==null) return Nil if(objectId == null) return Nil
using(new TreeWalk(git.getRepository)){ treeWalk => using(new TreeWalk(git.getRepository)){ treeWalk =>
treeWalk.addTree(objectId) treeWalk.addTree(objectId)
treeWalk.setRecursive(true) treeWalk.setRecursive(true)
@@ -705,6 +739,8 @@ object JGitUtil {
refUpdate.setNewObjectId(newHeadId) refUpdate.setNewObjectId(newHeadId)
refUpdate.update() refUpdate.update()
removeCache(git)
newHeadId newHeadId
} }
@@ -877,6 +913,7 @@ object JGitUtil {
/** /**
* Returns the last modified commit of specified path * Returns the last modified commit of specified path
*
* @param git the Git object * @param git the Git object
* @param startCommit the search base commit id * @param startCommit the search base commit id
* @param path the path of target file or directory * @param path the path of target file or directory
@@ -959,6 +996,7 @@ object JGitUtil {
/** /**
* Returns sha1 * Returns sha1
*
* @param owner repository owner * @param owner repository owner
* @param name repository name * @param name repository name
* @param revstr A git object references expression * @param revstr A git object references expression

View File

@@ -109,6 +109,9 @@ class Mailer(private val smtp: Smtp) extends Notifier {
} }
smtp.ssl.foreach { ssl => smtp.ssl.foreach { ssl =>
email.setSSLOnConnect(ssl) email.setSSLOnConnect(ssl)
if(ssl == true) {
email.setSslSmtpPort(smtp.port.get.toString)
}
} }
smtp.fromAddress smtp.fromAddress
.map (_ -> smtp.fromName.getOrElse(context.loginAccount.get.userName)) .map (_ -> smtp.fromName.getOrElse(context.loginAccount.get.userName))

View File

@@ -73,7 +73,7 @@ trait LinkConverter { self: RequestCache =>
} }
// convert issue id to link // convert issue id to link
.replaceBy(("(?<=(^|\\W))(GH-|" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r){ m => .replaceBy(("(?<=(^|\\W))(GH-|(?<!&)" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r){ m =>
val prefix = if(m.group(2) == "issue:") "#" else m.group(2) val prefix = if(m.group(2) == "issue:") "#" else m.group(2)
getIssue(repository.owner, repository.name, m.group(3)) match { getIssue(repository.owner, repository.name, m.group(3)) match {
case Some(issue) if(issue.isPullRequest) => case Some(issue) if(issue.isPullRequest) =>

View File

@@ -44,7 +44,8 @@ object Markdown {
val renderer = new GitBucketMarkedRenderer(options, repository, val renderer = new GitBucketMarkedRenderer(options, repository,
enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages) 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 = { override def text(text: String): String = {
// convert commit id and username to link. // convert commit id and username to link.
val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "#", false) else text val t1 = if(enableRefsLink) convertRefsLinks(text, repository, "#", false) else text
// convert task list to checkbox. // convert task list to checkbox.
val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1 val t2 = if(enableTaskList) convertCheckBox(t1, hasWritePermission) else t1
// decorate by TextDecorator plugins
t2 helpers.decorateHtml(t2, repository)
} }
override def link(href: String, title: String, text: String): String = { override def link(href: String, title: String, text: String): String = {

View File

@@ -19,8 +19,8 @@
</div> </div>
<a href="@context.path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a> <a href="@context.path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
<div style="width: 50%;"> <div style="width: 50%;">
@gitbucket.core.helper.html.copy("generated-token-copy", tokenString){ @gitbucket.core.helper.html.copy("generated-token", "generated-token-copy", tokenString){
<input type="text" value="@tokenString" class="form-control input-sm" readonly> <input type="text" value="@tokenString" class="form-control input-sm" id="generated-token" readonly>
} }
</div> </div>
<hr style="margin-top: 10px;"> <hr style="margin-top: 10px;">

View File

@@ -43,10 +43,10 @@
<fieldset class="border-top"> <fieldset class="border-top">
@if(account.isDefined){ @if(account.isDefined){
<div class="pull-right"> <div class="pull-right">
<a href="@helpers.url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a> <a href="@helpers.url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete group</a>
</div> </div>
} }
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/> <input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create group} else {Update group}"/>
@if(account.isDefined){ @if(account.isDefined){
<a href="@helpers.url(account.get.userName)" class="btn btn-default">Cancel</a> <a href="@helpers.url(account.get.userName)" class="btn btn-default">Cancel</a>
} }
@@ -136,4 +136,4 @@ $(function(){
$('#members').val(members); $('#members').val(members);
} }
}); });
</script> </script>

View File

@@ -38,7 +38,7 @@
@if(account.isGroupAccount){ @if(account.isGroupAccount){
<li@if(active == "members"){ class="active"}><a href="@helpers.url(account.userName)?tab=members">Members</a></li> <li@if(active == "members"){ class="active"}><a href="@helpers.url(account.userName)?tab=members">Members</a></li>
} else { } else {
<li@if(active == "activity"){ class="active"}><a href="@helpers.url(account.userName)?tab=activity">Public Activity</a></li> <li@if(active == "activity"){ class="active"}><a href="@helpers.url(account.userName)?tab=activity">Public activity</a></li>
} }
@gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab => @gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab =>
@tab(account, context).map { link => @tab(account, context).map { link =>
@@ -48,14 +48,14 @@
@if(context.loginAccount.isDefined && context.loginAccount.get.userName == account.userName){ @if(context.loginAccount.isDefined && context.loginAccount.get.userName == account.userName){
<li class="pull-right"> <li class="pull-right">
<div class="button-group"> <div class="button-group">
<a href="@helpers.url(account.userName)/_edit" class="btn btn-default">Edit Your Profile</a> <a href="@helpers.url(account.userName)/_edit" class="btn btn-default">Edit your profile</a>
</div> </div>
</li> </li>
} }
@if(context.loginAccount.isDefined && account.isGroupAccount && isGroupManager){ @if(context.loginAccount.isDefined && account.isGroupAccount && isGroupManager){
<li class="pull-right"> <li class="pull-right">
<div class="button-group"> <div class="button-group">
<a href="@helpers.url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a> <a href="@helpers.url(account.userName)/_editgroup" class="btn btn-default">Edit group</a>
</div> </div>
</li> </li>
} }

View File

@@ -3,10 +3,10 @@
<div class="sidebar"> <div class="sidebar">
<ul class="sidebar-menu" id="system-admin-menu-container"> <ul class="sidebar-menu" id="system-admin-menu-container">
<li@if(active=="users"){ class="active"}> <li@if(active=="users"){ class="active"}>
<a href="@context.path/admin/users">User Management</a> <a href="@context.path/admin/users">User management</a>
</li> </li>
<li@if(active=="system"){ class="active"}> <li@if(active=="system"){ class="active"}>
<a href="@context.path/admin/system">System Settings</a> <a href="@context.path/admin/system">System settings</a>
</li> </li>
<li@if(active=="plugins"){ class="active"}> <li@if(active=="plugins"){ class="active"}>
<a href="@context.path/admin/plugins">Plugins</a> <a href="@context.path/admin/plugins">Plugins</a>
@@ -15,7 +15,7 @@
<a href="@context.path/admin/data">Data export / import</a> <a href="@context.path/admin/data">Data export / import</a>
</li> </li>
<li> <li>
<a href="@context.path/console/login.jsp" target="_blank">H2 Console</a> <a href="@context.path/console/login.jsp" target="_blank">H2 console</a>
</li> </li>
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu => @gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
@menu(context).map { link => @menu(context).map { link =>

View File

@@ -1,10 +1,10 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context) @(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("System Settings"){ @gitbucket.core.html.main("System settings"){
@gitbucket.core.admin.html.menu("system"){ @gitbucket.core.admin.html.menu("system"){
@gitbucket.core.helper.html.information(info) @gitbucket.core.helper.html.information(info)
<form action="@context.path/admin/system" method="POST" validate="true" class="form-horizontal"> <form action="@context.path/admin/system" method="POST" validate="true" class="form-horizontal">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading strong">System Settings</div> <div class="panel-heading strong">System settings</div>
<div class="panel-body"> <div class="panel-body">
<!--====================================================================--> <!--====================================================================-->
<!-- System properties --> <!-- System properties -->

View File

@@ -1,5 +1,5 @@
@(account: Option[gitbucket.core.model.Account], error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context) @(account: Option[gitbucket.core.model.Account], error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main(if(account.isEmpty) "New User" else "Update User"){ @gitbucket.core.html.main(if(account.isEmpty) "New user" else "Update user"){
@gitbucket.core.admin.html.menu("users"){ @gitbucket.core.admin.html.menu("users"){
@gitbucket.core.helper.html.error(error) @gitbucket.core.helper.html.error(error)
<form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newuser} else {@context.path/admin/users/@account.get.userName/_edituser}" validate="true"> <form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newuser} else {@context.path/admin/users/@account.get.userName/_edituser}" validate="true">
@@ -75,7 +75,7 @@
</div> </div>
</div> </div>
<fieldset class="border-top"> <fieldset class="border-top">
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/> <input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create user} else {Update user}"/>
<a href="@context.path/admin/users" class="btn btn-default">Cancel</a> <a href="@context.path/admin/users" class="btn btn-default">Cancel</a>
</fieldset> </fieldset>
</form> </form>

View File

@@ -1,5 +1,5 @@
@(account: Option[gitbucket.core.model.Account], members: List[gitbucket.core.model.GroupMember])(implicit context: gitbucket.core.controller.Context) @(account: Option[gitbucket.core.model.Account], members: List[gitbucket.core.model.GroupMember])(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main(if(account.isEmpty) "New Group" else "Update Group"){ @gitbucket.core.html.main(if(account.isEmpty) "New group" else "Update group"){
@gitbucket.core.admin.html.menu("users"){ @gitbucket.core.admin.html.menu("users"){
<form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newgroup} else {@context.path/admin/users/@account.get.userName/_editgroup}" validate="true"> <form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newgroup} else {@context.path/admin/users/@account.get.userName/_editgroup}" validate="true">
<div class="row"> <div class="row">
@@ -44,7 +44,7 @@
</div> </div>
</div> </div>
<fieldset class="border-top"> <fieldset class="border-top">
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/> <input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create group} else {Update group}"/>
<a href="@context.path/admin/users" class="btn btn-default">Cancel</a> <a href="@context.path/admin/users" class="btn btn-default">Cancel</a>
</fieldset> </fieldset>
</form> </form>
@@ -127,4 +127,4 @@ $(function(){
$('#members').val(members); $('#members').val(members);
} }
}); });
</script> </script>

View File

@@ -3,8 +3,8 @@
@gitbucket.core.html.main("Manage Users"){ @gitbucket.core.html.main("Manage Users"){
@gitbucket.core.admin.html.menu("users"){ @gitbucket.core.admin.html.menu("users"){
<div class="pull-right" style="margin-bottom: 4px;"> <div class="pull-right" style="margin-bottom: 4px;">
<a href="@context.path/admin/users/_newuser" class="btn btn-default">New User</a> <a href="@context.path/admin/users/_newuser" class="btn btn-default">New user</a>
<a href="@context.path/admin/users/_newgroup" class="btn btn-default">New Group</a> <a href="@context.path/admin/users/_newgroup" class="btn btn-default">New group</a>
</div> </div>
<label for="includeRemoved"> <label for="includeRemoved">
<input type="checkbox" id="includeRemoved" name="includeRemoved" @if(includeRemoved){checked}/> <input type="checkbox" id="includeRemoved" name="includeRemoved" @if(includeRemoved){checked}/>
@@ -67,4 +67,4 @@ $(function(){
location.href = '@context.path/admin/users?includeRemoved=' + this.checked; location.href = '@context.path/admin/users?includeRemoved=' + this.checked;
}); });
}); });
</script> </script>

View File

@@ -7,7 +7,7 @@
groups: List[String], groups: List[String],
recentRepositories: 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) userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Pull Requests"){ @gitbucket.core.html.main("Pull requests"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){ @gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){
@gitbucket.core.dashboard.html.tab("pulls") @gitbucket.core.dashboard.html.tab("pulls")
<div class="container"> <div class="container">

View File

@@ -12,21 +12,15 @@
@if(userRepositories.isEmpty){ @if(userRepositories.isEmpty){
<li>No repositories</li> <li>No repositories</li>
} else { } else {
@defining(10){ max => <li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
@userRepositories.zipWithIndex.map { case (repository, i) => @userRepositories.zipWithIndex.map { case (repository, i) =>
<li class="repo-link" style="@if(i > max - 1){display:none;}"> <li class="repo-link">
@if(repository.owner == context.loginAccount.get.userName){ @if(repository.owner == context.loginAccount.get.userName){
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a> <a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
} else { } else {
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a> <a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
} }
</li> </li>
}
@if(userRepositories.size > max){
<li class="show-more">
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
</li>
}
} }
} }
} else { } else {
@@ -34,17 +28,11 @@
@if(recentRepositories.isEmpty){ @if(recentRepositories.isEmpty){
<li>No repositories</li> <li>No repositories</li>
} else { } else {
@defining(10){ max => <li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
@recentRepositories.zipWithIndex.map { case (repository, i) => @recentRepositories.zipWithIndex.map { case (repository, i) =>
<li class="repo-link" style="@if(i > max - 1){display:none;}"> <li class="repo-link">
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a> <a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
</li> </li>
}
@if(recentRepositories.size > max){
<li class="show-more">
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
</li>
}
} }
} }
} }
@@ -58,9 +46,21 @@
</div> </div>
<script> <script>
$(function(){ $(function(){
$('#show-more-repos, #show-more-recent-repos').click(function(e){ $('#filter-box').keyup(function(){
$(e.target).parents('ul').find('li.repo-link').show(); var inputVal = $('#filter-box').val();
$(e.target).parents('li.show-more').remove(); $.each($('li.repo-link a'), function(index, elem) {
console.log(inputVal);
console.log(elem.text.trim());
console.log(elem.text.trim().lastIndexOf(inputVal, 0));
if (!inputVal || !elem.text.trim() || elem.text.trim().indexOf(inputVal) >= 0) {
$(elem).parent().show();
} else {
$(elem).parent().hide();
}
});
});
$('form.sidebar-form').submit(function () {
return false;
}); });
}); });
</script> </script>

View File

@@ -1,8 +1,8 @@
@(active: String = "")(implicit context: gitbucket.core.controller.Context) @(active: String = "")(implicit context: gitbucket.core.controller.Context)
<ul class="nav nav-tabs" style="margin-bottom: 20px;"> <ul class="nav nav-tabs" style="margin-bottom: 20px;">
<li @if(active == ""){ class="active"}><a href="@context.path/">News Feed</a></li> <li @if(active == ""){ class="active"}><a href="@context.path/">News feed</a></li>
@if(context.loginAccount.isDefined){ @if(context.loginAccount.isDefined){
<li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull Requests</a></li> <li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
<li @if(active == "issues"){ class="active"}><a href="@context.path/dashboard/issues">Issues</a></li> <li @if(active == "issues"){ class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab => @gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
@tab(context).map { link => @tab(context).map { link =>

View File

@@ -1,4 +1,8 @@
@(title: String)(implicit context: gitbucket.core.controller.Context) @(title: String)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Error"){ @gitbucket.core.html.main("Error"){
<h1>@title</h1> <div class="content-wrapper main-center">
<div class="content body">
<h1>@title</h1>
</div>
</div>
} }

View File

@@ -64,7 +64,7 @@ $(function(){
@dropzone(clickable: Boolean, textareaId: Option[String]) = { @dropzone(clickable: Boolean, textareaId: Option[String]) = {
url: '@context.path/upload/file/@repository.owner/@repository.name', url: '@context.path/upload/file/@repository.owner/@repository.name',
maxFilesize: 10, maxFilesize: 10,
clickable: false, clickable: @clickable,
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")), acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.', dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>", previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",

View File

@@ -13,15 +13,14 @@
@helpers.avatar(comment.commentedUserName, 20) @helpers.avatar(comment.commentedUserName, 20)
@helpers.user(comment.commentedUserName, styleClass="username strong") @helpers.user(comment.commentedUserName, styleClass="username strong")
<span class="muted"> <span class="muted">
commented commented on
@if(comment.issueId.isDefined){ @if(comment.issueId.isDefined){
on this Pull Request <a href="@helpers.url(repository)/pull/@comment.issueId">#@comment.issueId</a>
} else {
@if(comment.fileName.isDefined){
on @comment.fileName.get
}
in <a href="@context.path/@repository.owner/@repository.name/commit/@comment.commitId">@comment.commitId.substring(0, 7)</a>
} }
@comment.fileName.map { fileName =>
@fileName in
}
<a href="@context.path/@repository.owner/@repository.name/commit/@comment.commitId">@comment.commitId.substring(0, 7)</a>
@gitbucket.core.helper.html.datetimeago(comment.registeredDate) @gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</span> </span>
<span class="pull-right"> <span class="pull-right">

View File

@@ -1,63 +1,48 @@
@(id: String, value: String, style: String = "")(html: Html = Html("")) @(targetTextId: String, copyButtonId: String, value: String, style: String = "")(html: Html = Html(""))
@if(html.body.nonEmpty){ @if(html.body.nonEmpty){
<div class="input-group" style="margin-bottom: 0px;"> <div class="input-group" style="margin-bottom: 0px;">
@html @html
<span class="input-group-btn"> <span class="input-group-btn">
<span id="@id" class="btn btn-default" @if(style.nonEmpty){style="@style"} <span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span> data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
</span> </span>
</div> </div>
} else { } else {
<span id="@id" class="btn btn-default" @if(style.nonEmpty){style="@style"} <span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span> data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
} }
<script> <script>
// copy to clipboard // copy to clipboard
(function() { (function() {
// Check flash availablibity // if document.execCommand('copy') is available, use it for copy.
var flashAvailable = false; if (document.queryCommandSupported('copy')) {
try { var title = $('#@copyButtonId').attr('title');
var flashObject = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); $('#@copyButtonId').tooltip({
if(flashObject) flashAvailable = true; @* if default container is used then 2 lines tooltip text is displayd because tooptip width is narrow. *@
} catch (e) { container: 'body'
if (navigator.mimeTypes });
&& navigator.mimeTypes['application/x-shockwave-flash'] != undefined $('#@copyButtonId').on('click', function() {
&& navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) { var target = document.getElementById('@targetTextId');
flashAvailable = true; if (!target) { @* target's id is incorrect. Fix argument's value *@
} $('#@copyButtonId').attr('title', 'failed to copy').tooltip('fixTitle').tooltip('show');
} return;
// if flash is not available, remove the copy button.
if(!flashAvailable) {
$('#@id').remove();
return
}
// Find ZeroClipboard.swf file URI from ZeroClipboard JavaScript file path.
// NOTE(tanacasino) I think this way is wrong... but i don't know correct way.
var moviePath = (function() {
var zclipjs = "ZeroClipboard.min.js";
var scripts = document.getElementsByTagName("script");
var i = scripts.length;
while(i--) {
var match = scripts[i].src.match(zclipjs + "$");
if(match) {
return match.input.substr(0, match.input.length - 6) + 'swf';
} }
} if (typeof target.select === 'function') {
})(); target.select();
var clip = new ZeroClipboard($("#@id"), { } else {
moviePath: moviePath var range = document.createRange();
}); range.selectNodeContents(target);
var title = $('#@id').attr('title'); var selection = window.getSelection();
$('#@id').removeAttr('title') selection.removeAllRanges();
clip.htmlBridge = "#global-zeroclipboard-html-bridge"; selection.addRange(range);
clip.on('complete', function(client, args) { }
$(clip.htmlBridge).attr('title', 'copied!').tooltip('fixTitle').tooltip('show'); document.execCommand('copy');
$(clip.htmlBridge).attr('title', title).tooltip('fixTitle'); $('#@copyButtonId').attr('title', 'copied!').tooltip('fixTitle').tooltip('show');
}); $('#@copyButtonId').attr('title', title).tooltip('fixTitle');
$(clip.htmlBridge).tooltip({ });
title: title, } else {
placement: $('#@id').attr('data-placement') // if copy is not supported, remove the copy button
}); $('#@copyButtonId').remove();
}
})(); })();
</script> </script>

View File

@@ -146,24 +146,24 @@ $(function(){
} }
// Render diffs as unified mode initially // Render diffs as unified mode initially
if(("&"+location.search.substring(1)).indexOf("&w=1")!=-1){ if(("&" + location.search.substring(1)).indexOf("&w=1") != -1){
$('.ignore-whitespace').prop('checked',true); $('.ignore-whitespace').prop('checked',true);
} }
window.viewType=1; window.viewType = 1;
if(("&"+location.search.substring(1)).indexOf("&diff=split")!=-1){ if(("&" + location.search.substring(1)).indexOf("&diff=split") != -1){
$('.container').removeClass('container').addClass('container-wide'); $('.container').removeClass('container').addClass('container-wide');
window.viewType=0; window.viewType = 0;
} }
renderDiffs(); renderDiffs();
$('#btn-unified').click(function(){ $('#btn-unified').click(function(){
window.viewType=1; window.viewType = 1;
$('.container-wide').removeClass('container-wide').addClass('container'); $('.container-wide').removeClass('container-wide').addClass('container');
renderDiffs(); renderDiffs();
}); });
$('#btn-split').click(function(){ $('#btn-split').click(function(){
window.viewType=0; window.viewType = 0;
$('.container').removeClass('container').addClass('container-wide'); $('.container').removeClass('container').addClass('container-wide');
renderDiffs(); renderDiffs();
}); });
@@ -192,9 +192,10 @@ $(function(){
$('#comment-list').children('.inline-comment').hide(); $('#comment-list').children('.inline-comment').hide();
} }
$('.diff-outside').on('click','table.diff .add-comment',function() { $('.diff-outside').on('click','table.diff .add-comment',function() {
var $this = $(this), var $this = $(this);
$tr = $this.closest('tr'), var $tr = $this.closest('tr');
$check = $this.closest('table:not(.diff)').find('.toggle-notes'); var $check = $this.closest('table:not(.diff)').find('.toggle-notes');
var url = '';
if (!$check.prop('checked')) { if (!$check.prop('checked')) {
$check.prop('checked', true).trigger('change'); $check.prop('checked', true).trigger('change');
} }
@@ -216,35 +217,29 @@ $(function(){
if (!isNaN(newLineNumber) && newLineNumber) { if (!isNaN(newLineNumber) && newLineNumber) {
url += ('&newLineNumber=' + newLineNumber) url += ('&newLineNumber=' + newLineNumber)
} }
$.get( $.get(url, { dataType : 'html' }, function(responseContent) {
url, var tmp;
{ if (!isNaN(oldLineNumber) && oldLineNumber) {
dataType : 'html' if (!isNaN(newLineNumber) && newLineNumber) {
}, tmp = getInlineContainer();
function(responseContent) {
var tmp;
if (!isNaN(oldLineNumber) && oldLineNumber) {
if (!isNaN(newLineNumber) && newLineNumber) {
tmp = getInlineContainer();
} else {
tmp = getInlineContainer('old');
}
} else { } else {
tmp = getInlineContainer('new'); tmp = getInlineContainer('old');
} }
tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent); } else {
$tr.nextAll(':not(.not-diff):first').before(tmp); tmp = getInlineContainer('new');
} }
); tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent);
$tr.nextAll(':not(.not-diff):first').before(tmp);
});
} }
}).on('click', 'table.diff .btn-default', function() { }).on('click', 'table.diff .btn-default', function() {
$(this).closest('.inline-comment-form').remove(); $(this).closest('.inline-comment-form').remove();
}); });
function renderOneCommitCommentIntoDiff($v, diff){ function renderOneCommitCommentIntoDiff($v, diff){
var filename = $v.attr('filename'), var filename = $v.attr('filename');
oldline = $v.attr('oldline'), newline = $v.attr('newline'); var oldline = $v.attr('oldline');
var newline = $v.attr('newline');
var tmp; var tmp;
var diff;
if (typeof oldline !== 'undefined') { if (typeof oldline !== 'undefined') {
if (typeof newline !== 'undefined') { if (typeof newline !== 'undefined') {
tmp = getInlineContainer(); tmp = getInlineContainer();
@@ -252,38 +247,36 @@ $(function(){
tmp = getInlineContainer('old'); tmp = getInlineContainer('old');
} }
tmp.children('td:first').html($v.clone().show()); tmp.children('td:first').html($v.clone().show());
diff.find('table.diff').find('.oldline[line-number=' + oldline + ']') diff.find('table.diff').find('.oldline[line-number=' + oldline + ']').parent().nextAll(':not(.not-diff):first').before(tmp);
.parent().nextAll(':not(.not-diff):first').before(tmp);
} else { } else {
tmp = getInlineContainer('new'); tmp = getInlineContainer('new');
tmp.children('td:last').html($v.clone().show()); tmp.children('td:last').html($v.clone().show());
diff.find('table.diff').find('.newline[line-number=' + newline + ']') diff.find('table.diff').find('.newline[line-number=' + newline + ']').parent().nextAll(':not(.not-diff):first').before(tmp);
.parent().nextAll(':not(.not-diff):first').before(tmp);
} }
if (!diff.find('.toggle-notes').prop('checked')) { if (!diff.find('.toggle-notes').prop('checked')) {
tmp.hide(); tmp.hide();
} }
} }
function renderStatBar(add,del){ function renderStatBar(add, del){
if(add+del>5){ if(add + del > 5){
if(add){ if(add){
if(add<del){ if(add < del){
add = Math.floor(1 + (add * 4 / (add+del))); add = Math.floor(1 + (add * 4 / (add + del)));
}else{ } else {
add = Math.ceil(1 + (add * 4 / (add+del))); add = Math.ceil(1 + (add * 4 / (add + del)));
} }
} }
del = 5-add; del = 5 - add;
} }
var ret = $('<div class="diffstat-bar">'); var ret = $('<div class="diffstat-bar">');
for(var i=0;i<5;i++){ for(var i = 0; i < 5; i++){
if(add){ if(add){
ret.append('<span class="text-diff-added">■</span>'); ret.append('<span class="text-diff-added">■</span>');
add --; add--;
}else if(del){ } else if(del){
ret.append('<span class="text-diff-deleted">■</span>'); ret.append('<span class="text-diff-deleted">■</span>');
del --; del--;
}else{ } else {
ret.append('■'); ret.append('■');
} }
} }
@@ -294,10 +287,12 @@ $(function(){
var i = table.data("diff-id"); var i = table.data("diff-id");
var ignoreWhiteSpace = table.find('.ignore-whitespace').prop('checked'); var ignoreWhiteSpace = table.find('.ignore-whitespace').prop('checked');
diffUsingJS('oldText-'+i, 'newText-'+i, diffText.attr('id'), viewType, ignoreWhiteSpace); diffUsingJS('oldText-'+i, 'newText-'+i, diffText.attr('id'), viewType, ignoreWhiteSpace);
var add = diffText.find("table").attr("add")*1; var add = diffText.find("table").attr("add") * 1;
var del = diffText.find("table").attr("del")*1; var del = diffText.find("table").attr("del") * 1;
table.find(".diffstat").text(add+del+" ").append(renderStatBar(add,del)).attr("title",add+" additions & "+del+" deletions").tooltip(); table.find(".diffstat").text(add+del+" ").append(renderStatBar(add,del)).attr("title",add+" additions & "+del+" deletions").tooltip();
$('span.diffstat[data-diff-id="'+i+'"]').html('<span class="text-diff-added">+'+add+'</span><span class="text-diff-deleted">-'+del+'</span>').append(renderStatBar(add,del).attr('title',(add+del)+" lines changed").tooltip()); $('span.diffstat[data-diff-id="'+i+'"]')
.html('<span class="text-diff-added">+' + add + '</span><span class="text-diff-deleted">-' + del + '</span>')
.append(renderStatBar(add, del).attr('title', (add + del) + " lines changed").tooltip());
@if(hasWritePermission) { @if(hasWritePermission) {
diffText.find('.body').each(function(){ $('<b class="add-comment">+</b>').prependTo(this); }); diffText.find('.body').each(function(){ $('<b class="add-comment">+</b>').prependTo(this); });
@@ -305,14 +300,14 @@ $(function(){
@if(showLineNotes){ @if(showLineNotes){
var fileName = table.attr('filename'); var fileName = table.attr('filename');
$('.inline-comment').each(function(i, v) { $('.inline-comment').each(function(i, v) {
if($(this).attr('filename')==fileName){ if($(this).attr('filename') == fileName){
renderOneCommitCommentIntoDiff($(this), table); renderOneCommitCommentIntoDiff($(this), table);
} }
}); });
} }
} }
function renderDiffs(){ function renderDiffs(){
var i=0, diffs = $('.diffText'); var i = 0, diffs = $('.diffText');
function render(){ function render(){
if(diffs[i]){ if(diffs[i]){
renderOneDiff($(diffs[i]), viewType); renderOneDiff($(diffs[i]), viewType);

View File

@@ -74,7 +74,7 @@
<span id="label-milestone"> <span id="label-milestone">
@issue.flatMap(_.milestoneId).map { milestoneId => @issue.flatMap(_.milestoneId).map { milestoneId =>
@milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) => @milestones.collect { case (milestone, _, _) if(milestone.milestoneId == milestoneId) =>
<span class="strong small">@milestone.title</span> <a class="strong small username" href="@helpers.url(repository)/issues?milestone=@helpers.urlEncode(milestone.title)&state=open">@milestone.title</a>
} }
}.getOrElse { }.getOrElse {
<span class="muted small">No milestone</span> <span class="muted small">No milestone</span>
@@ -210,7 +210,8 @@ $(function(){
$('#label-milestone').html($('<span class="muted small">').text('No milestone')); $('#label-milestone').html($('<span class="muted small">').text('No milestone'));
$('#milestone-progress-area').empty(); $('#milestone-progress-area').empty();
} else { } else {
$('#label-milestone').html($('<span class="strong small">').text(title)); $('#label-milestone').html($('<a class="strong small username">').text(title)
.attr('href', '@helpers.url(repository)/issues?milestone=' + encodeURIComponent(title) + '&state=open'));
if(progress){ if(progress){
$('#milestone-progress-area').html(progress); $('#milestone-progress-area').html(progress);
} }

View File

@@ -21,15 +21,22 @@
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a> <a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
</li> </li>
</ul> </ul>
<form method="GET" id="search-filter-form" class="form-inline pull-right"> <form method="GET" action="@helpers.url(repository)/search" id="search-filter-form" class="form-inline pull-right">
@if(isEditable){ <div class="input-group">
@if(target == "issues"){ <input type="text" class="form-control" name="q" placeholder="Search..."/>
<a class="btn btn-success" href="@helpers.url(repository)/issues/new">New issue</a> <input type="hidden" name="type" value="issue"/>
<span class="input-group-btn">
<button type="submit" id="search-btn" class="btn btn-default"><i class="fa fa-search"></i></button>
</span>
</div>
@if(isEditable){
@if(target == "issues"){
<a class="btn btn-success" href="@helpers.url(repository)/issues/new">New issue</a>
}
@if(target == "pulls"){
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
}
} }
@if(target == "pulls"){
<a class="btn btn-success" href="@helpers.url(repository)/compare">New pull request</a>
}
}
</form> </form>
@gitbucket.core.issues.html.listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), isManageable) @gitbucket.core.issues.html.listparts(target, issues, page, openCount, closedCount, condition, collaborators, milestones, labels, Some(repository), isManageable)
@if(isManageable){ @if(isManageable){

View File

@@ -15,11 +15,11 @@
<link href="@helpers.assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet"> <link href="@helpers.assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
<link href="@helpers.assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/> <link href="@helpers.assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
<link href="@helpers.assets/vendors/facebox/facebox.css" rel="stylesheet"/> <link href="@helpers.assets/vendors/facebox/facebox.css" rel="stylesheet"/>
<link href="@helpers.assets/vendors/AdminLTE-2.3.6/css/AdminLTE.min.css" rel="stylesheet"> <link href="@helpers.assets/vendors/AdminLTE-2.3.8/css/AdminLTE.min.css" rel="stylesheet">
<link href="@helpers.assets/vendors/AdminLTE-2.3.6/css/skins/skin-blue.min.css" rel="stylesheet"> <link href="@helpers.assets/vendors/AdminLTE-2.3.8/css/skins/skin-blue.min.css" rel="stylesheet">
<link href="@helpers.assets/vendors/font-awesome-4.6.3/css/font-awesome.min.css" rel="stylesheet"> <link href="@helpers.assets/vendors/font-awesome-4.6.3/css/font-awesome.min.css" rel="stylesheet">
<link href="@helpers.assets/common/css/gitbucket.css" rel="stylesheet"> <link href="@helpers.assets/common/css/gitbucket.css" rel="stylesheet">
<script src="@helpers.assets/vendors/jquery/jquery-1.11.1.js"></script> <script src="@helpers.assets/vendors/jquery/jquery-1.12.2.min.js"></script>
<script src="@helpers.assets/vendors/dropzone/dropzone.js"></script> <script src="@helpers.assets/vendors/dropzone/dropzone.js"></script>
<script src="@helpers.assets/common/js/validation.js"></script> <script src="@helpers.assets/common/js/validation.js"></script>
<script src="@helpers.assets/common/js/gitbucket.js"></script> <script src="@helpers.assets/common/js/gitbucket.js"></script>
@@ -29,7 +29,6 @@
<script src="@helpers.assets/vendors/datepicker/js/bootstrap-datetimepicker.min.js"></script> <script src="@helpers.assets/vendors/datepicker/js/bootstrap-datetimepicker.min.js"></script>
<script src="@helpers.assets/vendors/colorpicker/js/bootstrap-colorpicker.js"></script> <script src="@helpers.assets/vendors/colorpicker/js/bootstrap-colorpicker.js"></script>
<script src="@helpers.assets/vendors/google-code-prettify/prettify.js"></script> <script src="@helpers.assets/vendors/google-code-prettify/prettify.js"></script>
<script src="@helpers.assets/vendors/zclip/ZeroClipboard.min.js"></script>
<script src="@helpers.assets/vendors/elastic/jquery.elastic.source.js"></script> <script src="@helpers.assets/vendors/elastic/jquery.elastic.source.js"></script>
<script src="@helpers.assets/vendors/facebox/facebox.js"></script> <script src="@helpers.assets/vendors/facebox/facebox.js"></script>
<script src="@helpers.assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script> <script src="@helpers.assets/vendors/jquery-hotkeys/jquery.hotkeys.js"></script>
@@ -37,9 +36,9 @@
@repository.map { repository => @repository.map { repository =>
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" /> <meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
} }
<script src="@helpers.assets/vendors/AdminLTE-2.3.6/js/app.js" type="text/javascript"></script> <script src="@helpers.assets/vendors/AdminLTE-2.3.8/js/app.js" type="text/javascript"></script>
</head> </head>
<body class="skin-blue page-load"> <body class="skin-blue page-load @if(context.sidebarCollapse){sidebar-collapse}">
<div class="wrapper"> <div class="wrapper">
<header class="main-header"> <header class="main-header">
<a href="@context.path/" class="logo"> <a href="@context.path/" class="logo">
@@ -54,15 +53,11 @@
<span class="sr-only">Toggle navigation</span> <span class="sr-only">Toggle navigation</span>
</a> </a>
} }
@repository.map { repository => <form id="search" action="@context.path/search" method="GET" class="pc navbar-form navbar-left" role="search">
<form id="search" action="@context.path/search" method="POST" class="pc navbar-form navbar-left" role="search"> <div class="form-group">
<div class="form-group"> <input type="text" name="query" id="navbar-search-input" class="form-control" placeholder="Search repository"/>
<input type="text" name="query" id="navbar-search-input" class="form-control" placeholder="Search this repository"/> </div>
<input type="hidden" name="owner" value="@repository.owner"/> </form>
<input type="hidden" name="repository" value="@repository.name"/>
</div>
</form>
}
<ul class="pc nav navbar-nav"> <ul class="pc nav navbar-nav">
@if(context.loginAccount.isDefined){ @if(context.loginAccount.isDefined){
<li><a href="@context.path/dashboard/pulls">Pull requests</a></li> <li><a href="@context.path/dashboard/pulls">Pull requests</a></li>
@@ -115,6 +110,9 @@
$('#search').submit(function(){ $('#search').submit(function(){
return $.trim($(this).find('input[name=query]').val()) != ''; return $.trim($(this).find('input[name=query]').val()) != '';
}); });
$(".sidebar-toggle").on('click', function(e){
$.get('@context.path/sidebar-collapse', { collapse: !$('body').hasClass('sidebar-collapse') });
});
}); });
</script> </script>
@PluginRegistry().getJavaScript(context.request.getRequestURI).map { script => @PluginRegistry().getJavaScript(context.request.getRequestURI).map { script =>

View File

@@ -23,13 +23,13 @@
<div class="sidebar"> <div class="sidebar">
<ul class="sidebar-menu"> <ul class="sidebar-menu">
@menuitem("", "files", "Files", "code") @menuitem("", "files", "Files", "code")
@if(repository.commitCount != 0) { @if(repository.branchList.nonEmpty) {
@menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length) @menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length)
@menuitem("/tags", "tags", "Tags", "tag", repository.tags.length) @menuitem("/tags", "tags", "Tags", "tag", repository.tags.length)
} }
@if(repository.repository.options.issuesOption != "DISABLE") { @if(repository.repository.options.issuesOption != "DISABLE") {
@menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount) @menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount)
@menuitem("/pulls", "pulls", "Pull Requests", "git-pull-request", repository.pullCount) @menuitem("/pulls", "pulls", "Pull requests", "git-pull-request", repository.pullCount)
@menuitem("/issues/labels", "labels", "Labels", "tag") @menuitem("/issues/labels", "labels", "Labels", "tag")
@menuitem("/issues/milestones", "milestones", "Milestones", "milestone") @menuitem("/issues/milestones", "milestones", "Milestones", "milestone")
} else { } else {

View File

@@ -15,7 +15,7 @@
milestones: List[gitbucket.core.model.Milestone], milestones: List[gitbucket.core.model.Milestone],
labels: List[gitbucket.core.model.Label])(implicit context: gitbucket.core.controller.Context) labels: List[gitbucket.core.model.Label])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"Pull Requests - ${repository.owner}/${repository.name}", Some(repository)){ @gitbucket.core.html.main(s"Pull requests - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("pulls", repository){ @gitbucket.core.html.menu("pulls", repository){
<div class="pullreq-info"> <div class="pullreq-info">
<div id="compare-edit"> <div id="compare-edit">

View File

@@ -99,7 +99,7 @@
you can perform a manual merge on the command line. you can perform a manual merge on the command line.
</p> </p>
} }
@gitbucket.core.helper.html.copy("repository-url-copy", forkedRepository.httpUrl){ @gitbucket.core.helper.html.copy("repository-url", "repository-url-copy", forkedRepository.httpUrl){
<div class="input-group-btn" data-toggle="buttons"> <div class="input-group-btn" data-toggle="buttons">
<label class="btn btn-sm btn-default active" id="repository-url-http"><input type="radio" checked>HTTP</label> <label class="btn btn-sm btn-default active" id="repository-url-http"><input type="radio" checked>HTTP</label>
@if(context.settings.ssh && context.loginAccount.isDefined){ @if(context.settings.ssh && context.loginAccount.isDefined){
@@ -114,7 +114,7 @@
</p> </p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" + @defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command => s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
@gitbucket.core.helper.html.copy("merge-command-copy-1", command, "position: absolute; right: 31px;")() @gitbucket.core.helper.html.copy("merge-command", "merge-command-copy-1", command, "position: absolute; right: 31px;")()
<pre style="font-size: 12px; border-radius: 3px;" id="merge-command">@Html(command)</pre> <pre style="font-size: 12px; border-radius: 3px;" id="merge-command">@Html(command)</pre>
} }
</div> </div>
@@ -124,8 +124,8 @@
</p> </p>
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" + @defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
s"git push origin ${pullreq.branch}"){ command => s"git push origin ${pullreq.branch}"){ command =>
@gitbucket.core.helper.html.copy("merge-command-copy-2", command, "position: absolute; right: 31px;")() @gitbucket.core.helper.html.copy("merge-command-2", "merge-command-copy-2", command, "position: absolute; right: 31px;")()
<pre style="font-size: 12px; border-radius: 3px;">@command</pre> <pre style="font-size: 12px; border-radius: 3px;" id="merge-command-2">@command</pre>
} }
</div> </div>
</div> </div>
@@ -196,4 +196,4 @@ $(function(){
}); });
} }
}); });
</script> </script>

View File

@@ -14,7 +14,7 @@
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@import gitbucket.core.model.IssueComment @import gitbucket.core.model.IssueComment
@import gitbucket.core.model.CommitComment @import gitbucket.core.model.CommitComment
@gitbucket.core.html.main(s"${issue.title} - Pull Request #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){ @gitbucket.core.html.main(s"${issue.title} - Pull request #${issue.issueId} - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("pulls", repository){ @gitbucket.core.html.menu("pulls", repository){
@defining(dayByDayCommits.flatten){ commits => @defining(dayByDayCommits.flatten){ commits =>
<div> <div>
@@ -99,13 +99,13 @@
$(function(){ $(function(){
// Determine active tab from hash // Determine active tab from hash
if(location.hash == '#commits'){ if(location.hash == '#commits'){
$('li:has(a[href=#commits])').addClass('active'); $('li:has(a[href="#commits"])').addClass('active');
$('div#commits').addClass('active'); $('div#commits').addClass('active');
} else if(location.hash == '#files'){ } else if(location.hash == '#files'){
$('li:has(a[href=#files])').addClass('active'); $('li:has(a[href="#files"])').addClass('active');
$('div#files').addClass('active'); $('div#files').addClass('active');
} else { } else {
$('li:has(a[href=#conversation])').addClass('active'); $('li:has(a[href="#conversation"])').addClass('active');
$('div#conversation').addClass('active'); $('div#conversation').addClass('active');
} }
// Set hash when tab is clicked // Set hash when tab is clicked

View File

@@ -56,7 +56,7 @@
helpers.urlEncode(parent) + ":" + helpers.encodeRefName(repository.repository.defaultBranch) helpers.urlEncode(parent) + ":" + helpers.encodeRefName(repository.repository.defaultBranch)
}.getOrElse { }.getOrElse {
helpers.encodeRefName(repository.repository.defaultBranch) helpers.encodeRefName(repository.repository.defaultBranch)
}}...@{helpers.encodeRefName(branch.name)}?expand=1" class="btn btn-default">New Pull Request</a> }}...@{helpers.encodeRefName(branch.name)}?expand=1" class="btn btn-default">New Pull request</a>
} else { } else {
<a href="@helpers.url(repository)/compare/@{repository.repository.parentUserName.map { parent => <a href="@helpers.url(repository)/compare/@{repository.repository.parentUserName.map { parent =>
helpers.urlEncode(parent) + ":" + helpers.encodeRefName(repository.repository.defaultBranch) helpers.urlEncode(parent) + ":" + helpers.encodeRefName(repository.repository.defaultBranch)

View File

@@ -136,6 +136,7 @@ $(function(){
$.post('@helpers.url(repository)/_preview', { $.post('@helpers.url(repository)/_preview', {
content : editor.getValue(), content : editor.getValue(),
enableWikiLink : false, enableWikiLink : false,
filename : $('#newFileName').val(),
enableRefsLink : false, enableRefsLink : false,
enableLineBreaks : false, enableLineBreaks : false,
enableTaskList : false enableTaskList : false

View File

@@ -2,6 +2,7 @@
repository: gitbucket.core.service.RepositoryService.RepositoryInfo, repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
pathList: List[String], pathList: List[String],
latestCommit: gitbucket.core.util.JGitUtil.CommitInfo, latestCommit: gitbucket.core.util.JGitUtil.CommitInfo,
commitCount: Int,
files: List[gitbucket.core.util.JGitUtil.FileInfo], files: List[gitbucket.core.util.JGitUtil.FileInfo],
readme: Option[(List[String], String)], readme: Option[(List[String], String)],
hasWritePermission: Boolean, hasWritePermission: Boolean,
@@ -25,7 +26,7 @@
<div class="pull-right"> <div class="pull-right">
<div class="btn-group"> <div class="btn-group">
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t"><i class="octicon octicon-search"></i></a> <a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t"><i class="octicon octicon-search"></i></a>
<a href="@helpers.url(repository)/commits/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i> @if(repository.commitCount > 10000){10000+} else {@repository.commitCount} @helpers.plural(repository.commitCount, "commit")</a> <a href="@helpers.url(repository)/commits/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i> @if(commitCount > 10000){10000+} else {@commitCount} @helpers.plural(commitCount, "commit")</a>
</div> </div>
</div> </div>
@if(pathList.isEmpty){ @if(pathList.isEmpty){
@@ -39,7 +40,7 @@
</div> </div>
<div class="pull-right pc"> <div class="pull-right pc">
<div style="width: 240px; margin-right: 5px; margin-left: 5px;"> <div style="width: 240px; margin-right: 5px; margin-left: 5px;">
@gitbucket.core.helper.html.copy("repository-url-copy", repository.httpUrl){ @gitbucket.core.helper.html.copy("repository-url", "repository-url-copy", repository.httpUrl){
@if(repository.sshUrl.isDefined){ @if(repository.sshUrl.isDefined){
<div class="btn-group input-group-btn"> <div class="btn-group input-group-btn">
<button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@@ -85,7 +86,9 @@
<a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> / <a href="@helpers.url(repository)/tree/@helpers.encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
} }
} }
<a href="@helpers.url(repository)/new/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here" @if(!hasWritePermission){disabled}><i class="octicon octicon-plus"></i></a> @if(hasWritePermission){
<a href="@helpers.url(repository)/new/@helpers.encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here"><i class="octicon octicon-plus"></i></a>
}
</div> </div>
<table class="table table-hover"> <table class="table table-hover">
@* @*
@@ -143,7 +146,7 @@
<i class="octicon octicon-file-text"></i> <i class="octicon octicon-file-text"></i>
} }
</td> </td>
<td class="content-name"> <td class="ellipsis-cell" style="width: 20%; min-width: 160px;">
@if(file.isDirectory){ @if(file.isDirectory){
@if(file.linkUrl.isDefined){ @if(file.linkUrl.isDefined){
<a href="@file.linkUrl"> <a href="@file.linkUrl">
@@ -164,10 +167,10 @@
<a href="@helpers.url(repository)/blob@{(branch :: pathList).map(helpers.encodeRefName).mkString("/", "/", "/")}@{helpers.encodeRefName(file.name)}">@file.name</a> <a href="@helpers.url(repository)/blob@{(branch :: pathList).map(helpers.encodeRefName).mkString("/", "/", "/")}@{helpers.encodeRefName(file.name)}">@file.name</a>
} }
</td> </td>
<td class="mute"> <td class="ellipsis-cell" style="width: 70%;">
<a href="@helpers.url(repository)/commit/@file.commitId" class="commit-message shorten-text" title="@file.message">@helpers.link(file.message, repository)</a> <a href="@helpers.url(repository)/commit/@file.commitId" class="commit-message" title="@file.message">@helpers.link(file.message, repository)</a>
</td> </td>
<td style="text-align: right;">@gitbucket.core.helper.html.datetimeago(file.time, false)</td> <td style="width: 10%; min-width: 125px; text-align: right;">@gitbucket.core.helper.html.datetimeago(file.time, false)</td>
</tr> </tr>
} }
</table> </table>

View File

@@ -1,17 +1,17 @@
@(files: List[gitbucket.core.service.RepositorySearchService.FileSearchResult], @(files: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
issueCount: Int,
wikiCount: Int,
query: String, query: String,
page: Int, page: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@import gitbucket.core.service.RepositorySearchService @import gitbucket.core.service.RepositorySearchService
@gitbucket.core.html.main("Search Results", Some(repository)){ @gitbucket.core.html.main("Search Results", Some(repository)){
@gitbucket.core.search.html.menu("code", files.size, issueCount, wikiCount, query, repository){ @gitbucket.core.search.html.menu("files", query, repository){
@if(files.isEmpty){ @if(query.nonEmpty) {
<h4>We couldn't find any code matching '@query'</h4> @if(files.isEmpty) {
} else { <h4>We couldn't find any code matching '@query'</h4>
<h4>We've found @files.size code @helpers.plural(files.size, "result")</h4> } else {
<h4>We've found @files.size code @helpers.plural(files.size, "result")</h4>
}
} }
@files.drop((page - 1) * RepositorySearchService.CodeLimit).take(RepositorySearchService.CodeLimit).map { file => @files.drop((page - 1) * RepositorySearchService.CodeLimit).take(RepositorySearchService.CodeLimit).map { file =>
<div> <div>

View File

@@ -1,17 +1,17 @@
@(fileCount: Int, @(issues: List[gitbucket.core.service.RepositorySearchService.IssueSearchResult],
issues: List[gitbucket.core.service.RepositorySearchService.IssueSearchResult],
wikiCount: Int,
query: String, query: String,
page: Int, page: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@import gitbucket.core.service.RepositorySearchService @import gitbucket.core.service.RepositorySearchService
@gitbucket.core.html.main("Search Results", Some(repository)){ @gitbucket.core.html.main("Search Results", Some(repository)){
@gitbucket.core.search.html.menu("issue", fileCount, issues.size, wikiCount, query, repository){ @gitbucket.core.search.html.menu("issues", query, repository){
@if(issues.isEmpty){ @if(query.nonEmpty) {
<h4>We couldn't find any code matching '@query'</h4> @if(issues.isEmpty) {
} else { <h4>We couldn't find any code matching '@query'</h4>
<h4>We've found @issues.size code @helpers.plural(issues.size, "result")</h4> } else {
<h4>We've found @issues.size @helpers.plural(issues.size, "issue")</h4>
}
} }
@issues.drop((page - 1) * RepositorySearchService.IssueLimit).take(RepositorySearchService.IssueLimit).map { issue => @issues.drop((page - 1) * RepositorySearchService.IssueLimit).take(RepositorySearchService.IssueLimit).map { issue =>
<div class="block"> <div class="block">

View File

@@ -1,35 +1,18 @@
@(active: String, fileCount: Int, issueCount: Int, wikiCount: Int, query: String, @(active: String, query: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@gitbucket.core.html.menu("", repository){ @gitbucket.core.html.menu(active, repository){
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
<li@if(active=="code"){ class="active"}>
<a href="@helpers.url(repository)/search?q=@helpers.urlEncode(query)&type=code">
Files
@if(fileCount != 0){
<span class="badge">@fileCount</span>
}
</a>
</li>
<li@if(active=="issue"){ class="active"}>
<a href="@helpers.url(repository)/search?q=@helpers.urlEncode(query)&type=issue">
Issues
@if(issueCount != 0){
<span class="badge">@issueCount</span>
}
</a>
</li>
<li@if(active=="wiki"){ class="active"}>
<a href="@helpers.url(repository)/search?q=@helpers.urlEncode(query)&type=wiki">
Wiki
@if(wikiCount != 0){
<span class="badge">@wikiCount</span>
}
</a>
</li>
</ul>
<form action="@helpers.url(repository)/search" method="GET" class="form-inline"> <form action="@helpers.url(repository)/search" method="GET" class="form-inline">
<input type="text" name="q" value="@query" class="form-control" style="width: 400px; margin-bottom: 0px;"/> <select class="form-control" name="type">
<option value="code" @if(active == "files"){ selected }>Files</option>
@if(repository.repository.options.issuesOption != "DISABLE") {
<option value="issue" @if(active == "issues"){ selected }>Issues</option>
}
@if(repository.repository.options.wikiOption != "DISABLE") {
<option value="wiki" @if(active == "wiki"){ selected }>Wiki</option>
}
</select>
<input type="text" name="q" value="@query" class="form-control" style="width: 250px;" placeholder="Search..."/>
<input type="submit" value="Search" class="btn btn-default"/> <input type="submit" value="Search" class="btn btn-default"/>
<input type="hidden" name="type" value="@active"/> <input type="hidden" name="type" value="@active"/>
</form> </form>

View File

@@ -0,0 +1,40 @@
@(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)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("GitBucket"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){
<form action="@context.path/search" method="GET" class="form-inline">
<input type="text" name="query" value="@query" class="form-control" style="width: 250px; margin-bottom: 0px;"/>
<input type="submit" value="Search" class="btn btn-default"/>
</form>
@if(repositories.isEmpty) {
<h4>We couldn't find any repository matching '@query'</h4>
} else {
<h4>We've found @repositories.size @helpers.plural(repositories.size, "repository", "repositories")</h4>
}
@repositories.map { repository =>
<div class="block">
<div class="repository-icon">
@gitbucket.core.helper.html.repositoryicon(repository, true)
</div>
<div class="repository-content">
<div class="block-header">
<a href="@helpers.url(repository)">@repository.owner/@repository.name</a>
@if(repository.repository.isPrivate){
<i class="octicon octicon-lock"></i>
}
</div>
@if(repository.repository.originUserName.isDefined){
<div class="small muted">forked from <a href="@context.path/@repository.repository.parentUserName/@repository.repository.parentRepositoryName">@repository.repository.parentUserName/@repository.repository.parentRepositoryName</a></div>
}
@if(repository.repository.description.isDefined){
<div>@repository.repository.description</div>
}
<div><span class="muted small">Updated @gitbucket.core.helper.html.datetimeago(repository.repository.lastActivityDate)</span></div>
</div>
</div>
}
}
}

View File

@@ -1,17 +1,17 @@
@(fileCount: Int, @(wikis: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
issueCount: Int,
wikis: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
query: String, query: String,
page: Int, page: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@import gitbucket.core.service.RepositorySearchService @import gitbucket.core.service.RepositorySearchService
@gitbucket.core.html.main("Search Results", Some(repository)){ @gitbucket.core.html.main("Search Results", Some(repository)){
@gitbucket.core.search.html.menu("wiki", fileCount, issueCount, wikis.size, query, repository){ @gitbucket.core.search.html.menu("wiki", query, repository){
@if(wikis.isEmpty){ @if(query.nonEmpty) {
<h4>We couldn't find any code matching '@query'</h4> @if(wikis.isEmpty) {
} else { <h4>We couldn't find any code matching '@query'</h4>
<h4>We've found @wikis.size code @helpers.plural(wikis.size, "result")</h4> } else {
<h4>We've found @wikis.size @helpers.plural(wikis.size, "page")</h4>
}
} }
@wikis.drop((page - 1) * RepositorySearchService.CodeLimit).take(RepositorySearchService.CodeLimit).map { file => @wikis.drop((page - 1) * RepositorySearchService.CodeLimit).take(RepositorySearchService.CodeLimit).map { file =>
<div> <div>

View File

@@ -2,7 +2,7 @@
isGroupRepository: Boolean, isGroupRepository: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@import gitbucket.core.model.Permission @import gitbucket.core.model.Role
@gitbucket.core.html.main("Settings", Some(repository)){ @gitbucket.core.html.main("Settings", Some(repository)){
@gitbucket.core.html.menu("settings", repository){ @gitbucket.core.html.menu("settings", repository){
@gitbucket.core.settings.html.menu("collaborators", repository){ @gitbucket.core.settings.html.menu("collaborators", repository){
@@ -77,7 +77,8 @@ $(function(){
$.post('@context.path/_user/existence', { 'userName': userName }, $.post('@context.path/_user/existence', { 'userName': userName },
function(data, status){ function(data, status){
if(data != ''){ if(data != ''){
addListHTML(userName, '@Permission.ADMIN.name', '#' + id + '-list'); addListHTML(userName, '@Role.ADMIN.name', '#' + id + '-list');
$('#userName-' + id).val('');
} else { } else {
$('#error-' + id).text('User does not exist.'); $('#error-' + id).text('User does not exist.');
} }
@@ -94,26 +95,26 @@ $(function(){
}); });
@collaborators.map { case (collaborator, isGroup) => @collaborators.map { case (collaborator, isGroup) =>
addListHTML('@collaborator.collaboratorName', '@collaborator.permission', @if(isGroup){'#group-list'}else{'#collaborator-list'}); addListHTML('@collaborator.collaboratorName', '@collaborator.role', @if(isGroup){'#group-list'}else{'#collaborator-list'});
} }
function addListHTML(userName, permission, id){ function addListHTML(userName, role, id){
var adminButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Permission.ADMIN.name" name="' + userName + '">Admin</label>'); var adminButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.ADMIN.name" name="' + userName + '">Admin</label>');
if(permission == '@Permission.ADMIN.name'){ if(role == '@Role.ADMIN.name'){
adminButton.addClass('active'); adminButton.addClass('active');
} }
var writeButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Permission.WRITE.name" name="' + userName + '">Write</label>'); var writeButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.DEVELOPER.name" name="' + userName + '">Developer</label>');
if(permission == '@Permission.WRITE.name'){ if(role == '@Role.DEVELOPER.name'){
writeButton.addClass('active'); writeButton.addClass('active');
} }
var readButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Permission.READ.name" name="' + userName + '">Read</label>'); var readButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.GUEST.name" name="' + userName + '">Guest</label>');
if(permission == '@Permission.READ.name'){ if(role == '@Role.GUEST.name'){
readButton.addClass('active'); readButton.addClass('active');
} }
$(id).append($('<li>') $(id).append($('<li>')
.data('name', userName) .data('name', userName)
.append($('<div class="btn-group permission" data-toggle="buttons">') .append($('<div class="btn-group role" data-toggle="buttons">')
.append(adminButton) .append(adminButton)
.append(writeButton) .append(writeButton)
.append(readButton)) .append(readButton))

View File

@@ -66,8 +66,8 @@
--> -->
<label class="checkbox"><input type="checkbox" @check("events",IssueComment) />Issue comment <span class="help-block normal">Issue commented on. </span> </label> <label class="checkbox"><input type="checkbox" @check("events",IssueComment) />Issue comment <span class="help-block normal">Issue commented on. </span> </label>
<label class="checkbox"><input type="checkbox" @check("events",Issues) />Issues <span class="help-block normal">Issue opened, closed<!-- , assigned, or labeled -->. </span> </label> <label class="checkbox"><input type="checkbox" @check("events",Issues) />Issues <span class="help-block normal">Issue opened, closed<!-- , assigned, or labeled -->. </span> </label>
<label class="checkbox"><input type="checkbox" @check("events",PullRequest) />Pull Request <span class="help-block normal">Pull Request opened, closed<!-- , assigned, labeled -->, or synchronized. </span> </label> <label class="checkbox"><input type="checkbox" @check("events",PullRequest) />Pull request <span class="help-block normal">Pull request opened, closed<!-- , assigned, labeled -->, or synchronized. </span> </label>
<label class="checkbox"><input type="checkbox" @check("events",PullRequestReviewComment) />Pull Request review comment <span class="help-block normal">Pull Request diff commented on. </span> </label> <label class="checkbox"><input type="checkbox" @check("events",PullRequestReviewComment) />Pull request review comment <span class="help-block normal">Pull request diff commented on. </span> </label>
<label class="checkbox"><input type="checkbox" @check("events",Push) />Push <span class="help-block normal">Git push to a repository. </span> </label> <label class="checkbox"><input type="checkbox" @check("events",Push) />Push <span class="help-block normal">Git push to a repository. </span> </label>
<div class="text-right"> <div class="text-right">
@if(!create){ @if(!create){

View File

@@ -61,12 +61,17 @@
</div> </div>
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="issuesOption" value="PRIVATE" @if(repository.repository.options.issuesOption == "PRIVATE"){ checked}> Writable users can view, create and comment on issues <input type="radio" name="issuesOption" value="PRIVATE" @if(repository.repository.options.issuesOption == "PRIVATE"){ checked}> Developers can view, create and comment on issues
</label> </label>
</div> </div>
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="issuesOption" value="PUBLIC" @if(repository.repository.options.issuesOption == "PUBLIC"){ checked}> Readable users can view, create and comment on isues <input type="radio" name="issuesOption" value="PUBLIC" @if(repository.repository.options.issuesOption == "PUBLIC"){ checked}> Developers and guests can view, create and comment on isues
</label>
</div>
<div class="radio for-public-repo">
<label>
<input type="radio" name="issuesOption" value="ALL" @if(repository.repository.options.issuesOption == "ALL"){ checked}> All users can view, create and comment on isues
</label> </label>
</div> </div>
<label for="externalIssuesUrl" class="strong">External URL: <label for="externalIssuesUrl" class="strong">External URL:
@@ -87,12 +92,17 @@
</div> </div>
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="wikiOption" value="PRIVATE" @if(repository.repository.options.wikiOption == "PRIVATE"){ checked}> Writable users can view, create and edit wiki pages <input type="radio" name="wikiOption" value="PRIVATE" @if(repository.repository.options.wikiOption == "PRIVATE"){ checked}> Developers can view, create and edit wiki pages
</label> </label>
</div> </div>
<div class="radio"> <div class="radio">
<label> <label>
<input type="radio" name="wikiOption" value="PUBLIC" @if(repository.repository.options.wikiOption == "PUBLIC"){ checked}> Readable users can view, create and edit wiki pages <input type="radio" name="wikiOption" value="PUBLIC" @if(repository.repository.options.wikiOption == "PUBLIC"){ checked}> Developers and guests can view, create and edit wiki pages
</label>
</div>
<div class="radio for-public-repo">
<label>
<input type="radio" name="wikiOption" value="ALL" @if(repository.repository.options.issuesOption == "ALL"){ checked}> All users can view, create and comment on isues
</label> </label>
</div> </div>
<label for="externalWikiUrl" class="strong">External URL: <label for="externalWikiUrl" class="strong">External URL:
@@ -112,14 +122,25 @@
<script> <script>
$(function(){ $(function(){
updateFeatures(); updateFeatures();
$('input[name=isPrivate], input[name=issuesOption], input[name=wikiOption]').click(function(){
$('input[name=issuesOption], input[name=wikiOption]').click(function(){
updateFeatures(); updateFeatures();
}); });
}); });
function updateFeatures() { function updateFeatures() {
$('#externalIssuesUrl').prop('disabled', !$('input[name=issuesOption]').select('[value=DISABLE]').prop('checked')); if($('input[name=isPrivate]:checked').val() == 'false'){
$('#externalWikiUrl').prop('disabled', !$('input[name=wikiOption]').select('[value=DISABLE]').prop('checked')); $('.for-public-repo').show();
} else {
if($('input[name=issuesOption]:checked').val() == 'ALL'){
$('input[name=issuesOption][value=PUBLIC]').prop('checked', true);
}
if($('input[name=wikiOption]:checked').val() == 'ALL'){
$('input[name=wikiOption][value=PUBLIC]').prop('checked', true);
}
$('.for-public-repo').hide();
}
$('#externalIssuesUrl').prop('disabled', $('input[name=issuesOption]:checked').val() != 'DISABLE');
$('#externalWikiUrl').prop('disabled', $('input[name=wikiOption]:checked').val() != 'DISABLE');
} }
</script> </script>

View File

@@ -7,10 +7,9 @@
@gitbucket.core.html.menu("wiki", repository){ @gitbucket.core.html.menu("wiki", repository){
<div class="pull-right"> <div class="pull-right">
@if(page.isDefined){ @if(page.isDefined){
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a> <a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a>
<a class="btn btn-small btn-danger" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_delete" id="delete">Delete Page</a> <a class="btn btn-danger" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_delete" id="delete">Delete Page</a>
} }
<a class="btn btn-small btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a>
</div> </div>
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1> <h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
<form action="@helpers.url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true"> <form action="@helpers.url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
@@ -44,9 +43,22 @@
</form> </form>
} }
} }
<script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script><script> <script>
$(function(){ $(function(){
try { try {
$('#content1').dropzone({
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
maxFilesize: 10,
clickable: false,
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
success: function(file, id) {
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
$('#content1').val($('#content1').val() + attachFile);
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
}
});
$('.clickable').dropzone({ $('.clickable').dropzone({
url: '@context.path/upload/wiki/@repository.owner/@repository.name', url: '@context.path/upload/wiki/@repository.owner/@repository.name',
maxFilesize: 10, maxFilesize: 10,

View File

@@ -8,12 +8,12 @@
@if(isEditable) { @if(isEditable) {
<div class="pull-right"> <div class="pull-right">
@if(pageName.isEmpty) { @if(pageName.isEmpty) {
<a class="btn btn-small" href="@helpers.url(repository)/wiki/_new">New Page</a> <a class="btn" href="@helpers.url(repository)/wiki/_new">New Page</a>
} else { } else {
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_edit">Edit Page</a> <a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_edit">Edit Page</a>
<a class="btn btn-small btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a> <a class="btn btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a>
} }
</div> </div>
} }
<h1 class="wiki-title"> <h1 class="wiki-title">
@if(pageName.isEmpty){ @if(pageName.isEmpty){
@@ -28,7 +28,7 @@
<th colspan="3"> <th colspan="3">
<div class="pull-left" style="padding-top: 4px;">Revisions</div> <div class="pull-left" style="padding-top: 4px;">Revisions</div>
<div class="pull-right"> <div class="pull-right">
<input type="button" id="compare" value="Compare Revisions" class="btn btn-dm btn-default"/> <input type="button" id="compare" value="Compare Revisions" class="btn btn-sm btn-default"/>
</div> </div>
</th> </th>
</tr> </tr>

View File

@@ -10,13 +10,20 @@
@gitbucket.core.html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){ @gitbucket.core.html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("wiki", repository){ @gitbucket.core.html.menu("wiki", repository){
<div> <div>
<div class="pull-right"> <form method="GET" action="@helpers.url(repository)/search" id="search-filter-form" class="form-inline pull-right">
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a> <div class="input-group">
<input type="text" class="form-control" name="q" placeholder="Search..."/>
<input type="hidden" name="type" value="wiki"/>
<span class="input-group-btn">
<button type="submit" id="search-btn" class="btn btn-default"><i class="fa fa-search"></i></button>
</span>
</div>
<a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_history">Page History</a>
@if(isEditable){ @if(isEditable){
<a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_edit">Edit Page</a> <a class="btn btn-default" href="@helpers.url(repository)/wiki/@helpers.urlEncode(pageName)/_edit">Edit Page</a>
<a class="btn btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a> <a class="btn btn-success" href="@helpers.url(repository)/wiki/_new">New Page</a>
} }
</div> </form>
<h1 class="body-title">@pageName</h1> <h1 class="body-title">@pageName</h1>
<div> <div>
<span class="muted"><strong>@page.committer</strong> edited this page @gitbucket.core.helper.html.datetimeago(page.time)</span> <span class="muted"><strong>@page.committer</strong> edited this page @gitbucket.core.helper.html.datetimeago(page.time)</span>
@@ -64,7 +71,7 @@
<div> <div>
<strong>Clone this wiki locally</strong> <strong>Clone this wiki locally</strong>
</div> </div>
@gitbucket.core.helper.html.copy("repository-url-copy", WikiService.wikiHttpUrl(repository)){ @gitbucket.core.helper.html.copy("repository-url", "repository-url-copy", WikiService.wikiHttpUrl(repository)){
<input type="text" value="@WikiService.wikiHttpUrl(repository)" id="repository-url" class="form-control input-sm" readonly> <input type="text" value="@WikiService.wikiHttpUrl(repository)" id="repository-url" class="form-control input-sm" readonly>
} }
@if(WikiService.wikiSshUrl(repository).isDefined) { @if(WikiService.wikiSshUrl(repository).isDefined) {

View File

@@ -9,11 +9,9 @@
<h1 class="wiki-title"><span class="muted">Pages</span></h1> <h1 class="wiki-title"><span class="muted">Pages</span></h1>
</li> </li>
<li class="pull-right"> <li class="pull-right">
<div class="btn-group"> @if(isEditable){
@if(isEditable){ <a class="btn btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a>
<a class="btn btn-small btn-default" href="@helpers.url(repository)/wiki/_new">New Page</a> }
}
</div>
</li> </li>
</ul> </ul>
<ul class="pull-left"> <ul class="pull-left">

View File

@@ -174,12 +174,6 @@ span.error {
font-weight: normal; font-weight: normal;
} }
.shorten-text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
a.commit-message, a.commit-id, a.username, a.issue-comment-count { a.commit-message, a.commit-id, a.username, a.issue-comment-count {
color: #333333; color: #333333;
} }
@@ -299,6 +293,20 @@ th.box-header .octicon {
display: inline; display: inline;
} }
td.ellipsis-cell {
position: relative;
}
td.ellipsis-cell > * {
position: absolute;
left: 0;
right: 0;
padding-right: 5px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
img.avatar { img.avatar {
border-radius: 4px; border-radius: 4px;
-webkit-border-radius: 4px; -webkit-border-radius: 4px;
@@ -941,7 +949,7 @@ pre.reset.discussion-item-content-text{
} }
/****************************************************************************/ /****************************************************************************/
/* Pull Request */ /* Pull request */
/****************************************************************************/ /****************************************************************************/
div.pullreq-info { div.pullreq-info {
border: 1px solid #ddd; border: 1px solid #ddd;

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic); @import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic);
/*! /*!
* AdminLTE v2.3.6 * AdminLTE v2.3.8
* Author: Almsaeed Studio * Author: Almsaeed Studio
* Website: Almsaeed Studio <http://almsaeedstudio.com> * Website: Almsaeed Studio <http://almsaeedstudio.com>
* License: Open source - MIT * License: Open source - MIT
@@ -543,7 +543,14 @@ a:focus {
padding: 0; padding: 0;
margin-right: 10px; margin-right: 10px;
} }
.sidebar-menu li.active > a > .fa-angle-left > a > .pull-right-container > .fa-angle-left { .sidebar-menu li > a > .fa-angle-left {
position: absolute;
top: 50%;
right: 10px;
margin-top: -8px;
}
.sidebar-menu li.active > a > .fa-angle-left,
.sidebar-menu li.active > a > .pull-right-container > .fa-angle-left {
-webkit-transform: rotate(-90deg); -webkit-transform: rotate(-90deg);
-ms-transform: rotate(-90deg); -ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg); -o-transform: rotate(-90deg);
@@ -1269,7 +1276,8 @@ a:focus {
.form-group.has-success label { .form-group.has-success label {
color: #00a65a; color: #00a65a;
} }
.form-group.has-success .form-control { .form-group.has-success .form-control,
.form-group.has-success .input-group-addon {
border-color: #00a65a; border-color: #00a65a;
box-shadow: none; box-shadow: none;
} }
@@ -1279,7 +1287,8 @@ a:focus {
.form-group.has-warning label { .form-group.has-warning label {
color: #f39c12; color: #f39c12;
} }
.form-group.has-warning .form-control { .form-group.has-warning .form-control,
.form-group.has-warning .input-group-addon {
border-color: #f39c12; border-color: #f39c12;
box-shadow: none; box-shadow: none;
} }
@@ -1289,7 +1298,8 @@ a:focus {
.form-group.has-error label { .form-group.has-error label {
color: #dd4b39; color: #dd4b39;
} }
.form-group.has-error .form-control { .form-group.has-error .form-control,
.form-group.has-error .input-group-addon {
border-color: #dd4b39; border-color: #dd4b39;
box-shadow: none; box-shadow: none;
} }

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More