Merge branch 'master' into solidbase-integration

# Conflicts:
#	build.sbt
#	src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
#	src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
This commit is contained in:
Naoki Takezoe
2016-03-06 13:07:03 +09:00
110 changed files with 1872 additions and 1989 deletions

7
.github/CONTRIBUTING.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# Guideline for Issues
- At first, See [FAQ](https://github.com/gitbucket/gitbucket/wiki/FAQ) and check issues whether there is a same question or request in the past.
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- Write an issue in English. At least, write subject in English.
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.

19
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,19 @@
### Before submitting an issue to Gitbucket I have first:
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
- [] searched for similar already existing issue
- [] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
## Issue
**Impacted version**: xxxx
**Deployment mode**: *explain here how you use gitbucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
**Problem description**:
- *be as explicit has you can*
- *describe the problem and its symptoms*
- *explain how to reproduce*
- *attach whatever information that can help understanding the context (screen capture, log files)*
- *do your best to use a correct english (re-read yourself)*

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
### Before submitting a pull-request to Gitbucket I have first:
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
- [] rebased my branch over master
- [] verified that project is compiling
- [] verified that tests are passing
- [] squashed my commits as appropriate *(keep several commits if it is relevant to understand the PR)*
- [] [marked as closed](https://help.github.com/articles/closing-issues-via-commit-messages/) all issue ID that this PR should correct

View File

@@ -1,7 +0,0 @@
# Guideline for Issues
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
- Make sure check whether there is a same question or request in the past.
- When raise a new issue, write subject in **English** at least.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.

View File

@@ -60,6 +60,22 @@ Support
Release Notes
--------
### 3.12 - 27 Feb 2016
- New GitHub UI
- Improve mobile view
- Improve printing style
- Individual URL for pull request tabs
- SSH host configuration is separated from HTTP base URL
### 3.11 - 30 Jan 2016
- Upgrade Scalatra to 2.4
- Sidebar and Footer for Wiki
- Branch protection and receive hook extension point for plug-in
- Limit recent updated repositories list
- Issue actions look-alike GitHub
- Web API for labels
- Requires Java 8
### 3.10 - 30 Dec 2015
- Move to Bootstrap3
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`)

View File

@@ -1,6 +1,6 @@
val Organization = "gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "3.11.0-SNAPSHOT"
val GitBucketVersion = "3.12.0"
val ScalatraVersion = "2.4.0"
val JettyVersion = "9.3.6.v20151106"
@@ -10,13 +10,12 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.11.6"
scalaVersion := "2.11.7"
// dependency settings
resolvers ++= Seq(
Classpaths.typesafeReleases,
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
@@ -26,8 +25,8 @@ libraryDependencies ++= Seq(
"org.json4s" %% "json4s-jackson" % "3.3.0",
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
"commons-io" % "commons-io" % "2.4",
"io.github.gitbucket" % "markedj" % "1.0.6",
"io.github.gitbucket" % "solidbase" % "1.0.0-SNAPSHOT",
"io.github.gitbucket" % "markedj" % "1.0.7-SNAPSHOT",
"org.apache.commons" % "commons-compress" % "1.10",
"org.apache.commons" % "commons-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
@@ -40,12 +39,13 @@ libraryDependencies ++= Seq(
"com.mchange" % "c3p0" % "0.9.5.2",
"com.typesafe" % "config" % "1.3.0",
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.12" % "test",
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
"org.specs2" %% "specs2-junit" % "3.6.6" % "test"
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
"org.scalaz" %% "scalaz-core" % "7.2.0" % "test"
)
// Twirl settings
@@ -53,7 +53,7 @@ play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
// Compiler settings
scalacOptions := Seq("-deprecation", "-language:postfixOps")
javacOptions in compile ++= Seq("-target", "7", "-source", "7")
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console")
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
@@ -83,36 +83,36 @@ jrebelSettings
// Create executable war file
val executableConfig = config("executable").hide
Keys.ivyConfigurations += executableConfig
libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
)
val executableKey = TaskKey[File]("executable")
executableKey := {
val executableKey = TaskKey[File]("executable")
executableKey := {
import org.apache.ivy.util.ChecksumHelper
import java.util.jar.{ Manifest => JarManifest }
import java.util.jar.Attributes.{ Name => AttrName }
val workDir = Keys.target.value / "executable"
val warName = Keys.name.value + ".war"
val workDir = Keys.target.value / "executable"
val warName = Keys.name.value + ".war"
val log = streams.value.log
val log = streams.value.log
log info s"building executable webapp in ${workDir}"
// initialize temp directory
val temp = workDir / "webapp"
val temp = workDir / "webapp"
IO delete temp
// include jetty classes
val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name)
val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name)
jettyJars foreach { jar =>
IO unzip (jar, temp, (name:String) =>
(name startsWith "javax/") ||
@@ -121,31 +121,34 @@ executableKey := {
}
// include original war file
val warFile = (Keys.`package`).value
val warFile = (Keys.`package`).value
IO unzip (warFile, temp)
// include launcher classes
val classDir = (Keys.classDirectory in Compile).value
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */)
val classDir = (Keys.classDirectory in Compile).value
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */)
launchClasses foreach { name =>
IO copyFile (classDir / name, temp / name)
}
// zip it up
IO delete (temp / "META-INF" / "MANIFEST.MF")
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
val manifest = new JarManifest
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
val outputFile = workDir / warName
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
val manifest = new JarManifest
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
val outputFile = workDir / warName
IO jar (contentMappings, outputFile, manifest)
// generate checksums
Seq("md5", "sha1") foreach { algorithm =>
IO.write(
workDir / (warName + "." + algorithm),
ChecksumHelper computeAsString (outputFile, algorithm)
)
Seq(
"md5" -> "MD5",
"sha1" -> "SHA-1",
"sha256" -> "SHA-256"
)
.foreach { case (extension, algorithm) =>
val checksumFile = workDir / (warName + "." + extension)
Checksums generate (outputFile, checksumFile, algorithm)
}
// done
@@ -154,7 +157,7 @@ executableKey := {
}
/*
Keys.artifact in (Compile, executableKey) ~= {
_ copy (`type` = "war", extension = "war"))
_ copy (`type` = "war", extension = "war"))
}
addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
*/

View File

@@ -27,7 +27,16 @@ $ sbt package
To build executable war file, run
* Windows: Not available
* Linux: `./release/make-release-war.sh`
```
$ sbt executable
```
at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact.
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact.
Run tests spec
---------
To run the full serie of tests, run the following command:
```
sbt test
```

View File

@@ -6,15 +6,14 @@ Update version number
Note to update version number in files below:
### project/build.scala
### build.sbt
```scala
object MyBuild extends Build {
val Organization = "gitbucket"
val Name = "gitbucket"
val Version = "3.3.0" // <---- update version!!
val ScalaVersion = "2.11.6"
val ScalatraVersion = "2.3.1"
val Organization = "gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "3.12.0" // <---- update version!!
val ScalatraVersion = "2.4.0"
val JettyVersion = "9.3.6.v20151106"
```
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
@@ -26,8 +25,8 @@ object AutoUpdate {
* The history of versions. A head of this sequence is the current GitBucket version.
*/
val versions = Seq(
new Version(3, 3), // <---- add this line!!
new Version(3, 2),
new Version(3, 12), // <---- add this line!!
new Version(3, 11),
```
Generate release files

34
project/Checksums.scala Normal file
View File

@@ -0,0 +1,34 @@
import java.security.MessageDigest;
import scala.annotation._
import sbt._
import sbt.Using._
object Checksums {
private val bufferSize = 2048
def generate(source:File, target:File, algorithm:String):Unit =
IO write (target, compute(source, algorithm))
def compute(file:File, algorithm:String):String =
hex(raw(file, algorithm))
def raw(file:File, algorithm:String):Array[Byte] =
(Using fileInputStream file) { is =>
val md = MessageDigest getInstance algorithm
val buf = new Array[Byte](bufferSize)
md.reset()
@tailrec
def loop() {
val len = is read buf
if (len != -1) {
md update (buf, 0, len)
loop()
}
}
loop()
md.digest()
}
def hex(bytes:Array[Byte]):String =
bytes map { it => "%02x" format (it.toInt & 0xff) } mkString ""
}

View File

@@ -1 +1 @@
sbt.version=0.13.8
sbt.version=0.13.9

View File

@@ -1,3 +1,3 @@
#!/bin/sh
export GITBUCKET_VERSION=`cat ../project/build.scala | grep 'val Version' | cut -d \" -f 2`
export GITBUCKET_VERSION=`cat ../build.sbt | grep 'val GitBucketVersion' | cut -d \" -f 2`
echo "GITBUCKET_VERSION: $GITBUCKET_VERSION"

View File

@@ -1,2 +1,2 @@
set SCRIPT_DIR=%~dp0
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.8.jar" %*
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.9.jar" %*

2
sbt.sh
View File

@@ -1,2 +1,2 @@
#!/bin/sh
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.8.jar "$@"
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.9.jar "$@"

View File

@@ -0,0 +1 @@
ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100);

View File

@@ -27,12 +27,9 @@ class ScalatraBootstrap extends LifeCycle {
}
context.mount(new IndexController, "/")
context.mount(new SearchController, "/")
context.mount(new FileUploadController, "/upload")
context.mount(new DashboardController, "/*")
context.mount(new UserManagementController, "/*")
context.mount(new SystemSettingsController, "/*")
context.mount(new PluginsController, "/*")
context.mount(new AccountController, "/*")
context.mount(new RepositoryViewerController, "/*")
context.mount(new WikiController, "/*")

View File

@@ -133,7 +133,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val members = getGroupMembers(account.userName)
gitbucket.core.account.html.repositories(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
getVisibleRepositories(context.loginAccount, Some(userName)),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
}
}
@@ -366,7 +366,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
*/
post("/new", newRepositoryForm)(usersOnly { form =>
LockUtil.lock(s"${form.owner}/${form.name}"){
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
if(getRepository(form.owner, form.name).isEmpty){
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme)
}
@@ -385,9 +385,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${owner}/${data.name}") {
if(getRepository(owner, data.name, context.baseUrl).isEmpty){
if(getRepository(owner, data.name).isEmpty){
createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(owner, data.name, context.baseUrl).get
val repository = getRepository(owner, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else {
ApiError(
@@ -409,9 +409,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${groupName}/${data.name}") {
if(getRepository(groupName, data.name, context.baseUrl).isEmpty){
if(getRepository(groupName, data.name).isEmpty){
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(groupName, data.name, context.baseUrl).get
val repository = getRepository(groupName, data.name).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else {
ApiError(
@@ -447,7 +447,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val accountName = form.accountName
LockUtil.lock(s"${accountName}/${repository.name}"){
if(getRepository(accountName, repository.name, baseUrl).isDefined ||
if(getRepository(accountName, repository.name).isDefined ||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
// redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}")

View File

@@ -180,7 +180,6 @@ abstract class ControllerBase extends ScalatraFilter
* Context object for the current request.
*/
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
val path = settings.baseUrl.getOrElse(request.getContextPath)
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
val baseUrl = settings.baseUrl(request)

View File

@@ -94,7 +94,7 @@ trait DashboardControllerBase extends ControllerBase {
val userName = context.loginAccount.get.userName
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request)
html.issues(

View File

@@ -2,35 +2,46 @@ package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.helper.xml
import gitbucket.core.html
import gitbucket.core.model.Account
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService}
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService, RepositorySearchService, IssuesService}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
import io.github.gitbucket.scalatra.forms._
class IndexController extends IndexControllerBase
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
with RepositoryService with ActivityService with AccountService with RepositorySearchService with IssuesService
with UsersAuthenticator with ReferrerAuthenticator
trait IndexControllerBase extends ControllerBase {
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
self: RepositoryService with ActivityService with AccountService with RepositorySearchService
with UsersAuthenticator with ReferrerAuthenticator =>
case class SignInForm(userName: String, password: String)
val form = mapping(
val signinForm = mapping(
"userName" -> trim(label("Username", text(required))),
"password" -> trim(label("Password", text(required)))
)(SignInForm.apply)
val searchForm = mapping(
"query" -> trim(text(required)),
"owner" -> trim(text(required)),
"repository" -> trim(text(required))
)(SearchForm.apply)
case class SearchForm(query: String, owner: String, repository: String)
get("/"){
val loginAccount = context.loginAccount
if(loginAccount.isEmpty) {
html.index(getRecentActivities(),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
gitbucket.core.html.index(getRecentActivities(),
getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
)
} else {
val loginUserName = loginAccount.get.userName
@@ -39,9 +50,9 @@ trait IndexControllerBase extends ControllerBase {
visibleOwnerSet ++= loginUserGroups
html.index(getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
gitbucket.core.html.index(getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(loginAccount, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, withoutPhysicalInfo = true) }.getOrElse(Nil)
)
}
}
@@ -51,10 +62,10 @@ trait IndexControllerBase extends ControllerBase {
if(redirect.isDefined && redirect.get.startsWith("/")){
flash += Keys.Flash.Redirect -> redirect.get
}
html.signin()
gitbucket.core.html.signin()
}
post("/signin", form){ form =>
post("/signin", signinForm){ form =>
authenticate(context.settings, form.userName, form.password) match {
case Some(account) => signin(account)
case None => redirect("/signin")
@@ -119,4 +130,33 @@ trait IndexControllerBase extends ControllerBase {
// this message is same as github enterprise...
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
}
// TODO Move to RepositoryViwerController?
post("/search", searchForm){ form =>
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
}
// TODO Move to RepositoryViwerController?
get("/:owner/:repository/search")(referrersOnly { repository =>
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
val page = try {
val i = params.getOrElse("page", "1").toInt
if(i <= 0) 1 else i
} catch {
case e: NumberFormatException => 1
}
target.toLowerCase match {
case "issue" => gitbucket.core.search.html.issues(
searchIssues(repository.owner, repository.name, query),
countFiles(repository.owner, repository.name, query),
query, page, repository)
case _ => gitbucket.core.search.html.code(
searchFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query),
query, page, repository)
}
}
})
}

View File

@@ -1,11 +0,0 @@
package gitbucket.core.controller
import gitbucket.core.admin.plugins.html
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.util.AdminAuthenticator
class PluginsController extends ControllerBase with AdminAuthenticator {
get("/admin/plugins")(adminOnly {
html.plugins(PluginRegistry().getPlugins())
})
}

View File

@@ -137,7 +137,7 @@ trait PullRequestsControllerBase extends ControllerBase {
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
JsonFormat(ApiPullRequest(
issue,
@@ -196,7 +196,7 @@ trait PullRequestsControllerBase extends ControllerBase {
issue,
pullreq,
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get)
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get)
}
} getOrElse NotFound
})
@@ -229,7 +229,7 @@ trait PullRequestsControllerBase extends ControllerBase {
if(branchProtection.needStatusCheck(loginAccount.userName)){
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
} else {
val repository = getRepository(owner, name, context.baseUrl).get
val repository = getRepository(owner, name).get
LockUtil.lock(s"${owner}/${name}"){
val alias = if(pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName){
pullreq.branch
@@ -310,7 +310,7 @@ trait PullRequestsControllerBase extends ControllerBase {
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
// close issue by content of pull request
val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch
val defaultBranch = getRepository(owner, name).get.repository.defaultBranch
if(pullreq.branch == defaultBranch){
commits.flatten.foreach { commit =>
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
@@ -343,7 +343,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val headBranch:Option[String] = params.get("head")
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => {
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository =>
getRepository(originUserName, originRepositoryName).map { originRepository =>
using(
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
@@ -384,12 +384,12 @@ trait PullRequestsControllerBase extends ControllerBase {
forkedRepository.repository.originRepositoryName
} else {
// Sibling repository
getUserRepositories(originOwner, context.baseUrl).find { x =>
getUserRepositories(originOwner).find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}.map(_.repository.repositoryName)
};
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
using(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
@@ -457,7 +457,7 @@ trait PullRequestsControllerBase extends ControllerBase {
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
}
};
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
using(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),

View File

@@ -49,11 +49,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
)(CollaboratorForm.apply)
// for web hook url addition
case class WebHookForm(url: String, events: Set[WebHook.Event])
case class WebHookForm(url: String, events: Set[WebHook.Event], token: Option[String])
def webHookForm(update:Boolean) = mapping(
"url" -> trim(label("url", text(required, webHook(update)))),
"events" -> webhookEvents
"events" -> webhookEvents,
"token" -> optional(trim(label("token", text(maxlength(100)))))
)(WebHookForm.apply)
// for transfer ownership
@@ -198,7 +199,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook edit page.
*/
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
val webhook = WebHook(repository.owner, repository.name, "")
val webhook = WebHook(repository.owner, repository.name, "", None)
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
})
@@ -206,7 +207,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Add the web hook URL.
*/
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
addWebHook(repository.owner, repository.name, form.url, form.events)
addWebHook(repository.owner, repository.name, form.url, form.events, form.token)
flash += "info" -> s"Webhook ${form.url} created"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})
@@ -235,7 +236,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
import scala.concurrent.ExecutionContext.Implicits.global
val url = params("url")
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url)
val token = Some(params("token"))
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, token)
val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get
val commits = if(repository.commitCount == 0) List.empty else git.log
@@ -294,7 +296,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Update web hook settings.
*/
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
updateWebHook(repository.owner, repository.name, form.url, form.events)
updateWebHook(repository.owner, repository.name, form.url, form.events, form.token)
flash += "info" -> s"webhook ${form.url} updated"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})

View File

@@ -560,8 +560,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
html.forked(
getRepository(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name),
context.baseUrl),
repository.repository.originRepositoryName.getOrElse(repository.name)),
getForkedRepositories(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name)),
@@ -759,8 +758,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
.setTree(revCommit.getTree)
.setOutputStream(response.getOutputStream)
.call()
Unit
}
}

View File

@@ -1,51 +0,0 @@
package gitbucket.core.controller
import gitbucket.core.search.html
import gitbucket.core.service._
import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits}
import ControlUtil._
import Implicits._
import io.github.gitbucket.scalatra.forms._
class SearchController extends SearchControllerBase
with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator
trait SearchControllerBase extends ControllerBase { self: RepositoryService
with ActivityService with RepositorySearchService with ReferrerAuthenticator =>
val searchForm = mapping(
"query" -> trim(text(required)),
"owner" -> trim(text(required)),
"repository" -> trim(text(required))
)(SearchForm.apply)
case class SearchForm(query: String, owner: String, repository: String)
post("/search", searchForm){ form =>
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
}
get("/:owner/:repository/search")(referrersOnly { repository =>
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
val page = try {
val i = params.getOrElse("page", "1").toInt
if(i <= 0) 1 else i
} catch {
case e: NumberFormatException => 1
}
target.toLowerCase match {
case "issue" => html.issues(
searchIssues(repository.owner, repository.name, query),
countFiles(repository.owner, repository.name, query),
query, page, repository)
case _ => html.code(
searchFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query),
query, page, repository)
}
}
})
}

View File

@@ -1,17 +1,24 @@
package gitbucket.core.controller
import gitbucket.core.admin.html
import gitbucket.core.service.{AccountService, SystemSettingsService}
import gitbucket.core.service.{AccountService, SystemSettingsService, RepositoryService}
import gitbucket.core.util.AdminAuthenticator
import gitbucket.core.ssh.SshServer
import gitbucket.core.plugin.PluginRegistry
import SystemSettingsService._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.StringUtil._
import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages
class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with AdminAuthenticator
with AccountService with RepositoryService with AdminAuthenticator
trait SystemSettingsControllerBase extends ControllerBase {
self: AccountService with AdminAuthenticator =>
trait SystemSettingsControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with AdminAuthenticator =>
private val form = mapping(
"baseUrl" -> trim(label("Base URL", optional(text()))),
@@ -23,6 +30,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
"notification" -> trim(label("Notification", boolean())),
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
"ssh" -> trim(label("SSH access", boolean())),
"sshHost" -> trim(label("SSH host", optional(text()))),
"sshPort" -> trim(label("SSH port", optional(number()))),
"useSMTP" -> trim(label("SMTP", boolean())),
"smtp" -> optionalIfNotChecked("useSMTP", mapping(
@@ -50,11 +58,77 @@ trait SystemSettingsControllerBase extends ControllerBase {
"keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply))
)(SystemSettings.apply).verifying { settings =>
if(settings.ssh && settings.baseUrl.isEmpty){
Seq("baseUrl" -> "Base URL is required if SSH access is enabled.")
} else Nil
Vector(
if(settings.ssh && settings.baseUrl.isEmpty){
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
} else None,
if(settings.ssh && settings.sshHost.isEmpty){
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
} else None
).flatten
}
private val pluginForm = mapping(
"pluginId" -> list(trim(label("", text())))
)(PluginForm.apply)
case class PluginForm(pluginIds: List[String])
case class NewUserForm(userName: String, password: String, fullName: String,
mailAddress: String, isAdmin: Boolean,
url: Option[String], fileId: Option[String])
case class EditUserForm(userName: String, password: Option[String], fullName: String,
mailAddress: String, isAdmin: Boolean, url: Option[String],
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String)
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String, clearImage: Boolean, isRemoved: Boolean)
val newUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text())))
)(NewUserForm.apply)
val editUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
"password" -> trim(label("Password" ,optional(text(maxlength(20))))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
)(EditUserForm.apply)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members)))
)(NewGroupForm.apply)
val editGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean()))
)(EditGroupForm.apply)
get("/admin/system")(adminOnly {
html.system(flash.get("info"))
})
@@ -62,20 +136,155 @@ trait SystemSettingsControllerBase extends ControllerBase {
post("/admin/system", form)(adminOnly { form =>
saveSystemSettings(form)
if(form.ssh && SshServer.isActive && context.settings.sshPort != form.sshPort){
SshServer.stop()
}
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
SshServer.start(
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
form.baseUrl.get)
} else if(!form.ssh && SshServer.isActive){
if (form.sshAddress != context.settings.sshAddress) {
SshServer.stop()
for {
sshAddress <- form.sshAddress
baseUrl <- form.baseUrl
}
SshServer.start(sshAddress, baseUrl)
}
flash += "info" -> "System settings has been updated."
redirect("/admin/system")
})
get("/admin/plugins")(adminOnly {
html.plugins(PluginRegistry().getPlugins())
})
get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
val users = getAllUsers(includeRemoved)
val members = users.collect { case account if(account.isGroupAccount) =>
account.userName -> getGroupMembers(account.userName).map(_.userName)
}.toMap
html.userlist(users, members, includeRemoved)
})
get("/admin/users/_newuser")(adminOnly {
html.user(None)
})
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.url)
updateImage(form.userName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName")
html.user(getAccountByUserName(userName, true))
})
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
val userName = params("userName")
getAccountByUserName(userName, true).map { account =>
if(form.isRemoved){
// Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// deleteRepository(userName, repositoryName)
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// }
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
removeUserRelatedData(userName)
}
updateAccount(account.copy(
password = form.password.map(sha1).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
url = form.url,
isRemoved = form.isRemoved))
updateImage(userName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
})
get("/admin/users/_newgroup")(adminOnly {
html.usergroup(None, Nil)
})
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
createGroup(form.groupName, form.url)
updateGroupMembers(form.groupName, form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList)
updateImage(form.groupName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:groupName/_editgroup")(adminOnly {
defining(params("groupName")){ groupName =>
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
})
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
defining(params("groupName"), form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList){ case (groupName, members) =>
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.url, form.isRemoved)
if(form.isRemoved){
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
// Remove repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
deleteRepository(groupName, repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
}
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// Update COLLABORATOR for group repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
removeCollaborators(form.groupName, repositoryName)
members.foreach { case (userName, isManager) =>
addCollaborator(form.groupName, repositoryName, userName)
}
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
}
})
private def members: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if(value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None else Some("Must select one manager at least.")
}
}
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None
}
}
}
}

View File

@@ -1,204 +0,0 @@
package gitbucket.core.controller
import gitbucket.core.service.{RepositoryService, AccountService}
import gitbucket.core.admin.users.html
import gitbucket.core.util._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.StringUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import io.github.gitbucket.scalatra.forms._
import org.scalatra.i18n.Messages
import org.apache.commons.io.FileUtils
class UserManagementController extends UserManagementControllerBase
with AccountService with RepositoryService with AdminAuthenticator
trait UserManagementControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with AdminAuthenticator =>
case class NewUserForm(userName: String, password: String, fullName: String,
mailAddress: String, isAdmin: Boolean,
url: Option[String], fileId: Option[String])
case class EditUserForm(userName: String, password: Option[String], fullName: String,
mailAddress: String, isAdmin: Boolean, url: Option[String],
fileId: Option[String], clearImage: Boolean, isRemoved: Boolean)
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String)
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
members: String, clearImage: Boolean, isRemoved: Boolean)
val newUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text())))
)(NewUserForm.apply)
val editUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier))),
"password" -> trim(label("Password" ,optional(text(maxlength(20))))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress("userName")))),
"isAdmin" -> trim(label("User Type" ,boolean())),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean(disableByNotYourself("userName"))))
)(EditUserForm.apply)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members)))
)(NewGroupForm.apply)
val editGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members))),
"clearImage" -> trim(label("Clear image" ,boolean())),
"removed" -> trim(label("Disable" ,boolean()))
)(EditGroupForm.apply)
get("/admin/users")(adminOnly {
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
val users = getAllUsers(includeRemoved)
val members = users.collect { case account if(account.isGroupAccount) =>
account.userName -> getGroupMembers(account.userName).map(_.userName)
}.toMap
html.list(users, members, includeRemoved)
})
get("/admin/users/_newuser")(adminOnly {
html.user(None)
})
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, form.isAdmin, form.url)
updateImage(form.userName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName")
html.user(getAccountByUserName(userName, true))
})
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
val userName = params("userName")
getAccountByUserName(userName, true).map { account =>
if(form.isRemoved){
// Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// deleteRepository(userName, repositoryName)
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// }
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
removeUserRelatedData(userName)
}
updateAccount(account.copy(
password = form.password.map(sha1).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
url = form.url,
isRemoved = form.isRemoved))
updateImage(userName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
})
get("/admin/users/_newgroup")(adminOnly {
html.group(None, Nil)
})
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
createGroup(form.groupName, form.url)
updateGroupMembers(form.groupName, form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList)
updateImage(form.groupName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:groupName/_editgroup")(adminOnly {
defining(params("groupName")){ groupName =>
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
})
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
defining(params("groupName"), form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList){ case (groupName, members) =>
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.url, form.isRemoved)
if(form.isRemoved){
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
// Remove repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
deleteRepository(groupName, repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
}
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// Update COLLABORATOR for group repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
removeCollaborators(form.groupName, repositoryName)
members.foreach { case (userName, isManager) =>
addCollaborator(form.groupName, repositoryName, userName)
}
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
}
})
private def members: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if(value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None else Some("Must select one manager at least.")
}
}
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None
}
}
}
}

View File

@@ -35,7 +35,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.labelId === labelId)
def byLabel(owner: String, repository: String, labelName: String) =
byRepository(userName, repositoryName) && (this.labelName === labelName.bind)
byRepository(owner, repository) && (this.labelName === labelName.bind)
}
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>

View File

@@ -7,7 +7,8 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
val url = column[String]("URL")
def * = (userName, repositoryName, url) <> ((WebHook.apply _).tupled, WebHook.unapply)
val token = column[Option[String]]("TOKEN", O.Nullable)
def * = (userName, repositoryName, url, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
}
@@ -16,7 +17,8 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
case class WebHook(
userName: String,
repositoryName: String,
url: String
url: String,
token: Option[String]
)
object WebHook {

View File

@@ -399,7 +399,7 @@ trait IssuesService {
object IssuesService {
import javax.servlet.http.HttpServletRequest
val IssueLimit = 30
val IssueLimit = 25
case class IssueSearchCondition(
labels: Set[String] = Set.empty,

View File

@@ -1,5 +1,6 @@
package gitbucket.core.service
import gitbucket.core.controller.Context
import gitbucket.core.model.{Collaborator, Repository, Account}
import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil
@@ -194,10 +195,9 @@ trait RepositoryService { self: AccountService =>
*
* @param userName the user name of the repository owner
* @param repositoryName the repository name
* @param baseUrl the base url of this application
* @return the repository information
*/
def getRepository(userName: String, repositoryName: String, baseUrl: String)(implicit s: Session): Option[RepositoryInfo] = {
def getRepository(userName: String, repositoryName: String)(implicit s: Session): Option[RepositoryInfo] = {
(Repositories filter { t => t.byRepository(userName, repositoryName) } firstOption) map { repository =>
// for getting issue count and pull request count
val issues = Issues.filter { t =>
@@ -205,7 +205,7 @@ trait RepositoryService { self: AccountService =>
}.map(_.pullRequest).list
new RepositoryInfo(
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl),
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName),
repository,
issues.count(_ == false),
issues.count(_ == true),
@@ -234,7 +234,7 @@ trait RepositoryService { self: AccountService =>
}.list
}
def getUserRepositories(userName: String, baseUrl: String, withoutPhysicalInfo: Boolean = false)
def getUserRepositories(userName: String, withoutPhysicalInfo: Boolean = false)
(implicit s: Session): List[RepositoryInfo] = {
Repositories.filter { t1 =>
(t1.userName === userName.bind) ||
@@ -242,9 +242,9 @@ trait RepositoryService { self: AccountService =>
}.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo(
if(withoutPhysicalInfo){
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName)
} else {
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName)
},
repository,
getForkedCount(
@@ -260,13 +260,12 @@ trait RepositoryService { self: AccountService =>
* If repositoryUserName is given then filters results by repository owner.
*
* @param loginAccount the logged in account
* @param baseUrl the base url of this application
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
* @param withoutPhysicalInfo if true then the result does not include physical repository information such as commit count,
* branches and tags
* @return the repository information which is sorted in descending order of lastActivityDate.
*/
def getVisibleRepositories(loginAccount: Option[Account], baseUrl: String, repositoryUserName: Option[String] = None,
def getVisibleRepositories(loginAccount: Option[Account], repositoryUserName: Option[String] = None,
withoutPhysicalInfo: Boolean = false)
(implicit s: Session): List[RepositoryInfo] = {
(loginAccount match {
@@ -284,9 +283,9 @@ trait RepositoryService { self: AccountService =>
}.sortBy(_.lastActivityDate desc).list.map{ repository =>
new RepositoryInfo(
if(withoutPhysicalInfo){
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
new JGitUtil.RepositoryInfo(repository.userName, repository.repositoryName)
} else {
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName, baseUrl)
JGitUtil.getRepositoryInfo(repository.userName, repository.repositoryName)
},
repository,
getForkedCount(
@@ -389,32 +388,39 @@ trait RepositoryService { self: AccountService =>
object RepositoryService {
case class RepositoryInfo(owner: String, name: String, httpUrl: String, repository: Repository,
case class RepositoryInfo(owner: String, name: String, repository: Repository,
issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int,
branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]){
lazy val host = """^https?://(.+?)(:\d+)?/""".r.findFirstMatchIn(httpUrl).get.group(1)
def sshUrl(port: Int, userName: String) = s"ssh://${userName}@${host}:${port}/${owner}/${name}.git"
def sshOpenRepoUrl(platform: String, port: Int, userName: String) = openRepoUrl(platform, sshUrl(port, userName))
def httpOpenRepoUrl(platform: String) = openRepoUrl(platform, httpUrl)
def openRepoUrl(platform: String, openUrl: String) = s"github-${platform}://openRepo/${openUrl}"
branchList: Seq[String], tags: Seq[JGitUtil.TagInfo], managers: Seq[String]) {
/**
* Creates instance with issue count and pull request count.
*/
def this(repo: JGitUtil.RepositoryInfo, model: Repository, issueCount: Int, pullCount: Int, forkedCount: Int, managers: Seq[String]) =
this(repo.owner, repo.name, repo.url, model, issueCount, pullCount, repo.commitCount, forkedCount, repo.branchList, repo.tags, managers)
this(
repo.owner, repo.name, model,
issueCount, pullCount, repo.commitCount, forkedCount,
repo.branchList, repo.tags, managers)
/**
* Creates instance without issue count and pull request count.
*/
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) =
this(repo.owner, repo.name, repo.url, model, 0, 0, repo.commitCount, forkedCount, repo.branchList, repo.tags, managers)
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) =
this(
repo.owner, repo.name, model,
0, 0, repo.commitCount, forkedCount,
repo.branchList, repo.tags, managers)
def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name)
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
}
case class RepositoryTreeNode(owner: String, name: String, children: List[RepositoryTreeNode])
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
if(context.settings.ssh){
context.loginAccount.flatMap { loginAccount =>
context.settings.sshAddress.map { x => s"ssh://${loginAccount.userName}@${x.host}:${x.port}/${owner}/${name}.git" }
}
} else None
def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}"
}

View File

@@ -1,6 +1,7 @@
package gitbucket.core.service
import gitbucket.core.util.{Directory, ControlUtil}
import gitbucket.core.util.Implicits._
import Directory._
import ControlUtil._
import SystemSettingsService._
@@ -21,6 +22,7 @@ trait SystemSettingsService {
props.setProperty(Notification, settings.notification.toString)
settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString))
props.setProperty(Ssh, settings.ssh.toString)
settings.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
props.setProperty(UseSMTP, settings.useSMTP.toString)
if(settings.useSMTP) {
@@ -75,6 +77,7 @@ trait SystemSettingsService {
getValue(props, Notification, false),
getOptionValue[Int](props, ActivityLogLimit, None),
getValue(props, Ssh, false),
getOptionValue[String](props, SshHost, None).map(_.trim),
getOptionValue(props, SshPort, Some(DefaultSshPort)),
getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP
if(getValue(props, UseSMTP, getValue(props, Notification, false))){
@@ -126,16 +129,19 @@ object SystemSettingsService {
notification: Boolean,
activityLogLimit: Option[Int],
ssh: Boolean,
sshHost: Option[String],
sshPort: Option[Int],
useSMTP: Boolean,
smtp: Option[Smtp],
ldapAuthentication: Boolean,
ldap: Option[Ldap]){
def baseUrl(request: HttpServletRequest): String = baseUrl.getOrElse {
defining(request.getRequestURL.toString){ url =>
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
def sshAddress:Option[SshAddress] =
for {
host <- sshHost if ssh
}
}.stripSuffix("/")
yield SshAddress(host, sshPort.getOrElse(DefaultSshPort))
}
case class Ldap(
@@ -161,6 +167,10 @@ object SystemSettingsService {
fromAddress: Option[String],
fromName: Option[String])
case class SshAddress(
host:String,
port:Int)
val DefaultSshPort = 29418
val DefaultSmtpPort = 25
val DefaultLdapPort = 389
@@ -174,6 +184,7 @@ object SystemSettingsService {
private val Notification = "notification"
private val ActivityLogLimit = "activity_log_limit"
private val Ssh = "ssh"
private val SshHost = "ssh.host"
private val SshPort = "ssh.port"
private val UseSMTP = "useSMTP"
private val SmtpHost = "smtp.host"
@@ -216,7 +227,4 @@ object SystemSettingsService {
else value
}
// // TODO temporary flag
// val enablePluginSystem = Option(System.getProperty("enable.plugin")).getOrElse("false").toBoolean
}

View File

@@ -1,8 +1,13 @@
package gitbucket.core.service
import java.io.ByteArrayInputStream
import fr.brouillard.oss.security.xhub.XHub
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
import gitbucket.core.api._
import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent, CommitComment}
import gitbucket.core.model.Profile._
import org.apache.http.client.utils.URLEncodedUtils
import profile.simple._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.RepositoryName
@@ -33,8 +38,11 @@ trait WebHookService {
/** get All WebHook informations of repository event */
def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] =
WebHookEvents.filter(t => t.byRepository(owner, repository) && t.event === event.bind)
.list.map(t => WebHook(t.userName, t.repositoryName, t.url))
WebHooks.filter(_.byRepository(owner, repository))
.innerJoin(WebHookEvents).on { (wh, whe) => whe.byWebHook(wh) }
.filter{ case (wh, whe) => whe.event === event.bind}
.map{ case (wh, whe) => wh }
.list.distinct
/** get All WebHook information from repository to url */
def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(WebHook, Set[WebHook.Event])] =
@@ -44,14 +52,15 @@ trait WebHookService {
.map{ case (w,t) => w -> t.event }
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = {
WebHooks insert WebHook(owner, repository, url)
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
WebHooks insert WebHook(owner, repository, url, token)
events.toSet.map{ event: WebHook.Event =>
WebHookEvents insert WebHookEvent(owner, repository, url, event)
}
}
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = {
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => w.token).update(token)
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
events.toSet.map{ event: WebHook.Event =>
WebHookEvents insert WebHookEvent(owner, repository, url, event)
@@ -69,17 +78,17 @@ trait WebHookService {
}
}
def callWebHook(event: WebHook.Event, webHookURLs: 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])] = {
import org.apache.http.impl.client.HttpClientBuilder
import ExecutionContext.Implicits.global
import org.apache.http.protocol.HttpContext
import org.apache.http.client.methods.HttpPost
if(webHookURLs.nonEmpty){
if(webHooks.nonEmpty){
val json = JsonFormat(payload)
webHookURLs.map { webHookUrl =>
webHooks.map { webHook =>
val reqPromise = Promise[HttpRequest]
val f = Future {
val itcp = new org.apache.http.HttpRequestInterceptor{
@@ -89,19 +98,26 @@ trait WebHookService {
}
try{
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
logger.debug(s"start web hook invocation for ${webHookUrl.url}")
val httpPost = new HttpPost(webHookUrl.url)
logger.debug(s"start web hook invocation for ${webHook.url}")
val httpPost = new HttpPost(webHook.url)
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
httpPost.addHeader("X-Github-Event", event.name)
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
params.add(new BasicNameValuePair("payload", json))
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"))
def postContent = new UrlEncodedFormEntity(params, "UTF-8")
httpPost.setEntity(postContent)
if (!webHook.token.isEmpty) {
// TODO find a better way and see how to extract content from postContent
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, contentAsBytes))
}
val res = httpClient.execute(httpPost)
httpPost.releaseConnection()
logger.debug(s"end web hook invocation for ${webHookUrl}")
logger.debug(s"end web hook invocation for ${webHook}")
res
}catch{
case e:Throwable => {
@@ -113,12 +129,12 @@ trait WebHookService {
}
}
f.onSuccess {
case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}")
case s => logger.debug(s"Success: web hook request to ${webHook.url}")
}
f.onFailure {
case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t)
case t => logger.error(s"Failed: web hook request to ${webHook.url}", t)
}
(webHookUrl, json, reqPromise.future, f)
(webHook, json, reqPromise.future, f)
}
} else {
Nil
@@ -161,7 +177,7 @@ trait WebHookPullRequestService extends WebHookService {
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
WebHookPullRequestPayload(
action = action,
@@ -200,7 +216,7 @@ trait WebHookPullRequestService extends WebHookService {
import WebHookService._
for{
((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch)
baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName, baseUrl)
baseRepo <- getRepository(pullRequest.userName, pullRequest.repositoryName)
} yield {
val payload = WebHookPullRequestPayload(
action = action,
@@ -229,7 +245,7 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName)
} yield {
WebHookPullRequestReviewCommentPayload(
action = action,

View File

@@ -1,7 +1,9 @@
package gitbucket.core.service
import java.util.Date
import gitbucket.core.controller.Context
import gitbucket.core.model.Account
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util._
import gitbucket.core.util.ControlUtil._
import org.eclipse.jgit.api.Git
@@ -13,7 +15,6 @@ import java.io.ByteArrayInputStream
import org.eclipse.jgit.patch._
import org.eclipse.jgit.api.errors.PatchFormatException
import scala.collection.JavaConverters._
import RepositoryService.RepositoryInfo
object WikiService {
@@ -38,10 +39,13 @@ object WikiService {
*/
case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git")
def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings, userName: String) =
repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), userName).replaceFirst("\\.git\\Z", ".wiki.git")
def wikiHttpUrl(repositoryInfo: RepositoryInfo)(implicit context: Context): String
= RepositoryService.httpUrl(repositoryInfo.owner, repositoryInfo.name + ".wiki")
def wikiSshUrl(repositoryInfo: RepositoryInfo)(implicit context: Context): Option[String]
= RepositoryService.sshUrl(repositoryInfo.owner, repositoryInfo.name + ".wiki")
}
trait WikiService {

View File

@@ -74,7 +74,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
request.paths match {
case Array(_, repositoryOwner, repositoryName, _*) =>
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match {
case Some(repository) => {
if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
chain.doFilter(request, response)

View File

@@ -160,7 +160,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) +
countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository)
val repositoryInfo = getRepository(owner, repository, baseUrl).get
val repositoryInfo = getRepository(owner, repository).get
// Extract new commit and apply issue comment
val defaultBranch = repositoryInfo.repository.defaultBranch

View File

@@ -87,11 +87,11 @@ abstract class DefaultGitCommand(val owner: String, val repoName: String) extend
}
class DefaultGitUploadPack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName)
class DefaultGitUploadPack(owner: String, repoName: String) extends DefaultGitCommand(owner, repoName)
with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo =>
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo =>
if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
val repository = git.getRepository
@@ -107,7 +107,7 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) ex
with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo =>
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).foreach { repositoryInfo =>
if(isWritableUser(user, repositoryInfo)){
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
val repository = git.getRepository
@@ -124,7 +124,7 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) ex
}
}
class PluginGitUploadPack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand
class PluginGitUploadPack(repoName: String, routing: GitRepositoryRouting) extends GitCommand
with SystemSettingsService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
@@ -139,7 +139,7 @@ class PluginGitUploadPack(repoName: String, baseUrl: String, routing: GitReposit
}
}
class PluginGitReceivePack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand
class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting) extends GitCommand
with SystemSettingsService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
@@ -163,9 +163,9 @@ class GitCommandFactory(baseUrl: String) extends CommandFactory {
logger.debug(s"command: $command")
command match {
case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, baseUrl, routing(repoName))
case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, baseUrl, routing(repoName))
case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName, baseUrl)
case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, routing(repoName))
case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, routing(repoName))
case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName)
case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl)
case _ => new UnknownCommand(command)
}

View File

@@ -1,12 +1,13 @@
package gitbucket.core.ssh
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.service.SystemSettingsService.SshAddress
import org.apache.sshd.common.Factory
import org.apache.sshd.server.{Environment, ExitCallback, Command}
import java.io.{OutputStream, InputStream}
import org.eclipse.jgit.lib.Constants
class NoShell extends Factory[Command] with SystemSettingsService {
class NoShell(sshAddress:SshAddress) extends Factory[Command] {
override def create(): Command = new Command() {
private var in: InputStream = null
private var out: OutputStream = null
@@ -15,7 +16,6 @@ class NoShell extends Factory[Command] with SystemSettingsService {
override def start(env: Environment): Unit = {
val user = env.getEnv.get("USER")
val port = loadSystemSettings().sshPort.getOrElse(SystemSettingsService.DefaultSshPort)
val message =
"""
| Welcome to
@@ -31,8 +31,8 @@ class NoShell extends Factory[Command] with SystemSettingsService {
|
| Please use:
|
| git clone ssh://%s@GITBUCKET_HOST:%d/OWNER/REPOSITORY_NAME.git
""".stripMargin.format(user, port).replace("\n", "\r\n") + "\r\n"
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
""".stripMargin.format(user, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
err.write(Constants.encode(message))
err.flush()
in.close()

View File

@@ -5,7 +5,8 @@ import java.util.concurrent.atomic.AtomicBoolean
import javax.servlet.{ServletContextEvent, ServletContextListener}
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.util.Directory
import gitbucket.core.service.SystemSettingsService.SshAddress
import gitbucket.core.util.{Directory}
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
import org.slf4j.LoggerFactory
@@ -14,20 +15,20 @@ object SshServer {
private val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
private val active = new AtomicBoolean(false)
private def configure(port: Int, baseUrl: String) = {
server.setPort(port)
private def configure(sshAddress: SshAddress, baseUrl: String) = {
server.setPort(sshAddress.port)
val provider = new SimpleGeneratorHostKeyProvider(new File(s"${Directory.GitBucketHome}/gitbucket.ser"))
provider.setAlgorithm("RSA")
provider.setOverwriteAllowed(false)
server.setKeyPairProvider(provider)
server.setPublickeyAuthenticator(new PublicKeyAuthenticator)
server.setCommandFactory(new GitCommandFactory(baseUrl))
server.setShellFactory(new NoShell)
server.setShellFactory(new NoShell(sshAddress))
}
def start(port: Int, baseUrl: String) = {
def start(sshAddress: SshAddress, baseUrl: String) = {
if(active.compareAndSet(false, true)){
configure(port, baseUrl)
configure(sshAddress, baseUrl)
server.start()
logger.info(s"Start SSH Server Listen on ${server.getPort}")
}
@@ -55,20 +56,18 @@ class SshServerListener extends ServletContextListener with SystemSettingsServic
override def contextInitialized(sce: ServletContextEvent): Unit = {
val settings = loadSystemSettings()
if(settings.ssh){
settings.baseUrl match {
case None =>
logger.error("Could not start SshServer because the baseUrl is not configured.")
case Some(baseUrl) =>
SshServer.start(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), baseUrl)
}
if (settings.sshAddress.isDefined && settings.baseUrl.isEmpty) {
logger.error("Could not start SshServer because the baseUrl is not configured.")
}
for {
sshAddress <- settings.sshAddress
baseUrl <- settings.baseUrl
}
SshServer.start(sshAddress, baseUrl)
}
override def contextDestroyed(sce: ServletContextEvent): Unit = {
if(loadSystemSettings().ssh){
SshServer.stop()
}
SshServer.stop()
}
}

View File

@@ -36,7 +36,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
private def authenticate(action: (RepositoryInfo) => Any) = {
{
defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository =>
getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(repository.owner == x.userName) => action(repository)
@@ -95,7 +95,7 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =
private def authenticate(action: (RepositoryInfo) => Any) = {
{
defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository =>
getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(paths(0) == x.userName) => action(repository)
@@ -118,7 +118,7 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
private def authenticate(action: (RepositoryInfo) => Any) = {
{
defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository =>
getRepository(paths(0), paths(1)).map { repository =>
if(!repository.repository.isPrivate){
action(repository)
} else {
@@ -145,7 +145,7 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =
private def authenticate(action: (RepositoryInfo) => Any) = {
{
defining(request.paths){ paths =>
getRepository(paths(0), paths(1), baseUrl).map { repository =>
getRepository(paths(0), paths(1)).map { repository =>
context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(!repository.repository.isPrivate) => action(repository)

View File

@@ -75,6 +75,11 @@ object Implicits {
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^/git/", "/")
def baseUrl:String = {
val url = request.getRequestURL.toString
val len = url.length - (request.getRequestURI.length - request.getContextPath.length)
url.substring(0, len).stripSuffix("/")
}
}
implicit class RichSession(session: HttpSession){

View File

@@ -32,14 +32,13 @@ object JGitUtil {
*
* @param owner the user name of the repository owner
* @param name the repository name
* @param url the repository URL
* @param commitCount the commit count. If the repository has over 1000 commits then this property is 1001.
* @param branchList the list of branch names
* @param tags the list of tags
*/
case class RepositoryInfo(owner: String, name: String, url: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){
def this(owner: String, name: String, baseUrl: String) = {
this(owner, name, s"${baseUrl}/git/${owner}/${name}.git", 0, Nil, Nil)
case class RepositoryInfo(owner: String, name: String, commitCount: Int, branchList: List[String], tags: List[TagInfo]){
def this(owner: String, name: String) = {
this(owner, name, 0, Nil, Nil)
}
}
@@ -174,14 +173,14 @@ object JGitUtil {
/**
* Returns the repository information. It contains branch names and tag names.
*/
def getRepositoryInfo(owner: String, repository: String, baseUrl: String): RepositoryInfo = {
def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = {
using(Git.open(getRepositoryDir(owner, repository))){ git =>
try {
// get commit count
val commitCount = git.log.all.call.iterator.asScala.map(_ => 1).take(10001).sum
RepositoryInfo(
owner, repository, s"${baseUrl}/git/${owner}/${repository}.git",
owner, repository,
// commit count
commitCount,
// branches
@@ -197,7 +196,7 @@ object JGitUtil {
} catch {
// not initialized
case e: NoHeadException => RepositoryInfo(
owner, repository, s"${baseUrl}/git/${owner}/${repository}.git", 0, Nil, Nil)
owner, repository, 0, Nil, Nil)
}
}

View File

@@ -9,7 +9,7 @@ import gitbucket.core.plugin.{RenderRequest, PluginRegistry}
import gitbucket.core.service.{RepositoryService, RequestCache}
import gitbucket.core.util.{FileUtil, JGitUtil, StringUtil}
import play.twirl.api.Html
import play.twirl.api.{Html, HtmlFormat}
/**
* Provides helper methods for Twirl templates.
@@ -225,6 +225,13 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
def avatarLink(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false)(implicit context: Context): Html =
userWithContent(userName, mailAddress)(avatar(userName, size, tooltip, mailAddress))
/**
* Generates the avatar link to the account page.
* If user does not exist or disabled, this method returns avatar image without link.
*/
def avatarLink(commit: JGitUtil.CommitInfo, size: Int)(implicit context: Context): Html =
userWithContent(commit.authorName, commit.authorEmailAddress)(avatar(commit, size))
private def userWithContent(userName: String, mailAddress: String = "", styleClass: String = "")(content: Html)(implicit context: Context): Html =
(if(mailAddress.isEmpty){
getAccountByUserName(userName)
@@ -306,6 +313,19 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
private[this] val detectAndRenderLinksRegex = """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,13}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r
def detectAndRenderLinks(text: String): Html = {
Html(detectAndRenderLinksRegex.replaceAllIn(text, m => s"""<a href="${m.group(0)}">${m.group(0)}</a>"""))
val matches = detectAndRenderLinksRegex.findAllMatchIn(text).toSeq
val (x, pos) = matches.foldLeft((collection.immutable.Seq.empty[Html], 0)){ case ((x, pos), m) =>
val url = m.group(0)
val href = url.replace("\"", "&quot;")
(x ++ (Seq(
if(pos < m.start) Some(HtmlFormat.escape(text.substring(pos, m.start))) else None,
Some(Html(s"""<a href="${href}">${url}</a>"""))
).flatten), m.end)
}
// append rest fragment
val out = if (pos < text.length) x :+ HtmlFormat.escape(text.substring(pos)) else x
HtmlFormat.fill(out)
}
}

View File

@@ -4,7 +4,7 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main("Applications"){
<div class="container">
<div class="container body">
<div class="row">
<div class="col-md-3">
@menu("application", settings.ssh)
@@ -16,24 +16,27 @@
@if(personalTokens.isEmpty && gneratedToken.isEmpty){
No tokens.
} else {
Tokens you have generated that can be used to access the GitBucket API.<hr>
Tokens you have generated that can be used to access the GitBucket API.
<hr style="margin-top: 10px;">
}
@gneratedToken.map{ case (token, tokenString) =>
<div class="alert alert-info">
Make sure to copy your new personal access token now. You won't be able to see it again!
</div>
@helper.html.copy("generated-token-copy", tokenString){
<input type="text" value="@tokenString" style="width:21em" readonly>
}
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-mini btn-danger pull-right">Delete</a>
<hr>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
<div style="width: 50%;">
@helper.html.copy("generated-token-copy", tokenString){
<input type="text" value="@tokenString" class="form-control input-sm" readonly>
}
</div>
<hr style="margin-top: 10px;">
}
@personalTokens.zipWithIndex.map { case (token, i) =>
@if(i != 0){
<hr>
<hr style="margin-top: 10px;">
}
<strong>@token.note</strong>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-mini btn-danger pull-right">Delete</a>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
}
</div>
</div>
@@ -44,7 +47,7 @@
<fieldset>
<label for="note" class="strong">Token description</label>
<div><span id="error-note" class="error"></span></div>
<input type="text" name="note" id="note" class="form-control" style="width: 400px;"/>
<input type="text" name="note" id="note" class="form-control"/>
<p class="muted">What's this token for?</p>
</fieldset>
<input type="submit" class="btn btn-success" value="Generate token"/>

View File

@@ -3,7 +3,7 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main("Edit your profile"){
<div class="container">
<div class="container body">
<div class="row">
<div class="col-md-3">
@menu("profile", settings.ssh)

View File

@@ -2,7 +2,7 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main(if(account.isEmpty) "Create group" else "Edit group"){
<div class="container">
<div class="container body">
<form id="form" method="post" action="@if(account.isEmpty){@path/groups/new} else {@path/@account.get.userName/_editgroup}" validate="true">
<div class="row">
<div class="col-md-5">

View File

@@ -3,7 +3,7 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main(account.userName){
<div class="container">
<div class="container body">
<div class="container-fluid">
<div class="row">
<div class="col-md-4">
@@ -22,7 +22,7 @@
<div>
<div>Groups</div>
@groupNames.map { groupName =>
<a href="@url(groupName)">@avatar(groupName, 36, tooltip = true)</a>
@avatarLink(groupName, 36, tooltip = true)
}
</div>
}

View File

@@ -3,7 +3,7 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
@import context._
@import gitbucket.core.view.helpers._
@html.main("Create a New Repository"){
<div style="width: 600px; margin: 10px auto;">
<div class="body" style="width: 600px; margin: 10px auto;">
<h2>Create a new repository</h2>
<p class="muted">
A repository contains all the files for your project, including the revision history.

View File

@@ -2,7 +2,7 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main("Create your account"){
<div class="container">
<div class="container body">
<h3>Create your account</h3>
<form action="@path/register" method="POST" validate="true">
<div class="row">

View File

@@ -3,7 +3,7 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main("SSH Keys"){
<div class="container">
<div class="container body">
<div class="row">
<div class="col-md-3">
@menu("ssh", settings.ssh)
@@ -17,10 +17,10 @@
}
@sshKeys.zipWithIndex.map { case (key, i) =>
@if(i != 0){
<hr>
<hr style="margin-top: 10px;">
}
<strong>@key.title</strong> (@SshUtil.fingerPrint(key.publicKey).getOrElse("Key is invalid."))
<a href="@path/@account.userName/_ssh/delete/@key.sshKeyId" class="btn btn-mini btn-danger pull-right">Delete</a>
<a href="@path/@account.userName/_ssh/delete/@key.sshKeyId" class="btn btn-sm btn-danger pull-right">Delete</a>
}
</div>
</div>
@@ -31,12 +31,12 @@
<fieldset class="form-group">
<label for="title" class="strong">Title</label>
<div><span id="error-title" class="error"></span></div>
<input type="text" name="title" id="title" class="form-control" style="width: 400px;"/>
<input type="text" name="title" id="title" class="form-control"/>
</fieldset>
<fieldset class="form-group">
<label for="publicKey" class="strong">Key</label>
<div><span id="error-publicKey" class="error"></span></div>
<textarea name="publicKey" id="publicKey" class="form-control" style="width: 600px; height: 250px;"></textarea>
<textarea name="publicKey" id="publicKey" class="form-control" style="height: 250px;"></textarea>
</fieldset>
<input type="submit" class="btn btn-success" value="Add"/>
</div>

View File

@@ -11,7 +11,7 @@
<a href="@path/admin/system">System Settings</a>
</li>
<li@if(active=="plugins"){ class="active"}>
<a href="@path/admin/plugins">Plugins</a>
<a href="@path/admin/plugins">Plugins</a>
</li>
<li>
<a href="@path/console/login.jsp">H2 Console</a>

View File

@@ -111,15 +111,24 @@
Enable SSH access to git repository
</label>
</fieldset>
<div class="form-group ssh">
<label class="control-label col-md-3" for="sshPort">SSH Port</label>
<div class="col-md-9">
<input type="text" id="sshPort" name="sshPort" class="form-control" value="@settings.sshPort"/>
<span id="error-sshPort" class="error"></span>
<div class="ssh">
<div class="form-group">
<label class="control-label col-md-3" for="sshHost">SSH Host</label>
<div class="col-md-9">
<input type="text" id="sshHost" name="sshHost" class="form-control" value="@settings.sshHost"/>
<span id="error-sshHost" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-3" for="sshPort">SSH Port</label>
<div class="col-md-9">
<input type="text" id="sshPort" name="sshPort" class="form-control" value="@settings.sshPort"/>
<span id="error-sshPort" class="error"></span>
</div>
</div>
</div>
<p class="muted">
Base URL is required if SSH access is enabled.
Both of SSH host and Base URL are required if SSH access is enabled.
</p>
<!--====================================================================-->
<!-- Authentication -->

View File

@@ -8,9 +8,11 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main("Issues"){
@dashboard.html.tab("issues")
<div class="container">
@issuesnavi(filter, "issues", condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
<div class="body">
@dashboard.html.tab("issues")
<div class="container">
@issuesnavi(filter, "issues", condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div>
</div>
}

View File

@@ -8,9 +8,11 @@
@import context._
@import gitbucket.core.view.helpers._
@html.main("Pull Requests"){
@dashboard.html.tab("pulls")
<div class="container">
@issuesnavi(filter, "pulls", condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
<div class="body">
@dashboard.html.tab("pulls")
<div class="container">
@issuesnavi(filter, "pulls", condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div>
</div>
}

View File

@@ -27,12 +27,6 @@ $(function(){
throw e;
}
}
// Adjust clickable area width
$('#@textareaId').next('div.clickable').css({
'width': ($('#@textareaId').width() + 18) + 'px',
'font-size': '13px'
});
});
</script>
}

View File

@@ -10,8 +10,8 @@
@if(comment.fileName.isDefined){filename="@comment.fileName.get"}
@if(comment.newLine.isDefined){newline="@comment.newLine.get"}
@if(comment.oldLine.isDefined){oldline="@comment.oldLine.get"}>
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="panel- panel-default commit-comment-box commit-comment-@comment.commentId">
<div class="issue-avatar-image">@avatarLink(comment.commentedUserName, 48)</div>
<div class="panel panel-default commit-comment-box commit-comment-@comment.commentId">
<div class="panel-heading">
@user(comment.commentedUserName, styleClass="username strong")
<span class="muted">

View File

@@ -1,8 +1,16 @@
@(id: String, value: String, prepend: Boolean = false)(html: Html)
<div class="input-group @if(prepend){input-prepend}" style="margin-bottom: 0px;">
@html
<span class="input-group-btn"><span id="@id" class="btn btn-sm btn-default" data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span></span>
</div>
@(id: String, value: String, style: String = "")(html: Html = Html(""))
@if(html.body.nonEmpty){
<div class="input-group" style="margin-bottom: 0px;">
@html
<span class="input-group-btn">
<span id="@id" class="btn btn-sm 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>
</span>
</div>
} else {
<span id="@id" class="btn btn-sm 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>
}
<script>
// copy to clipboard
(function() {

View File

@@ -4,79 +4,81 @@
@import context._
@import gitbucket.core.view.helpers._
@main("GitBucket"){
@dashboard.html.tab()
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="pull-right">
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
</div>
@helper.html.activities(activities)
</div>
<div class="col-md-4">
@settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
<div class="body">
@dashboard.html.tab()
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="pull-right">
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
</div>
}
@if(loginAccount.isEmpty){
@signinform(settings)
} else {
<div class="panel panel-default">
<div class="panel-heading strong">
<div class="pull-right">
<a href="@path/new" class="btn btn-success btn-sm">New repository</a>
</div>
Your repositories <span class="badge">@userRepositories.size</span>
@helper.html.activities(activities)
</div>
<div class="col-md-4">
@settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
<ul class="list-group list-group-flush">
@if(userRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@userRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
@if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
} else {
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
}
</li>
}
@if(userRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
</li>
}
@if(loginAccount.isEmpty){
<div id="dashboard-signin-form">@signinform(settings)</div>
} else {
<div class="panel panel-default">
<div class="panel-heading strong">
<div class="pull-right">
<a href="@path/new" class="btn btn-success btn-sm">New repository</a>
</div>
Your repositories <span class="badge">@userRepositories.size</span>
</div>
<ul class="list-group list-group-flush">
@if(userRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@userRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
@if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
} else {
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
}
</li>
}
@if(userRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
</li>
}
}
}
}
</ul>
</div>
}
<div class="panel panel-default">
<div class="panel-heading strong">Recent updated repositories</div>
<ul class="list-group list-group-flush">
@if(recentRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@recentRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</li>
}
@if(recentRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
</li>
</ul>
</div>
}
<div class="panel panel-default">
<div class="panel-heading strong">Recent updated repositories</div>
<ul class="list-group list-group-flush">
@if(recentRepositories.isEmpty){
<li class="list-group-item">No repositories</li>
} else {
@defining(20){ max =>
@recentRepositories.zipWithIndex.map { case (repository, i) =>
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
@helper.html.repositoryicon(repository, false)
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
</li>
}
@if(recentRepositories.size > max){
<li class="list-group-item show-more">
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
</li>
}
}
}
}
</ul>
</ul>
</div>
</div>
</div>
</div>

View File

@@ -7,7 +7,7 @@
@if(loginAccount.isDefined){
<hr/><br/>
<form method="POST" validate="true">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
<div class="panel panel-default issue-comment-box">
<div class="panel-body">
@helper.html.preview(

View File

@@ -7,7 +7,7 @@
@import gitbucket.core.view.helpers._
@import gitbucket.core.model.CommitComment
@if(issue.isDefined){
<div class="issue-avatar-image">@avatar(issue.get.openedUserName, 48)</div>
<div class="issue-avatar-image">@avatarLink(issue.get.openedUserName, 48)</div>
<div class="panel panel-default issue-comment-box">
<div class="panel-heading">
@user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.get.registeredDate)</span>
@@ -36,7 +36,7 @@
case comment: gitbucket.core.model.IssueComment => {
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"
&& comment.action != "commit" && comment.action != "refer"){
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
<div class="issue-avatar-image">@avatarLink(comment.commentedUserName, 48)</div>
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
<div class="panel-heading">
@user(comment.commentedUserName, styleClass="username strong")
@@ -138,7 +138,7 @@
<span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span>
@avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong")
close @issueOrPullRequest()
closed this @issueOrPullRequest()
@helper.html.datetimeago(comment.registeredDate)
</div>
</div>
@@ -210,7 +210,7 @@ $(function(){
$(document).on('click', '.commit-comment-box i.octicon-pencil', function(){
var id = $(this).closest('a').data('comment-id');
var url = '@url(repository)/commit_comments/_data/' + id;
var $content = $('.commit-commentContent-' + id, $(this).closest('.box'));
var $content = $('.commit-commentContent-' + id, $(this).closest('.commit-comment-box'));
$.get(url,
{

View File

@@ -10,11 +10,11 @@
<form action="@url(repository)/issues/new" method="POST" validate="true" class="form-group">
<div class="row">
<div class="col-md-10">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
<div class="panel panel-default issue-box">
<div class="panel-body">
<span id="error-title" class="error"></span>
<input type="text" name="title" class="form-control input-lg" value="" placeholder="Title" style="width: 680px;" autofocus/>
<input type="text" id="issue-title" name="title" class="form-control input-lg" value="" placeholder="Title" autofocus/>
@helper.html.preview(
repository = repository,
content = "",
@@ -23,7 +23,7 @@
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = hasWritePermission,
style = "width: 680px; height: 200px; max-height: 250px;",
style = "height: 200px; max-height: 250px;",
elastic = true
)
<div class="align-right">

View File

@@ -49,7 +49,8 @@
<div class="small" style="padding-left: 20px;">
@milestone.dueDate.map { dueDate =>
@if(isPast(dueDate)){
<i class="octicon octicon-alert" style="color:#BD2C00;"></i><span class="milestone-alert">Due by @date(dueDate)</span>
<i class="octicon octicon-alert" style="color:#BD2C00;"></i>
<span class="milestone-alert">Due by @date(dueDate)</span>
} else {
<span class="muted">Due by @date(dueDate)</span>
}

View File

@@ -11,7 +11,7 @@
<span class="input-group-addon"><i style="background-color: #@label.map(_.color).getOrElse("888888");"></i></span>
</div>
<script>
$('div#label-color-@labelId').colorpicker();
$('div#label-color-@labelId').colorpicker({format: "hex"});
</script>
<span class="pull-right">
<span id="label-error-@labelId" class="error"></span>

View File

@@ -41,7 +41,8 @@
} else {
@milestone.dueDate.map { dueDate =>
@if(isPast(dueDate)){
<i class="octicon octicon-alert" style="color:#BD2C00;"></i><span class="muted milestone-alert">Due by @date(dueDate)</span>
<i class="octicon octicon-alert" style="color:#BD2C00;"></i>
<span class="muted milestone-alert">Due by @date(dueDate)</span>
} else {
<span class="muted">Due by @date(dueDate)</span>
}

View File

@@ -1,30 +1,28 @@
@(active: String,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
id: Option[String] = None,
expand: Boolean = false,
isRepoTop: Boolean = false,
isNoGroup: Boolean = true,
info: Option[Any] = None,
error: Option[Any] = None)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.service.SystemSettingsService
@import context._
@import gitbucket.core.view.helpers._
@sidemenu(path: String, name: String, icon: String, label: String, count: Int = 0) = {
<li @if(active == name){class="active"} @if(!expand){data-toggle="tooltip" data-placement="left" data-original-title="@label"}>
@menuitem(path: String, name: String, icon: String, label: String, count: Int = 0) = {
<li @if(active == name){class="active"}>
<a href="@url(repository)@path">
<i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i>
@if(expand){ @label}
@if(expand && count > 0){
<div class="pull-right"><span class="badge">@count</span></div>
<i class="menu-icon @if(active == name){menu-icon-active} octicon octicon-@{icon} "></i> <span class="pc">@label</span>
@if(count > 0){
<span class="badge">@count</span>
}
</a>
</li>
}
<div class="container">
@helper.html.information(info)
@helper.html.error(error)
<div class="row">
<div class="headbar">
<div class="container">
@helper.html.information(info)
@helper.html.error(error)
<div class="head">
@if(repository.commitCount > 0){
<div class="input-group pull-right">
@@ -60,83 +58,52 @@
}
}
</div>
<ul class="headmenu">
@menuitem("" , "code" , "code" , "Code")
@menuitem("/issues", "issues" , "issue-opened" , "Issues", repository.issueCount)
@menuitem("/pulls" , "pulls" , "git-pull-request" , "Pull Requests", repository.pullCount)
@menuitem("/wiki" , "wiki" , "book" , "Wiki")
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
@menuitem("/settings" , "settings" , "tools", "Settings")
}
</ul>
</div>
</div>
<hr style="margin-bottom: 20px;"/>
<div class="container body">
<div style="width: @if(expand){170px} else {40px};" class="pull-right">
<ul class="sidemenu">
<li style="height: 12px"><div class="gradient pull-left" style="height: 12px"></div></li>
@sidemenu("" , "code" , "code" , "Code")
@sidemenu("/issues", "issues" , "issue-opened" , "Issues", repository.issueCount)
@sidemenu("/pulls" , "pulls" , "git-pull-request" , "Pull Requests", repository.pullCount)
@sidemenu("/wiki" , "wiki" , "book" , "Wiki")
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
@sidemenu("/settings" , "settings" , "tools", "Settings")
}
<li style="height: 12px"><div class="gradient pull-left" style="height: 12px"></div></li>
</ul>
@if(expand){
<div class="small">
<strong id="repository-url-proto">HTTP</strong> <span class="mute">clone URL</span>
</div>
@helper.html.copy("repository-url-copy", repository.httpUrl){
<input type="text" value="@repository.httpUrl" id="repository-url" class="form-control input-sm" readonly>
}
@if(settings.ssh && loginAccount.isDefined){
<div class="small">
<span class="mute">You can clone <a href="javascript:void(0);" id="repository-url-http">HTTP</a> or <a href="javascript:void(0);" id="repository-url-ssh">SSH</a>.</span>
</div>
}
@id.map { id =>
@if(context.platform != "linux" && context.platform != null){
<div style="margin-top: 10px;">
<a href="@repository.httpOpenRepoUrl(context.platform)" id="repository-clone-url" class="btn btn-sm btn-default btn-block"><i class="octicon octicon-desktop-download"></i>&nbsp;&nbsp;Clone in Desktop</a>
</div>
}
<div style="margin-top: 10px;">
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-sm btn-default btn-block"><i class="octicon octicon-cloud-download"></i>Download ZIP</a>
</div>
@*
<div style="margin-top: 10px;">
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.tar.gz" class="btn btn-sm btn-default btn-block "><i class="octicon octicon-cloud-download"></i>Download TAR.GZ</a>
</div>
*@
}
@if(isRepoTop){
@repository.repository.description.map { description =>
<p class="description">@detectAndRenderLinks(description)</p>
}
</div>
<div class="pull-left" style="width: @if(expand){770px} else {895px};">
@if(expand){
@repository.repository.description.map { description =>
<p class="description">@detectAndRenderLinks(description)</p>
}
<div style="margin-bottom: 10px; padding: 4px;" class="panel panel-default">
<table class="fill-width">
<tr>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link">
<i class="octicon octicon-history"></i>
<div style="margin-bottom: 10px; padding: 4px;" class="panel panel-default">
<table class="fill-width">
<tr>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/commits/@encodeRefName(id.getOrElse(""))" class="header-link">
<i class="octicon octicon-history"></i>
@if(repository.commitCount > 10000){
<strong>10000+</strong> commits
} else {
<strong>@repository.commitCount</strong> commits
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/branches" class="header-link" class="header-link">
<i class="octicon octicon-git-branch"></i>
<strong>@repository.branchList.length</strong> branches
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/tags" class="header-link" class="header-link">
<i class="octicon octicon-tag"></i>
<strong>@repository.tags.length</strong> releases
</a>
</td>
</tr>
</table>
</div>
}
@body
</div>
}
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/branches" class="header-link" class="header-link">
<i class="octicon octicon-git-branch"></i>
<strong>@repository.branchList.length</strong> branches
</a>
</td>
<td style="width: 33%; text-align: center;">
<a href="@url(repository)/tags" class="header-link" class="header-link">
<i class="octicon octicon-tag"></i>
<strong>@repository.tags.length</strong> releases
</a>
</td>
</tr>
</table>
</div>
}
@body
</div>
<script>
$(function(){
@@ -146,7 +113,7 @@ $(function(){
if(e.target.tagName == "I"){
target = e.target.parentElement;
}
$(target).prev ('div.gradient' ).css('border-left', '1px solid silver');
$(target).prev('div.gradient').css('border-left', '1px solid silver');
});
$('ul.sidemenu a').mouseout(function(e){
@@ -154,7 +121,7 @@ $(function(){
if(e.target.tagName == "I"){
target = e.target.parentElement;
}
$(target).prev ('div.gradient' ).css('border-left', '1px solid #eee');
$(target).prev('div.gradient').css('border-left', '1px solid #eee');
});
$('a[rel*=facebox]').facebox({
@@ -176,21 +143,5 @@ $(function(){
$('#fork-form').submit();
});
}
@if(settings.ssh && loginAccount.isDefined){
$('#repository-url-http').click(function(){
$('#repository-url-proto').text('HTTP');
$('#repository-url').val('@repository.httpUrl');
$('#repository-clone-url').attr('href', '@repository.httpOpenRepoUrl(context.platform)')
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
$('#repository-url-ssh').click(function(){
$('#repository-url-proto').text('SSH');
$('#repository-url').val('@repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)');
$('#repository-clone-url').attr('href', '@repository.sshOpenRepoUrl(context.platform, settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
}
});
</script>

View File

@@ -17,7 +17,7 @@
<li><a href="@url(repository)/tree/@commit.id" style="line-height: 16px;"><i class="octicon octicon-code link"></i></a></li>
</ul>
<div>
<div class="commit-avatar-image">@avatar(commit, 40)</div>
<div class="commit-avatar-image">@avatarLink(commit, 40)</div>
<div>
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){

View File

@@ -55,11 +55,11 @@
<form method="POST" action="@path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
<div class="row">
<div class="col-md-10">
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
<div class="panel panel-default issue-box">
<div class="panel-body">
<span class="error" id="error-title"></span>
<input type="text" name="title" class="form-control input-lg" style="width: 680px" placeholder="Title"/>
<input type="text" name="title" class="form-control input-lg" placeholder="Title"/>
@helper.html.preview(
repository = repository,
content = "",
@@ -68,7 +68,7 @@
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true,
style = "width: 680px; height: 200px;"
style = "height: 200px;"
)
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
<input type="hidden" name="targetBranch" value="@originId"/>

View File

@@ -83,8 +83,8 @@
</div>
@if(status.hasMergePermission){
<div style="padding:15px;border-top:solid 1px #e5e5e5;background:#fafafa">
<input type="button" class="btn @if(!status.hasProblem){ btn-success }" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/>
You can also merge branches on the <a href="#" class="show-command-line">command line</a>.
<input type="button" class="btn btn-lg @if(!status.hasProblem){btn-success} else {btn-default}" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/>
&nbsp;&nbsp;You can also merge branches on the <a href="#" class="show-command-line">command line</a>.
<div id="command-line" style="display: none;margin-top: 15px;">
<hr />
@if(status.hasConflict){
@@ -100,24 +100,23 @@
you can perform a manual merge on the command line.
</p>
}
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl, true){
<div class="btn-group" data-toggle="buttons-radio">
<button class="btn btn-small active" type="button" id="repository-url-http">HTTP</button>
@helper.html.copy("repository-url-copy", forkedRepository.httpUrl){
<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>
@if(settings.ssh && loginAccount.isDefined){
<button class="btn btn-small" type="button" id="repository-url-ssh" style="border-radius: 0px;">SSH</button>
<label class="btn btn-sm btn-default" id="repository-url-ssh"><input type="radio">SSH</label>
}
</div>
<input type="text" style="width: 500px;" value="@forkedRepository.httpUrl" id="repository-url" readonly />
<input type="text" class="form-control input-sm" value="@forkedRepository.httpUrl" id="repository-url" readonly />
}
<div>
<div style="margin-top: 10px;">
<p>
<span class="strong">Step 1:</span> From your project repository, check out a new branch and test the changes.
</p>
@defining(s"git checkout -b ${pullreq.requestUserName}-${pullreq.requestBranch} ${pullreq.branch}\n" +
s"git pull ${forkedRepository.httpUrl} ${pullreq.requestBranch}"){ command =>
@helper.html.copy("merge-command-copy-1", command){
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;" id="merge-command">@Html(command)</pre>
}
@helper.html.copy("merge-command-copy-1", command, "position: absolute; right: 31px;")()
<pre style="font-size: 12px; border-radius: 3px;" id="merge-command">@Html(command)</pre>
}
</div>
<div>
@@ -126,9 +125,8 @@
</p>
@defining(s"git checkout ${pullreq.branch}\ngit merge --no-ff ${pullreq.requestUserName}-${pullreq.requestBranch}\n" +
s"git push origin ${pullreq.branch}"){ command =>
@helper.html.copy("merge-command-copy-2", command){
<pre style="width: 600px; float: left; font-size: 12px; border-radius: 3px 0px 3px 3px;">@command</pre>
}
@helper.html.copy("merge-command-copy-2", command, "position: absolute; right: 31px;")()
<pre style="font-size: 12px; border-radius: 3px;">@command</pre>
}
</div>
</div>
@@ -141,7 +139,7 @@
Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}
</div>
<span id="error-message" class="error"></span>
<textarea name="message" style="width: 635px; height: 80px;">@issue.title</textarea>
<textarea name="message" style="height: 80px;" class="form-control">@issue.title</textarea>
<div>
<input type="button" class="btn" value="Cancel" id="cancel-merge-pull-request"/>
<input type="submit" class="btn btn-success" value="Confirm merge"/>
@@ -171,27 +169,25 @@ $(function(){
$('#confirm-merge-form').show();
});
@if(settings.ssh && loginAccount.isDefined){
$('#repository-url-http').click(function(){
@forkedRepository.sshUrl.map { sshUrl =>
$('#repository-url-http').click(function(e){
// Update URL box
$('#repository-url').val('@forkedRepository.httpUrl');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
// Update command guidance
$('#merge-command').text($('#merge-command').text().replace(
'@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)',
'@forkedRepository.httpUrl'
'@sshUrl', '@forkedRepository.httpUrl'
));
$('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text());
});
$('#repository-url-ssh').click(function(){
$('#repository-url-ssh').click(function(e){
// Update URL box
$('#repository-url').val('@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)');
$('#repository-url').val('@sshUrl');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
// Update command guidance
$('#merge-command').text($('#merge-command').text().replace(
'@forkedRepository.httpUrl',
'@forkedRepository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)'
'@forkedRepository.httpUrl', '@sshUrl'
));
$('#merge-command-copy-1').attr('data-clipboard-text', $('#merge-command').text());
});

View File

@@ -21,7 +21,9 @@
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
<a class="btn" href="#" id="edit">Edit</a>
}
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
@if(loginAccount.isDefined){
<a class="btn btn-success" href="@url(repository)/compare">New pull request</a>
}
</div>
<div class="edit-title pull-right" style="display: none;">
<a class="btn" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a>
@@ -65,7 +67,7 @@
}
</div>
<ul class="nav nav-tabs fill-width pull-left" id="pullreq-tab">
<li class="active"><a href="#conversation">Conversation <span class="badge">@comments.flatMap @{
<li><a href="#conversation">Conversation <span class="badge">@comments.flatMap @{
case comment: IssueComment => Some(comment)
case _: CommitComment => None
}.size</span></a></li>
@@ -73,7 +75,7 @@
<li><a href="#files">Files Changed <span class="badge">@diffs.size</span></a></li>
</ul>
<div class="tab-content fill-width pull-left">
<div class="tab-pane active" id="conversation">
<div class="tab-pane" id="conversation">
@flash.get("error").map{ error =>
<div class="alert alert-error">@error</div>
}
@@ -94,6 +96,22 @@
}
<script>
$(function(){
// Determine active tab from hash
if(location.hash == '#commits'){
$('li:has(a[href=#commits])').addClass('active');
$('div#commits').addClass('active');
} else if(location.hash == '#files'){
$('li:has(a[href=#files])').addClass('active');
$('div#files').addClass('active');
} else {
$('li:has(a[href=#conversation])').addClass('active');
$('div#conversation').addClass('active');
}
// Set hash when tab is clicked
$('ul.nav-tabs li a').click(function(e){
location.href = $(e.delegateTarget).attr("href");
});
$('#pullreq-tab a').click(function (e) {
e.preventDefault();
$(this).tab('show');

View File

@@ -11,7 +11,7 @@
@html.menu("code", repository){
<div class="head">
<div class="pull-right hide-if-blame"><div class="btn-group">
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="octicon octicon-list-unordered"></i></a>
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t">Find file</a>
</div></div>
<div class="line-age-legend">
<span>Newer</span>

View File

@@ -11,7 +11,7 @@
@if(!fileName.isDefined){<hr/><br/>}
<form method="POST" validate="true" style="max-width: 874px;">
@if(!fileName.isDefined){
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
}
<div class="panel panel-default issue-comment-box">
<div class="panel-body">
@@ -23,7 +23,7 @@
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = hasWritePermission,
style = "width: 635px; height: 100px; max-height: 150px;",
style = "height: 100px; max-height: 150px;",
elastic = true
)
</div>

View File

@@ -46,7 +46,7 @@
<li><a href="@url(repository)/tree/@commit.id" style="line-height: 16px;"><i class="octicon octicon-code link"></i></a></li>
</ul>
<div>
<div class="commit-avatar-image">@avatar(commit, 40)</div>
<div class="commit-avatar-image">@avatarLink(commit, 40)</div>
<div>
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
@if(commit.description.isDefined){

View File

@@ -33,7 +33,7 @@
</td>
</tr>
</table>
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
<div class="box issue-comment-box">
<div class="box-content">
<div>

View File

@@ -2,11 +2,11 @@
@import context._
<span class="error-edit-content-@commentId error"></span>
@helper.html.attached(owner, repository){
<textarea style="width: 635px; height: 100px;" id="edit-content-@commentId">@content</textarea>
<textarea style="height: 100px;" id="edit-content-@commentId" class="form-control">@content</textarea>
}
<div>
<input type="button" class="cancel-comment-@commentId btn btn-small btn-danger" value="Cancel"/>
<input type="button" class="update-comment-@commentId btn btn-small pull-right" value="Update comment"/>
<input type="button" class="update-comment-@commentId btn btn-small btn-default pull-right" value="Update comment"/>
</div>
<script>
$(function(){
@@ -19,7 +19,7 @@ $(function(){
}
$(document).on('click', '.update-comment-@commentId', function(){
$box = $(this).closest('.box');
$box = $(this).closest('.commit-comment-box');
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).attr('disabled', 'disabled');
$.ajax({
url: '@path/@owner/@repository/commit_comments/edit/@commentId',

View File

@@ -47,7 +47,7 @@
</td>
</tr>
</table>
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
<div class="panel panel-default issue-comment-box">
<div class="panel-body">
<div>

View File

@@ -11,6 +11,7 @@
error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.service.RepositoryService._
@html.main(
if(pathList.isEmpty){
if(branch == repository.repository.defaultBranch){
@@ -23,32 +24,68 @@
}, Some(repository)) {
@html.menu("code", repository, Some(branch), pathList.isEmpty, groupNames.isEmpty, info, error){
<div class="head">
<div class="pull-right"><div class="btn-group">
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default" data-toggle="tooltip" data-placement="bottom" data-hotkey="t" title="Quickly jump between files"><i class="octicon octicon-list-unordered"></i></a>
@if(pathList.nonEmpty){
<a href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default" data-toggle="tooltip" data-placement="bottom" title="Browse commits for this branch"><i class="octicon octicon-clock"></i></a>
}
</div></div>
@branchPullRequest.map{ case (pullRequest, issue) =>
<a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-sm btn-pullrequest-branch" title="@issue.title" data-toggle="tooltip">#@pullRequest.issueId</a>
}.getOrElse{
<a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-sm btn-success"><i class="octicon octicon-git-compare" data-toggle="tooltip" title="Compare, review, create a pull request"></i></a>
@if(pathList.isEmpty){
<div class="pull-right pc">
@if(platform != "linux" && platform != null){
<a href="@openRepoUrl(repository.httpUrl)" id="repository-clone-url" class="btn btn-sm btn-default"><i class="octicon octicon-desktop-download"></i></a>
}
<a href="@{url(repository)}/archive/@{encodeRefName(branch)}.zip" class="btn btn-sm btn-default">Download ZIP</a>
</div>
<div class="pull-right pc">
<div style="width: 240px; margin-top: 2px; margin-right: 5px; margin-left: 5px;">
@helper.html.copy("repository-url-copy", repository.httpUrl){
@if(repository.sshUrl.isDefined){
<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">
<span id="repository-url-proto">HTTP</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="javascript:void(0);" id="repository-url-http">
<strong>HTTP (recommended)</strong><br>
Clone with Git using the repository's web address.
</a>
</li>
<li>
<a href="javascript:void(0);" id="repository-url-ssh">
<strong>SSH</strong><br>
Clone with an SSH key and passphrase from your GitBucket settings.
</a>
</li>
</ul>
</div>
}
<input type="text" value="@repository.httpUrl" id="repository-url" class="form-control input-sm" readonly>
}
</div>
</div>
}
@helper.html.branchcontrol(
branch,
repository,
hasWritePermission
){
<div class="pull-right">
<div class="btn-group">
<a href="@url(repository)/new/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default pc" title="Create a new file here" @if(!hasWritePermission){disabled}>New file</i></a>
<a href="@url(repository)/find/@encodeRefName(branch)" class="btn btn-sm btn-default pc" data-hotkey="t">Find file</a>
@if(pathList.nonEmpty){
<a href="@url(repository)/commits/@encodeRefName(branch)/@pathList.mkString("/")" class="btn btn-sm btn-default" data-toggle="tooltip" data-placement="bottom" title="Browse commits for this branch">History</a>
}
</div>
</div>
@helper.html.branchcontrol(branch, repository, hasWritePermission){
@repository.branchList.map { x =>
<li><a href="@url(repository)/tree/@encodeRefName(x)">@helper.html.checkicon(x == branch) @x</a></li>
}
}
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
@pathList.zipWithIndex.map { case (section, i) =>
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
}
@if(hasWritePermission){
<a href="@url(repository)/new/@encodeRefName(branch)/@pathList.mkString("/")" title="Create a new file here" style="text-decoration: none;">+</i></a>
@if(pathList.isEmpty){
@branchPullRequest.map{ case (pullRequest, issue) =>
<a href="@url(repository)/pull/@pullRequest.issueId" class="btn btn-sm btn-pullrequest-branch" title="@issue.title" data-toggle="tooltip">View #@pullRequest.issueId</a>
}.getOrElse {
<a href="@url(repository)/compare?head=@urlEncode(encodeRefName(branch))" class="btn btn-sm btn-success" @if(loginAccount.isEmpty){disabled}>New pull request</a>
}
} else {
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
@pathList.zipWithIndex.map { case (section, i) =>
<a href="@url(repository)/tree/@encodeRefName(branch)/@pathList.take(i + 1).mkString("/")">@section</a> /
}
}
</div>
<table class="table table-file-list">
@@ -141,3 +178,20 @@
}
}
}
<script>
@repository.sshUrl.map { sshUrl =>
$('#repository-url-http').click(function(){
$('#repository-url-proto').text('HTTP');
$('#repository-url').val('@repository.httpUrl');
$('#repository-clone-url').attr('href', '@openRepoUrl(repository.httpUrl)')
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
$('#repository-url-ssh').click(function(){
$('#repository-url-proto').text('SSH');
$('#repository-url').val('@sshUrl');
$('#repository-clone-url').attr('href', '@openRepoUrl(sshUrl)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
}
</script>

View File

@@ -13,7 +13,7 @@
@if(originRepository.isDefined){
@avatar(originRepository.get.owner, 20)
<span@if(repository.owner == originRepository.get.owner){ class="highlight"}>
<a href="@url(originRepository.get)">@originRepository.get.owner</a> / <a href="@path/@originRepository.get.owner/@originRepository.get.name">@originRepository.get.name</a>
<a href="@url(originRepository.get.owner)">@originRepository.get.owner</a> / <a href="@path/@originRepository.get.owner/@originRepository.get.name">@originRepository.get.name</a>
</span>
} else {
@avatar(repository.repository.originUserName.get, 20)

View File

@@ -11,9 +11,9 @@
<h3><strong>Quick setup</strong> — if you've done this kind of thing before</h3>
<div class="empty-repo-options">
via <a href="@repository.httpUrl" class="git-protocol-selector">HTTP</a>
@if(settings.ssh && loginAccount.isDefined){
@repository.sshUrl.map { sshUrl =>
or
<a href="@repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort), loginAccount.get.userName)" class="git-protocol-selector">SSH</a>
<a href="@sshUrl" class="git-protocol-selector">SSH</a>
}
</div>
<h3 style="margin-top: 30px;">Create a new repository on the command line</h3>

View File

@@ -24,7 +24,8 @@
Opened by <a href="@url(issue.openedUserName)" class="username">@issue.openedUserName</a>
@helper.html.datetimeago(issue.registeredDate)
@if(issue.commentCount > 0){
&nbsp;&nbsp;<i class="octicon octicon-comment"></i><span class="strong">@issue.commentCount</span> @plural(issue.commentCount, "comment")
&nbsp;&nbsp;<i class="octicon octicon-comment"></i>
<span class="strong">@issue.commentCount</span> @plural(issue.commentCount, "comment")
}
</div>
</div>

View File

@@ -30,6 +30,11 @@
}
<button class="btn btn-default" id="test">Test Hook</button>
</fieldset>
<fieldset class="form-group">
<label class="strong">Security Token</label>
<div></div>
<input type="text" name="token" id="token" placeholder="leave blank for no X-Hub-Signature usage" value="@webHook.token" class="form-control" style="display: inline; width: 500px; vertical-align: middle;" />
</fieldset>
<hr />
<label class="strong">Which events would you like to trigger this webhook?</label>
<div>
@@ -123,6 +128,7 @@ $(function(){
e.stopImmediatePropagation();
e.preventDefault();
var url = this.form.url.value;
var token = this.form.token.value;
if(!/^https?:\/\/.+/.test(url)){
alert("invalid url");
return;
@@ -132,7 +138,7 @@ $(function(){
$("#test-report").hide();
$.ajax({
method:'POST',
url:'@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url),
url:'@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url) + '&token=' + encodeURIComponent(token),
success: function(e){
//console.log(e);
$('#test-report-tab a:first').tab('show');

View File

@@ -21,7 +21,7 @@
</ul>
<form action="@url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
<span id="error-pageName" class="error"></span>
<input type="text" name="pageName" value="@pageName" class="form-control input-lg" style="width: 900px; font-weight: bold;" placeholder="Input a page name."/>
<input type="text" name="pageName" value="@pageName" class="form-control input-lg" style="font-weight: bold;" placeholder="Input a page name."/>
@helper.html.preview(
repository = repository,
content = page.map(_.content).getOrElse(""),
@@ -30,13 +30,13 @@
enableLineBreaks = false,
enableTaskList = false,
hasWritePermission = false,
style = "width: 900px; height: 400px;",
style = "height: 400px;",
styleClass = "monospace",
placeholder = ""
)
<div class="form-group">
<label for="message">Edit Message</label>
<input type="text" id="message" name="message" value="" class="form-control" style="width: 900px;" placeholder="Write a small message here explaining this change. (Optional)"/>
<input type="text" id="message" name="message" value="" class="form-control" placeholder="Write a small message here explaining this change. (Optional)"/>
</div>
<div class="form-group pull-right">
<input type="hidden" name="currentPageName" value="@pageName"/>

View File

@@ -7,7 +7,7 @@
footer: Option[gitbucket.core.service.WikiService.WikiPageInfo])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.service.WikiService._
@import gitbucket.core.service.WikiService.{wikiHttpUrl, wikiSshUrl}
@html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("wiki", repository){
<ul class="nav nav-tabs fill-width">
@@ -26,7 +26,7 @@
}
</li>
</ul>
<div style="width: 200px; margin-top: 20px;" class="pull-right">
<div style="width: 200px; margin-top: 20px;" class="pull-right pc">
@defining(15){ max =>
<div class="panel panel-default">
<div class="panel-heading strong">
@@ -67,10 +67,10 @@
<div class="small">
<strong>Clone this wiki locally</strong>
</div>
@helper.html.copy("repository-url-copy", httpUrl(repository)){
<input type="text" value="@httpUrl(repository)" id="repository-url" class="form-control input-sm" readonly>
@helper.html.copy("repository-url-copy", wikiHttpUrl(repository)){
<input type="text" value="@wikiHttpUrl(repository)" id="repository-url" class="form-control input-sm" readonly>
}
@if(settings.ssh && loginAccount.isDefined){
@if(wikiSshUrl(repository).isDefined) {
<div class="small">
<span class="mute">You can clone <a href="javascript:void(0);" id="repository-url-http">HTTP</a> or <a href="javascript:void(0);" id="repository-url-ssh">SSH</a>.</span>
</div>
@@ -131,13 +131,13 @@ $(function(){
$('#triangle-right').show();
}
@if(settings.ssh && loginAccount.isDefined){
@wikiSshUrl(repository).map { sshUrl =>
$('#repository-url-http').click(function(){
$('#repository-url').val('@httpUrl(repository)');
$('#repository-url').val('@wikiHttpUrl(repository)');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
$('#repository-url-ssh').click(function(){
$('#repository-url').val('@sshUrl(repository, settings, loginAccount.get.userName)');
$('#repository-url').val('@sshUrl');
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
});
}

View File

@@ -58,7 +58,7 @@ h6 {
margin-right: 5px;
}
.head .octicon,.head .mega-octicon{
.head .octicon, .head .mega-octicon{
color : #BBB;
}
@@ -70,57 +70,6 @@ blockquote p {
font-size: 15px;
}
/*
.nav {
margin-bottom: 12px;
}
.table-bordered {
border-collapse: inherit;
border: none;
}
.table-bordered > thead > tr > th,
.table-bordered > tbody > tr > th,
.table-bordered > tbody > tr > td {
border-bottom: none;
}
.table-bordered > thead > tr:first-child > th:nth-of-type(1),
.table-bordered > tbody > tr:first-child > th:nth-of-type(1),
.table-bordered > tbody > tr:first-child > td:nth-of-type(1) {
border-top-left-radius: 4px;
}
.table-bordered > thead > tr:first-child > th:nth-last-of-type(1),
.table-bordered > tbody > tr:first-child > th:nth-last-of-type(1),
.table-bordered > tbody > tr:first-child > td:nth-last-of-type(1) {
border-top-right-radius: 4px;
}
.table-bordered > tbody > tr:last-child > td:nth-of-type(1) {
border-bottom-left-radius: 4px;
}
.table-bordered > tbody > tr:last-child > td:nth-last-of-type(1) {
border-bottom-right-radius: 4px;
}
.table-bordered > tbody > tr:last-child > td {
border-bottom: 1px solid #dddddd;
}
*/
/*
.table-bordered > thead > tr > th,
.table-bordered > thead > tr > td {
border-bottom-width: 1px;
}
*/
/*
.tab-content {
margin-top: 20px;
}
*/
.danger {
color: #900;
}
@@ -167,43 +116,6 @@ pre.reset {
/* ======================================================================== */
/* Global Header */
/* ======================================================================== */
/*
div.navbar-inner {
border-radius: 0px;
-webkit-border-radius: 0px;
-moz-border-radius: 0px;
border-top: none;
border-left: none;
border-right: none;
border-bottom: 1px solid #d4d4d4;
padding-right: 0px;
}
div.header-menu {
line-height: 40px;
}
div.header-menu .octicon{
color: #333;
}
div.header-menu input,
div.header-menu a.btn {
margin-top: 0px;
margin-bottom: 0px;
}
/*
div.nav-collapse a.menu {
margin-right: 12px;
}
div.nav-collapse a.btn-last,
div.nav-collapse a.menu-last {
margin-right: 30px;
}
*/
.navbar-brand {
height: unset;
padding: 8px;
@@ -211,6 +123,7 @@ div.nav-collapse a.menu-last {
.navbar {
min-height: unset;
margin-bottom: 0px;
}
span.header-version {
@@ -277,6 +190,7 @@ div.pagination {
*/
div.body {
margin-top: 20px;
margin-bottom: 40px;
}
@@ -412,27 +326,13 @@ div.box-content {
padding: 4px;
border-radius: 3px;
}
/*
div > div.box-content-row:nth-of-type(1) {
border: none;
}
div.box-content-row {
border-top: 1px solid #d8d8d8;
padding: 4px;
}
*/
/*
div.repo-link {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
*/
li.repo-link, li.page-link {
padding-top: 4px;
padding-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
div.box-content-bottom {
@@ -444,6 +344,10 @@ div.box-content-bottom {
margin-bottom: 20px;
}
div.box-content-bottom img {
max-width: 100%;
}
table.table th.box-header {
background-color: #f5f5f5;
}
@@ -533,61 +437,56 @@ a.btn-danger:hover .octicon {
}
/****************************************************************************/
/* Side Menu */
/* Head Menu */
/****************************************************************************/
ul.sidemenu {
div.headbar {
background-color: #fafafa;
padding-top: 19px;
border-bottom: 1px solid #eee;
margin-bottom: 20px;
}
ul.headmenu {
margin-top: 20px;
margin-left: 0px;
padding-left: 0px;
margin-bottom: -1px;
}
ul.sidemenu a {
display: block;
padding: 8px 10px;
}
ul.sidemenu a:hover {
ul.headmenu a:hover {
text-decoration: none;
color: black;
}
ul.sidemenu li.active {
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
border-right: 3px solid #bb4444;
border-left: 1px solid white;
ul.headmenu li {
display: inline-block;
list-style-type: none;
font-size: 13px;
}
ul.sidemenu li.active a {
ul.headmenu li a {
color: #666;
padding: 8px 10px;
display: block;
}
ul.headmenu li.active a {
color: black;
}
ul.headmenu li.active {
border-top: 3px solid #bb4444;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
border-bottom: none;
background-color: white;
}
ul.headmenu li.active a {
background-color: #fff;
}
ul.sidemenu {
background-image: -webkit-linear-gradient(left, #f6f6f6 0%, #fff 8px);
background-image: linear-gradient(to right, #f6f6f6 0%, #fff 8px);
box-shadow: inset 1px 0 0 #eee;
}
ul.sidemenu div.margin {
width: 5px;
height: 30px;
margin-right: 4px;
border-left: 1px solid white;
}
ul.sidemenu li {
border-left: 1px solid #eee;
margin-left:0px;
border-right: 2px solid white;
list-style-type: none;
font-size: 90%;
}
ul.sidemenu span.badge {
}
ul.sidemenu a:hover i.menu-icon, ul.sidemenu i.menu-icon-active {
color: #333;
}
/****************************************************************************/
/* Create Repository */
/****************************************************************************/
@@ -636,18 +535,6 @@ a#show-pages-index {
text-decoration: none;
}
/*
ul.nav-stacked.side-menu li span.header {
border-top-right-radius: 3px;
border-top-left-radius: 3px;
border: 1px solid #d8d8d8;
display: block;
padding: 8px 15px 9px;
margin-right: 2px;
background-color: #f5f5f5;
}
*/
ul.nav-stacked.side-menu li a:hover {
background-color: transparent;
}
@@ -750,7 +637,6 @@ div.repository-content {
padding: 0 3px;
}
/****************************************************************************/
/* Activity */
/****************************************************************************/
@@ -978,73 +864,6 @@ span.simplified-path {
color: #0088cc;
}
/****************************************************************************/
/* nav pulls group */
/****************************************************************************/
/*
.nav-pills-group:after {
display: table;
line-height: 0;
content: "";
}
.nav-pills-group:after {
clear: both;
}
.nav-pills-group > li {
float: left;
}
*/
/*
.nav-pills > li + li {
margin-left: 0px;
}
.nav-pills > li > a {
padding-right: 12px;
padding-left: 12px;
line-height: 14px;
color: #666;
font-weight: bold;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.nav-pills > li > a {
padding-top: 10px;
padding-bottom: 10px;
border-left : 1px solid #e5e5e5;
border-top : 1px solid #e5e5e5;
border-bottom : 1px solid #e5e5e5;
}
.nav-pills > li:nth-of-type(1) > a {
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
.nav-pills > li:nth-last-of-type(1) > a {
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
border-right : 1px solid #e5e5e5;
}
*/
/*
.nav-pills-group > .active > a,
.nav-pills-group > .active > a:hover,
.nav-pills-group > .active > a:focus {
color: #ffffff;
background-color: #0088cc;
border-color: #0088cc;
}
*/
/****************************************************************************/
/* Issues */
/****************************************************************************/
@@ -1081,11 +900,11 @@ table.table-issues {
margin-top: 12px;
}
table.table-issues td .octicon-issue-opened,table.table-issues td .octicon-git-pull-request .open {
table.table-issues td .octicon-issue-opened, table.table-issues td .octicon-git-pull-request .open {
color: #6CC644;
}
table.table-issues td .octicon-issue-closed,table.table-issues td .octicon-git-pull-request .closed{
table.table-issues td .octicon-issue-closed, table.table-issues td .octicon-git-pull-request .closed{
color : #BD2C00;;
}
@@ -1099,6 +918,10 @@ a.issue-title {
background-color: #6cc644;
}
.label-important {
background-color: #bd2c00;
}
ul.label-list {
list-style-type: none;
padding-left: 0px;
@@ -1153,47 +976,6 @@ div.milestone-menu a.delete {
color: #b00;
}
/*
div#milestone-progress-area {
display: inline-block;
}
div#milestone-progress-area div.milestone-progress {
width: 130px;
margin-bottom: -6px;
}
div.milestone-progress {
position: relative;
height: 10px;
color: white;
margin-bottom: 4px;
font-weight: bold;
font-size: 12px;
text-shadow: 0px 0px 5px #444;
background-color: silver;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
}
span.milestone-progress {
position: absolute;
height: 100%;
background-color: green;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
}
div.issue-header {
padding-left: 8px;
padding-right: 8px;
padding-top: 12px;
padding-bottom: 12px;
}
*/
div.issue-info {
border-top: 1px solid #e5e5e5;
border-bottom: 1px solid #e5e5e5;
@@ -1203,21 +985,6 @@ div.issue-info {
margin-right: 0px;
}
/*
div.issue-content {
padding: 13px;
background-color: #fff;
}
div.issue-content p:first-child {
margin-top: 0;
}
div.issue-content p:last-child {
margin-bottom: 0;
}
*/
h4#issueTitle {
font-size: large;
font-weight: bold;
@@ -1258,7 +1025,6 @@ div.commit-comment-box > div.panel-body {
div.issue-comment-box textarea {
width: 650px;
height: 100px;
max-height: 300px;
}
@@ -1425,18 +1191,6 @@ a.absent {
color: #c00;
}
/*
div.wiki-index-header {
background-color: #f5f5f5;
color: #333333;
margin: 0;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border: 1px solid #d8d8d8;
padding: 8px 8px 8px 8px;
}
*/
div.wiki-sidebar {
background-color: white;
border: 1px solid #d8d8d8;
@@ -1466,18 +1220,6 @@ div.wiki-footer {
color: gray;
}
/*
div.wiki-index-content {
background-color: white;
border: 1px solid #d8d8d8;
padding: 0px;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
margin-bottom: 20px;
border-top: none;
}
*/
/****************************************************************************/
/* Commit */
/****************************************************************************/
@@ -2216,94 +1958,96 @@ div.container.blame-container{
/* Mobile */
/****************************************************************************/
@media (max-width: 767px) {
body>form#search {
margin: 0 -20px 20px -20px;
}
/* Hide header search box */
input[name=query] {
display: none;
}
#dashboard-signin-form {
display: none;
}
.container {
width: auto !important;
}
.body>div.pull-left {
width: auto !important;
}
.pc {
display: none;
}
body>div.dashboard-nav {
margin: 0 -20px 20px -20px;
padding: 0 10px;
}
/* Adjust issue / comment form */
#issue-title {
width: 100% !important;
}
div.attachable>textarea,
div.attachable>div.clickable {
width: 100% !important;
}
.container {
width: auto !important;
}
/* Adjust issue search box size and position */
#search-filter-box {
width: 90% !important;
position: absolute;
left: 14px;
right: 20px;
margin-top: 42px;
}
form#search-filter-form {
float: none !important;
margin-bottom: 80px !important;
}
.table-issues a.button-link {
width: 42px;
height: 16px;
overflow: hidden;
display: inline-block;
}
.nav-tabs a.btn[href$="/_edit"] {
width: 24px;
white-space: nowrap;
overflow: hidden;
padding: 4px 6px;
margin: 3px 4px 0 0;
}
body>div.container.body {
margin: 0 -12px 40px -12px;
}
.container.body>div[style="width: 170px;"]{
width: 32px !important;
margin-right: -5px;
overflow: hidden;
white-space: nowrap;
}
.container.body>div[style="margin-right: 180px;"]{
margin-right: 32px !important;
}
.container.body>div[style="width: 170px;"] .sidemenu i, .container.body>div[style="width: 170px;"] .sidemenu img {
padding-right: 5px;
}
/* Hide repository url box */
.container.body>div[style="width: 170px;"] .small,.container.body>div[style="width: 170px;"] .input-group {
display: none;
}
.container.body>div[style="width: 170px;"] div[style="margin-top: 10px;"] a.btn{
width: 26px !important;
padding: 2px;
}
.container.body>div[style="width: 170px;"] div[style="margin-top: 10px;"] a.btn i {
margin: 5px 10px 5px 6px;
}
/* Hide fork button */
div.col-md-1>div.input-group.pull-right {
display: none;
}
body>.container>#fork-form{
display: inline;
}
/* Adjust issue search box size and position */
#search-filter-box {
width: 100% !important;
}
form#search-filter-form {
float: none !important;
margin-bottom: 10px;
}
form#search-filter-form>div.form-group {
width: 100% !important;
margin-bottom: 10px;
}
.table-issues a.button-link {
width: 42px;
height: 16px;
overflow: hidden;
display: inline-block;
}
/*
.nav-tabs a.btn[href$="/_edit"] {
width: 24px;
white-space: nowrap;
overflow: hidden;
padding: 4px 6px;
margin: 3px 4px 0 0;
}
*/
body>div.container.body {
margin: 0 -12px 40px -12px;
}
/* Adjust sidemenu */
.container.body>div[style="width: 170px;"]{
width: 32px !important;
margin-right: -5px;
overflow: hidden;
white-space: nowrap;
}
/* Hide badge of sidemenu */
.container.body>div[style="width: 170px;"] span.badge {
display: none;
}
/* Hide download button */
.container.body>div[style="width: 170px;"] a.btn-sm {
display: none;
}
/* Hide repository url box */
.container.body>div[style="width: 170px;"] .small,
.container.body>div[style="width: 170px;"] .input-group {
display: none;
}
/* Hide fork button */
div.input-group>span.fork {
display: none;
}
}
/****************************************************************************/
/* Print */
/****************************************************************************/
a[href]:after {
display: none;
@media print {
div.headbar {
display: none;
}
a[href]:after {
display: none;
}
}

View File

@@ -2,16 +2,14 @@ package gitbucket.core.api
import gitbucket.core.util.RepositoryName
import org.specs2.mutable.Specification
import org.json4s.jackson.JsonMethods.{pretty, parse}
import org.json4s.jackson.JsonMethods.parse
import org.json4s._
import org.specs2.matcher._
import org.scalatest.FunSuite
import java.util.{Calendar, TimeZone, Date}
class JsonFormatSpec extends Specification {
class JsonFormatSpec extends FunSuite {
val date1 = {
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
d.set(2011,3,14,16,0,49)
@@ -374,67 +372,58 @@ class JsonFormatSpec extends Specification {
}
}"""
def beFormatted(json2Arg:String) = new Matcher[String] {
def apply[S <: String](e: Expectable[S]) = {
import java.util.regex.Pattern
val json2 = Pattern.compile("""^\s*//.*$""", Pattern.MULTILINE).matcher(json2Arg).replaceAll("")
val js2 = try{
parse(json2)
}catch{
case e:com.fasterxml.jackson.core.JsonParseException => {
val p = java.lang.Math.max(e.getLocation.getCharOffset()-10,0).toInt
val message = json2.substring(p,java.lang.Math.min(p+100,json2.length))
throw new com.fasterxml.jackson.core.JsonParseException(message + e.getMessage , e.getLocation)
}
def assertJson(resultJson: String, expectJson: String) = {
import java.util.regex.Pattern
val json2 = Pattern.compile("""^\s*//.*$""", Pattern.MULTILINE).matcher(expectJson).replaceAll("")
val js2 = try {
parse(json2)
} catch {
case e: com.fasterxml.jackson.core.JsonParseException => {
val p = java.lang.Math.max(e.getLocation.getCharOffset() - 10, 0).toInt
val message = json2.substring(p, java.lang.Math.min(p + 100, json2.length))
throw new com.fasterxml.jackson.core.JsonParseException(message + e.getMessage, e.getLocation)
}
val js1 = parse(e.value)
result(js1 == js2,
"expected",
{
val diff = js2 diff js1
s"${pretty(js1)} is not ${pretty(js2)} \n\n ${pretty(Extraction.decompose(diff)(org.json4s.DefaultFormats))}"
},
e)
}
val js1 = parse(resultJson)
assert(js1 === js2)
}
"JsonFormat" should {
"apiUser" in {
JsonFormat(apiUser) must beFormatted(apiUserJson)
}
"repository" in {
JsonFormat(repository) must beFormatted(repositoryJson)
}
"apiPushCommit" in {
JsonFormat(apiPushCommit) must beFormatted(apiPushCommitJson)
}
"apiComment" in {
JsonFormat(apiComment) must beFormatted(apiCommentJson)
JsonFormat(apiCommentPR) must beFormatted(apiCommentPRJson)
}
"apiCommitListItem" in {
JsonFormat(apiCommitListItem) must beFormatted(apiCommitListItemJson)
}
"apiCommitStatus" in {
JsonFormat(apiCommitStatus) must beFormatted(apiCommitStatusJson)
}
"apiCombinedCommitStatus" in {
JsonFormat(apiCombinedCommitStatus) must beFormatted(apiCombinedCommitStatusJson)
}
"apiLabel" in {
JsonFormat(apiLabel) must beFormatted(apiLabelJson)
}
"apiIssue" in {
JsonFormat(apiIssue) must beFormatted(apiIssueJson)
JsonFormat(apiIssuePR) must beFormatted(apiIssuePRJson)
}
"apiPullRequest" in {
JsonFormat(apiPullRequest) must beFormatted(apiPullRequestJson)
}
"apiPullRequestReviewComment" in {
JsonFormat(apiPullRequestReviewComment) must beFormatted(apiPullRequestReviewCommentJson)
}
"apiBranchProtection" in {
JsonFormat(apiBranchProtection) must beFormatted(apiBranchProtectionJson)
}
test("apiUser") {
assertJson(JsonFormat(apiUser), apiUserJson)
}
test("repository") {
assertJson(JsonFormat(repository), repositoryJson)
}
test("apiPushCommit") {
assertJson(JsonFormat(apiPushCommit), apiPushCommitJson)
}
test("apiComment") {
assertJson(JsonFormat(apiComment), apiCommentJson)
assertJson(JsonFormat(apiCommentPR), apiCommentPRJson)
}
test("apiCommitListItem") {
assertJson(JsonFormat(apiCommitListItem), apiCommitListItemJson)
}
test("apiCommitStatus") {
assertJson(JsonFormat(apiCommitStatus), apiCommitStatusJson)
}
test("apiCombinedCommitStatus") {
assertJson(JsonFormat(apiCombinedCommitStatus), apiCombinedCommitStatusJson)
}
test("apiLabel") {
assertJson(JsonFormat(apiLabel), apiLabelJson)
}
test("apiIssue") {
assertJson(JsonFormat(apiIssue), apiIssueJson)
assertJson(JsonFormat(apiIssuePR), apiIssuePRJson)
}
test("apiPullRequest") {
assertJson(JsonFormat(apiPullRequest), apiPullRequestJson)
}
test("apiPullRequestReviewComment") {
assertJson(JsonFormat(apiPullRequestReviewComment), apiPullRequestReviewCommentJson)
}
test("apiBranchProtection") {
assertJson(JsonFormat(apiBranchProtection), apiBranchProtectionJson)
}
}

View File

@@ -1,26 +1,25 @@
package gitbucket.core.model
import gitbucket.core.model.CommitState._
import org.specs2.mutable.Specification
import org.scalatest.FunSpec
class CommitStateSpec extends Specification {
"CommitState" should {
"combine empty must eq PENDING" in {
combine(Set()) must_== PENDING
class CommitStateSpec extends FunSpec {
describe("CommitState") {
it("should combine empty must eq PENDING") {
assert(combine(Set()) == PENDING)
}
"combine includes ERROR must eq FAILURE" in {
combine(Set(ERROR, SUCCESS, PENDING)) must_== FAILURE
it("should combine includes ERROR must eq FAILURE") {
assert(combine(Set(ERROR, SUCCESS, PENDING)) == FAILURE)
}
"combine includes FAILURE must eq peinding" in {
combine(Set(FAILURE, SUCCESS, PENDING)) must_== FAILURE
it("should combine includes FAILURE must eq peinding") {
assert(combine(Set(FAILURE, SUCCESS, PENDING)) == FAILURE)
}
"combine includes PENDING must eq peinding" in {
combine(Set(PENDING, SUCCESS)) must_== PENDING
it("should combine includes PENDING must eq peinding") {
assert(combine(Set(PENDING, SUCCESS)) == PENDING)
}
"combine only SUCCESS must eq SUCCESS" in {
combine(Set(SUCCESS)) must_== SUCCESS
it("should combine only SUCCESS must eq SUCCESS") {
assert(combine(Set(SUCCESS)) == SUCCESS)
}
}
}

View File

@@ -1,91 +1,78 @@
package gitbucket.core.service
import gitbucket.core.model._
import org.specs2.mutable.Specification
import java.util.Date
import org.scalatest.FunSuite
class AccessTokenServiceSpec extends Specification with ServiceSpecBase {
class AccessTokenServiceSpec extends FunSuite with ServiceSpecBase {
"AccessTokenService" should {
"generateAccessToken" in { withTestDB { implicit session =>
AccessTokenService.generateAccessToken("root", "note") must be like{
case (id, token) if id != 0 => ok
}
}}
test("generateAccessToken") { withTestDB { implicit session =>
assert(AccessTokenService.generateAccessToken("root", "note") match {
case (id, token) => id != 0
})
}}
"getAccessTokens" in { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
val tokenHash = AccessTokenService.tokenToHash(token)
test("getAccessTokens") { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
val tokenHash = AccessTokenService.tokenToHash(token)
AccessTokenService.getAccessTokens("root") must be like{
case List(AccessToken(`id`, "root", `tokenHash`, "note")) => ok
}
}}
assert(AccessTokenService.getAccessTokens("root") == List(AccessToken(`id`, "root", `tokenHash`, "note")))
}}
"getAccessTokens(root) get root's tokens" in { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
val tokenHash = AccessTokenService.tokenToHash(token)
val user2 = generateNewAccount("user2")
AccessTokenService.generateAccessToken("user2", "note2")
test("getAccessTokens(root) get root's tokens") { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
val tokenHash = AccessTokenService.tokenToHash(token)
val user2 = generateNewAccount("user2")
AccessTokenService.generateAccessToken("user2", "note2")
AccessTokenService.getAccessTokens("root") must be like{
case List(AccessToken(`id`, "root", `tokenHash`, "note")) => ok
}
}}
assert(AccessTokenService.getAccessTokens("root") == List(AccessToken(`id`, "root", `tokenHash`, "note")))
}}
"deleteAccessToken" in { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
val user2 = generateNewAccount("user2")
AccessTokenService.generateAccessToken("user2", "note2")
test("deleteAccessToken") { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
val user2 = generateNewAccount("user2")
AccessTokenService.generateAccessToken("user2", "note2")
AccessTokenService.deleteAccessToken("root", id)
AccessTokenService.deleteAccessToken("root", id)
AccessTokenService.getAccessTokens("root") must beEmpty
}}
assert(AccessTokenService.getAccessTokens("root").isEmpty)
}}
"getAccountByAccessToken" in { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
AccessTokenService.getAccountByAccessToken(token) must beSome.like {
case user => user.userName must_== "root"
}
}}
test("getAccountByAccessToken") { withTestDB { implicit session =>
val (id, token) = AccessTokenService.generateAccessToken("root", "note")
assert(AccessTokenService.getAccountByAccessToken(token) match {
case Some(user) => user.userName == "root"
})
}}
"getAccountByAccessToken don't get removed account" in { withTestDB { implicit session =>
val user2 = generateNewAccount("user2")
val (id, token) = AccessTokenService.generateAccessToken("user2", "note")
AccountService.updateAccount(user2.copy(isRemoved=true))
test("getAccountByAccessToken don't get removed account") { withTestDB { implicit session =>
val user2 = generateNewAccount("user2")
val (id, token) = AccessTokenService.generateAccessToken("user2", "note")
AccountService.updateAccount(user2.copy(isRemoved=true))
AccessTokenService.getAccountByAccessToken(token) must beEmpty
}}
assert(AccessTokenService.getAccountByAccessToken(token).isEmpty)
}}
"generateAccessToken create uniq token" in { withTestDB { implicit session =>
val tokenIt = List("token1","token1","token1","token2").iterator
val service = new AccessTokenService{
override def makeAccessTokenString:String = tokenIt.next
}
test("generateAccessToken create uniq token") { withTestDB { implicit session =>
val tokenIt = List("token1","token1","token1","token2").iterator
val service = new AccessTokenService{
override def makeAccessTokenString:String = tokenIt.next
}
service.generateAccessToken("root", "note1") must like{
case (_, "token1") => ok
}
service.generateAccessToken("root", "note2") must like{
case (_, "token2") => ok
}
}}
assert(service.generateAccessToken("root", "note1")._2 == "token1")
assert(service.generateAccessToken("root", "note2")._2 == "token2")
}}
"when update Account.userName then AccessToken.userName changed" in { withTestDB { implicit session =>
val user2 = generateNewAccount("user2")
val (id, token) = AccessTokenService.generateAccessToken("user2", "note")
import gitbucket.core.model.Profile._
import profile.simple._
Accounts.filter(_.userName === "user2".bind).map(_.userName).update("user3")
test("when update Account.userName then AccessToken.userName changed") { withTestDB { implicit session =>
val user2 = generateNewAccount("user2")
val (id, token) = AccessTokenService.generateAccessToken("user2", "note")
import gitbucket.core.model.Profile._
import profile.simple._
Accounts.filter(_.userName === "user2".bind).map(_.userName).update("user3")
AccessTokenService.getAccountByAccessToken(token) must beSome.like {
case user => user.userName must_== "user3"
}
}}
}
assert(AccessTokenService.getAccountByAccessToken(token) match {
case Some(user) => user.userName == "user3"
})
}}
}

View File

@@ -1,79 +1,71 @@
package gitbucket.core.service
import gitbucket.core.model.{Account, GroupMember}
import org.specs2.mutable.Specification
import java.util.Date
import org.scalatest.FunSuite
class AccountServiceSpec extends Specification with ServiceSpecBase {
class AccountServiceSpec extends FunSuite with ServiceSpecBase {
"AccountService" should {
val RootMailAddress = "root@localhost"
val RootMailAddress = "root@localhost"
"getAllUsers" in { withTestDB { implicit session =>
AccountService.getAllUsers() must be like{
case List(Account("root", "root", RootMailAddress, _, true, _, _, _, None, None, false, false)) => ok
}
}}
test("getAllUsers") { withTestDB { implicit session =>
assert(AccountService.getAllUsers() match {
case List(Account("root", "root", RootMailAddress, _, true, _, _, _, None, None, false, false)) => true
case _ => false
})
}}
"getAccountByUserName" in { withTestDB { implicit session =>
AccountService.getAccountByUserName("root") must beSome.like {
case user => user.userName must_== "root"
}
test("getAccountByUserName") { withTestDB { implicit session =>
assert(AccountService.getAccountByUserName("root").get.userName == "root")
assert(AccountService.getAccountByUserName("invalid user name").isEmpty)
}}
AccountService.getAccountByUserName("invalid user name") must beNone
}}
test("getAccountByMailAddress") { withTestDB { implicit session =>
assert(AccountService.getAccountByMailAddress(RootMailAddress).isDefined)
}}
"getAccountByMailAddress" in { withTestDB { implicit session =>
AccountService.getAccountByMailAddress(RootMailAddress) must beSome
}}
test("updateLastLoginDate") { withTestDB { implicit session =>
val root = "root"
def user() = AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists"))
"updateLastLoginDate" in { withTestDB { implicit session =>
val root = "root"
def user() =
AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists"))
assert(user().lastLoginDate.isEmpty)
user().lastLoginDate must beNone
val date1 = new Date
AccountService.updateLastLoginDate(root)
user().lastLoginDate must beSome.like{ case date =>
date must be_>(date1)
}
val date2 = new Date
Thread.sleep(1000)
AccountService.updateLastLoginDate(root)
user().lastLoginDate must beSome.like{ case date =>
date must be_>(date2)
}
}}
val date1 = new Date
AccountService.updateLastLoginDate(root)
assert(user().lastLoginDate.get.compareTo(date1) > 0)
"updateAccount" in { withTestDB { implicit session =>
val root = "root"
def user() =
AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists"))
val date2 = new Date
Thread.sleep(1000)
AccountService.updateLastLoginDate(root)
assert(user().lastLoginDate.get.compareTo(date2) > 0)
}}
val newAddress = "new mail address"
AccountService.updateAccount(user().copy(mailAddress = newAddress))
user().mailAddress must_== newAddress
}}
test("updateAccount") { withTestDB { implicit session =>
val root = "root"
def user() = AccountService.getAccountByUserName(root).getOrElse(sys.error(s"user $root does not exists"))
"group" in { withTestDB { implicit session =>
val group1 = "group1"
val user1 = "root"
AccountService.createGroup(group1, None)
val newAddress = "new mail address"
AccountService.updateAccount(user().copy(mailAddress = newAddress))
assert(user().mailAddress == newAddress)
}}
AccountService.getGroupMembers(group1) must_== Nil
AccountService.getGroupsByUserName(user1) must_== Nil
test("group") { withTestDB { implicit session =>
val group1 = "group1"
val user1 = "root"
AccountService.createGroup(group1, None)
AccountService.updateGroupMembers(group1, List((user1, true)))
assert(AccountService.getGroupMembers(group1) == Nil)
assert(AccountService.getGroupsByUserName(user1) == Nil)
AccountService.getGroupMembers(group1) must_== List(GroupMember(group1, user1, true))
AccountService.getGroupsByUserName(user1) must_== List(group1)
AccountService.updateGroupMembers(group1, List((user1, true)))
AccountService.updateGroupMembers(group1, Nil)
assert(AccountService.getGroupMembers(group1) == List(GroupMember(group1, user1, true)))
assert(AccountService.getGroupsByUserName(user1) == List(group1))
AccountService.getGroupMembers(group1) must_== Nil
AccountService.getGroupsByUserName(user1) must_== Nil
}}
}
AccountService.updateGroupMembers(group1, Nil)
assert(AccountService.getGroupMembers(group1) == Nil)
assert(AccountService.getGroupsByUserName(user1) == Nil)
}}
}

View File

@@ -1,78 +0,0 @@
package gitbucket.core.service
import gitbucket.core.model._
import gitbucket.core.model.Profile._
import profile.simple._
import org.specs2.mutable.Specification
import java.util.Date
class CommitStatusServiceSpec extends Specification with ServiceSpecBase with CommitStatusService
with RepositoryService with AccountService{
val now = new java.util.Date()
val fixture1 = CommitStatus(
userName = "root",
repositoryName = "repo",
commitId = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7",
context = "jenkins/test",
creator = "tester",
state = CommitState.PENDING,
targetUrl = Some("http://example.com/target"),
description = Some("description"),
updatedDate = now,
registeredDate = now)
def findById(id: Int)(implicit s:Session) = CommitStatuses.filter(_.byPrimaryKey(id)).firstOption
def generateFixture1(tester:Account)(implicit s:Session) = createCommitStatus(
userName = fixture1.userName,
repositoryName = fixture1.repositoryName,
sha = fixture1.commitId,
context = fixture1.context,
state = fixture1.state,
targetUrl = fixture1.targetUrl,
description = fixture1.description,
creator = tester,
now = fixture1.registeredDate)
"CommitStatusService" should {
"createCommitState can insert and update" in { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) must_==
Some(fixture1.copy(commitStatusId=id))
// other one can update
val tester2 = generateNewAccount("tester2")
val time2 = new java.util.Date();
val id2 = createCommitStatus(
userName = fixture1.userName,
repositoryName = fixture1.repositoryName,
sha = fixture1.commitId,
context = fixture1.context,
state = CommitState.SUCCESS,
targetUrl = Some("http://example.com/target2"),
description = Some("description2"),
creator = tester2,
now = time2)
getCommitStatus(fixture1.userName, fixture1.repositoryName, id2) must_== Some(fixture1.copy(
commitStatusId = id,
creator = "tester2",
state = CommitState.SUCCESS,
targetUrl = Some("http://example.com/target2"),
description = Some("description2"),
updatedDate = time2))
}}
"getCommitStatus can find by commitId and context" in { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) must_== Some(fixture1.copy(commitStatusId=id))
}}
"getCommitStatus can find by commitStatusId" in { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) must_== Some(fixture1.copy(commitStatusId=id))
}}
}
}

View File

@@ -0,0 +1,74 @@
package gitbucket.core.service
import gitbucket.core.model._
import gitbucket.core.model.Profile._
import profile.simple._
import org.scalatest.FunSuite
class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitStatusService
with RepositoryService with AccountService{
val now = new java.util.Date()
val fixture1 = CommitStatus(
userName = "root",
repositoryName = "repo",
commitId = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7",
context = "jenkins/test",
creator = "tester",
state = CommitState.PENDING,
targetUrl = Some("http://example.com/target"),
description = Some("description"),
updatedDate = now,
registeredDate = now)
def findById(id: Int)(implicit s:Session) = CommitStatuses.filter(_.byPrimaryKey(id)).firstOption
def generateFixture1(tester:Account)(implicit s:Session) = createCommitStatus(
userName = fixture1.userName,
repositoryName = fixture1.repositoryName,
sha = fixture1.commitId,
context = fixture1.context,
state = fixture1.state,
targetUrl = fixture1.targetUrl,
description = fixture1.description,
creator = tester,
now = fixture1.registeredDate)
test("createCommitState can insert and update") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
// other one can update
val tester2 = generateNewAccount("tester2")
val time2 = new java.util.Date();
val id2 = createCommitStatus(
userName = fixture1.userName,
repositoryName = fixture1.repositoryName,
sha = fixture1.commitId,
context = fixture1.context,
state = CommitState.SUCCESS,
targetUrl = Some("http://example.com/target2"),
description = Some("description2"),
creator = tester2,
now = time2)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id2) == Some(fixture1.copy(
commitStatusId = id,
creator = "tester2",
state = CommitState.SUCCESS,
targetUrl = Some("http://example.com/target2"),
description = Some("description2"),
updatedDate = time2)))
}}
test("getCommitStatus can find by commitId and context") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(fixture1.copy(commitStatusId=id)))
}}
test("getCommitStatus can find by commitStatusId") { withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
createRepository(fixture1.repositoryName,fixture1.userName,None,false)
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
}}
}

View File

@@ -2,49 +2,44 @@ package gitbucket.core.service
import gitbucket.core.model._
import gitbucket.core.service.IssuesService._
import org.specs2.mutable.Specification
import java.util.Date
import org.scalatest.FunSuite
class IssuesServiceSpec extends Specification with ServiceSpecBase {
"IssuesService" should {
"getCommitStatues" in { withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1","repo1")
class IssuesServiceSpec extends FunSuite with ServiceSpecBase {
test("getCommitStatues") { withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1","repo1")
def getCommitStatues = dummyService.getCommitStatues(List(("user1","repo1",1),("user1","repo1",2)))
def getCommitStatues = dummyService.getCommitStatues(List(("user1","repo1",1),("user1","repo1",2)))
getCommitStatues must_== Map.empty
assert(getCommitStatues == Map.empty)
val now = new java.util.Date()
val issueId = generateNewIssue("user1","repo1")
issueId must_== 1
val now = new java.util.Date()
val issueId = generateNewIssue("user1","repo1")
assert(issueId == 1)
getCommitStatues must_== Map.empty
assert(getCommitStatues == Map.empty)
val cs = dummyService.createCommitStatus("user1","repo1","shasha", "default", CommitState.SUCCESS, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
val cs = dummyService.createCommitStatus("user1","repo1","shasha", "default", CommitState.SUCCESS, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
getCommitStatues must_== Map.empty
assert(getCommitStatues == Map.empty)
val (is2, pr2) = generateNewPullRequest("user1/repo1/master","user1/repo1/feature1")
pr2.issueId must_== 2
val (is2, pr2) = generateNewPullRequest("user1/repo1/master","user1/repo1/feature1")
assert(pr2.issueId == 2)
// if there are no statuses, state is none
getCommitStatues must_== Map.empty
// if there are no statuses, state is none
assert(getCommitStatues == Map.empty)
// if there is a status, state is that
val cs2 = dummyService.createCommitStatus("user1","repo1","feature1", "default", CommitState.SUCCESS, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
getCommitStatues must_== Map(("user1","repo1",2) -> CommitStatusInfo(1,1,Some("default"),Some(CommitState.SUCCESS),Some("http://exmple.com/ci"),Some("exampleService")))
// if there is a status, state is that
val cs2 = dummyService.createCommitStatus("user1","repo1","feature1", "default", CommitState.SUCCESS, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
assert(getCommitStatues == Map(("user1","repo1",2) -> CommitStatusInfo(1,1,Some("default"),Some(CommitState.SUCCESS),Some("http://exmple.com/ci"),Some("exampleService"))))
// if there are two statuses, state is none
val cs3 = dummyService.createCommitStatus("user1","repo1","feature1", "pend", CommitState.PENDING, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
getCommitStatues must_== Map(("user1","repo1",2) -> CommitStatusInfo(2,1,None,None,None,None))
// if there are two statuses, state is none
val cs3 = dummyService.createCommitStatus("user1","repo1","feature1", "pend", CommitState.PENDING, Some("http://exmple.com/ci"), Some("exampleService"), now, user1)
assert(getCommitStatues == Map(("user1","repo1",2) -> CommitStatusInfo(2,1,None,None,None,None)))
// get only statuses in query issues
val (is3, pr3) = generateNewPullRequest("user1/repo1/master","user1/repo1/feature3")
val cs4 = dummyService.createCommitStatus("user1","repo1","feature3", "none", CommitState.PENDING, None, None, now, user1)
getCommitStatues must_== Map(("user1","repo1",2) -> CommitStatusInfo(2,1,None,None,None,None))
} }
}
// get only statuses in query issues
val (is3, pr3) = generateNewPullRequest("user1/repo1/master","user1/repo1/feature3")
val cs4 = dummyService.createCommitStatus("user1","repo1","feature3", "none", CommitState.PENDING, None, None, now, user1)
assert(getCommitStatues == Map(("user1","repo1",2) -> CommitStatusInfo(2,1,None,None,None,None)))
} }
}

View File

@@ -1,12 +1,11 @@
package gitbucket.core.service
import gitbucket.core.model._
import org.scalatest.FunSpec
import org.specs2.mutable.Specification
class LabelsServiceSpec extends Specification with ServiceSpecBase {
"getLabels" should {
"be empty when not have any labels" in { withTestDB { implicit session =>
class LabelsServiceSpec extends FunSpec with ServiceSpecBase {
describe("getLabels") {
it("should be empty when not have any labels") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
generateNewUserWithDBRepository("user1", "repo2")
@@ -15,9 +14,9 @@ class LabelsServiceSpec extends Specification with ServiceSpecBase {
generateNewUserWithDBRepository("user2", "repo1")
dummyService.createLabel("user2", "repo1", "label1", "000000")
dummyService.getLabels("user1", "repo1") must haveSize(0)
assert(dummyService.getLabels("user1", "repo1").isEmpty)
}}
"return contained labels" in { withTestDB { implicit session =>
it("should return contained labels") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
val labelId2 = dummyService.createLabel("user1", "repo1", "label2", "ffffff")
@@ -30,20 +29,22 @@ class LabelsServiceSpec extends Specification with ServiceSpecBase {
def getLabels = dummyService.getLabels("user1", "repo1")
getLabels must haveSize(2)
getLabels must_== List(
assert(getLabels.length == 2)
assert(getLabels == List(
Label("user1", "repo1", labelId1, "label1", "000000"),
Label("user1", "repo1", labelId2, "label2", "ffffff"))
)
}}
}
"getLabel" should {
"return None when the label not exist" in { withTestDB { implicit session =>
describe("getLabel") {
it("should return None when the label not exist") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
dummyService.getLabel("user1", "repo1", 1) must beNone
dummyService.getLabel("user1", "repo1", "label1") must beNone
assert(dummyService.getLabel("user1", "repo1", 1) == None)
assert(dummyService.getLabel("user1", "repo1", "label1") == None)
}}
"return a label fetched by label id" in { withTestDB { implicit session =>
it("should return a label fetched by label id") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
dummyService.createLabel("user1", "repo1", "label2", "ffffff")
@@ -55,9 +56,9 @@ class LabelsServiceSpec extends Specification with ServiceSpecBase {
dummyService.createLabel("user2", "repo1", "label1", "000000")
def getLabel = dummyService.getLabel("user1", "repo1", labelId1)
getLabel must_== Some(Label("user1", "repo1", labelId1, "label1", "000000"))
assert(getLabel == Some(Label("user1", "repo1", labelId1, "label1", "000000")))
}}
"return a label fetched by label name" in { withTestDB { implicit session =>
it("should return a label fetched by label name") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
dummyService.createLabel("user1", "repo1", "label2", "ffffff")
@@ -69,11 +70,11 @@ class LabelsServiceSpec extends Specification with ServiceSpecBase {
dummyService.createLabel("user2", "repo1", "label1", "000000")
def getLabel = dummyService.getLabel("user1", "repo1", "label1")
getLabel must_== Some(Label("user1", "repo1", labelId1, "label1", "000000"))
getLabel == Some(Label("user1", "repo1", labelId1, "label1", "000000"))
}}
}
"createLabel" should {
"return accurate label id" in { withTestDB { implicit session =>
describe("createLabel") {
it("should return accurate label id") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
generateNewUserWithDBRepository("user1", "repo2")
generateNewUserWithDBRepository("user2", "repo1")
@@ -81,13 +82,13 @@ class LabelsServiceSpec extends Specification with ServiceSpecBase {
dummyService.createLabel("user1", "repo2", "label1", "000000")
dummyService.createLabel("user2", "repo1", "label1", "000000")
val labelId = dummyService.createLabel("user1", "repo1", "label2", "000000")
labelId must_== 4
assert(labelId == 4)
def getLabel = dummyService.getLabel("user1", "repo1", labelId)
getLabel must_== Some(Label("user1", "repo1", labelId, "label2", "000000"))
assert(getLabel == Some(Label("user1", "repo1", labelId, "label2", "000000")))
}}
}
"updateLabel" should {
"change target label" in { withTestDB { implicit session =>
describe("updateLabel") {
it("should change target label") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
generateNewUserWithDBRepository("user1", "repo2")
generateNewUserWithDBRepository("user2", "repo1")
@@ -96,11 +97,11 @@ class LabelsServiceSpec extends Specification with ServiceSpecBase {
dummyService.createLabel("user2", "repo1", "label1", "000000")
dummyService.updateLabel("user1", "repo1", labelId, "updated-label", "ffffff")
def getLabel = dummyService.getLabel("user1", "repo1", labelId)
getLabel must_== Some(Label("user1", "repo1", labelId, "updated-label", "ffffff"))
assert(getLabel == Some(Label("user1", "repo1", labelId, "updated-label", "ffffff")))
}}
}
"deleteLabel" should {
"remove target label" in { withTestDB { implicit session =>
describe("deleteLabel") {
it("should remove target label") { withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
generateNewUserWithDBRepository("user1", "repo2")
generateNewUserWithDBRepository("user2", "repo1")
@@ -108,7 +109,7 @@ class LabelsServiceSpec extends Specification with ServiceSpecBase {
dummyService.createLabel("user1", "repo2", "label1", "000000")
dummyService.createLabel("user2", "repo1", "label1", "000000")
dummyService.deleteLabel("user1", "repo1", labelId)
dummyService.getLabel("user1", "repo1", labelId) must beNone
assert(dummyService.getLabel("user1", "repo1", labelId) == None)
}}
}
}

View File

@@ -1,26 +1,17 @@
package gitbucket.core.service
import gitbucket.core.model._
import gitbucket.core.util.JGitUtil
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.GitSpecUtil._
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib._
import org.eclipse.jgit.revwalk._
import org.eclipse.jgit.treewalk._
import org.specs2.mutable.Specification
import org.scalatest.FunSpec
import java.io.File
import java.nio.file._
import java.util.Date
class MergeServiceSpec extends Specification {
sequential
class MergeServiceSpec extends FunSpec {
val service = new MergeService{}
val branch = "master"
val issueId = 10
@@ -36,95 +27,95 @@ class MergeServiceSpec extends Specification {
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" )
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" )
}
"checkConflict, checkConflictCache" should {
"checkConflict false if not conflicted, and create cache" in {
describe("checkConflict, checkConflictCache") {
it("checkConflict false if not conflicted, and create cache") {
val repo1Dir = initRepository("user1","repo1")
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual None
assert(service.checkConflictCache("user1", "repo1", branch, issueId) == None)
val conflicted = service.checkConflict("user1", "repo1", branch, issueId)
service.checkConflictCache("user1", "repo1", branch, issueId) mustEqual Some(false)
conflicted mustEqual false
assert(service.checkConflictCache("user1", "repo1", branch, issueId) == Some(false))
assert(conflicted == false)
}
"checkConflict true if not conflicted, and create cache" in {
it("checkConflict true if not conflicted, and create cache") {
val repo2Dir = initRepository("user1","repo2")
using(Git.open(repo2Dir)){ git =>
createConfrict(git)
}
service.checkConflictCache("user1", "repo2", branch, issueId) mustEqual None
assert(service.checkConflictCache("user1", "repo2", branch, issueId) == None)
val conflicted = service.checkConflict("user1", "repo2", branch, issueId)
conflicted mustEqual true
service.checkConflictCache("user1", "repo2", branch, issueId) mustEqual Some(true)
assert(conflicted == true)
assert(service.checkConflictCache("user1", "repo2", branch, issueId) == Some(true))
}
}
"checkConflictCache" should {
"merged cache invalid if origin branch moved" in {
describe("checkConflictCache") {
it("merged cache invalid if origin branch moved") {
val repo3Dir = initRepository("user1","repo3")
service.checkConflict("user1", "repo3", branch, issueId) mustEqual false
service.checkConflictCache("user1", "repo3", branch, issueId) mustEqual Some(false)
assert(service.checkConflict("user1", "repo3", branch, issueId) == false)
assert(service.checkConflictCache("user1", "repo3", branch, issueId) == Some(false))
using(Git.open(repo3Dir)){ git =>
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" )
}
service.checkConflictCache("user1", "repo3", branch, issueId) mustEqual None
assert(service.checkConflictCache("user1", "repo3", branch, issueId) == None)
}
"merged cache invalid if request branch moved" in {
it("merged cache invalid if request branch moved") {
val repo4Dir = initRepository("user1","repo4")
service.checkConflict("user1", "repo4", branch, issueId) mustEqual false
service.checkConflictCache("user1", "repo4", branch, issueId) mustEqual Some(false)
assert(service.checkConflict("user1", "repo4", branch, issueId) == false)
assert(service.checkConflictCache("user1", "repo4", branch, issueId) == Some(false))
using(Git.open(repo4Dir)){ git =>
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" )
}
service.checkConflictCache("user1", "repo4", branch, issueId) mustEqual None
assert(service.checkConflictCache("user1", "repo4", branch, issueId) == None)
}
"merged cache invalid if origin branch moved" in {
it("should merged cache invalid if origin branch moved") {
val repo5Dir = initRepository("user1","repo5")
service.checkConflict("user1", "repo5", branch, issueId) mustEqual false
service.checkConflictCache("user1", "repo5", branch, issueId) mustEqual Some(false)
assert(service.checkConflict("user1", "repo5", branch, issueId) == false)
assert(service.checkConflictCache("user1", "repo5", branch, issueId) == Some(false))
using(Git.open(repo5Dir)){ git =>
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2" )
}
service.checkConflictCache("user1", "repo5", branch, issueId) mustEqual None
assert(service.checkConflictCache("user1", "repo5", branch, issueId) == None)
}
"conflicted cache invalid if request branch moved" in {
it("conflicted cache invalid if request branch moved") {
val repo6Dir = initRepository("user1","repo6")
using(Git.open(repo6Dir)){ git =>
createConfrict(git)
}
service.checkConflict("user1", "repo6", branch, issueId) mustEqual true
service.checkConflictCache("user1", "repo6", branch, issueId) mustEqual Some(true)
assert(service.checkConflict("user1", "repo6", branch, issueId) == true)
assert(service.checkConflictCache("user1", "repo6", branch, issueId) == Some(true))
using(Git.open(repo6Dir)){ git =>
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4" )
}
service.checkConflictCache("user1", "repo6", branch, issueId) mustEqual None
assert(service.checkConflictCache("user1", "repo6", branch, issueId) == None)
}
"conflicted cache invalid if origin branch moved" in {
it("conflicted cache invalid if origin branch moved") {
val repo7Dir = initRepository("user1","repo7")
using(Git.open(repo7Dir)){ git =>
createConfrict(git)
}
service.checkConflict("user1", "repo7", branch, issueId) mustEqual true
service.checkConflictCache("user1", "repo7", branch, issueId) mustEqual Some(true)
assert(service.checkConflict("user1", "repo7", branch, issueId) == true)
assert(service.checkConflictCache("user1", "repo7", branch, issueId) == Some(true))
using(Git.open(repo7Dir)){ git =>
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge4" )
}
service.checkConflictCache("user1", "repo7", branch, issueId) mustEqual None
assert(service.checkConflictCache("user1", "repo7", branch, issueId) == None)
}
}
"mergePullRequest" should {
"can merge" in {
describe("mergePullRequest") {
it("can merge") {
val repo8Dir = initRepository("user1","repo8")
using(Git.open(repo8Dir)){ git =>
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge2" )
val committer = new PersonIdent("dummy2", "dummy2@example.com")
getFile(git, branch, "test.txt").content.get mustEqual "hoge"
assert(getFile(git, branch, "test.txt").content.get == "hoge")
val requestBranchId = git.getRepository.resolve(s"refs/pull/${issueId}/head")
val masterId = git.getRepository.resolve(branch)
service.mergePullRequest(git, branch, issueId, "merged", committer)
val lastCommitId = git.getRepository.resolve(branch);
val lastCommitId = git.getRepository.resolve(branch)
val commit = using(new RevWalk(git.getRepository))(_.parseCommit(lastCommitId))
commit.getCommitterIdent() mustEqual committer
commit.getAuthorIdent() mustEqual committer
commit.getFullMessage() mustEqual "merged"
commit.getParents.toSet mustEqual Set( requestBranchId, masterId )
getFile(git, branch, "test.txt").content.get mustEqual "hoge2"
assert(commit.getCommitterIdent() == committer)
assert(commit.getAuthorIdent() == committer)
assert(commit.getFullMessage() == "merged")
assert(commit.getParents.toSet == Set( requestBranchId, masterId ))
assert(getFile(git, branch, "test.txt").content.get == "hoge2")
}
}
}

View File

@@ -1,134 +1,134 @@
package gitbucket.core.service
import gitbucket.core.util.GitSpecUtil._
import org.specs2.mutable.Specification
import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand}
import org.eclipse.jgit.lib.ObjectId
import gitbucket.core.model.CommitState
import gitbucket.core.service.ProtectedBranchService.{ProtectedBranchReceiveHook, ProtectedBranchInfo}
import scalaz._, Scalaz._
import org.scalatest.FunSpec
class ProtectedBranchServiceSpec extends Specification with ServiceSpecBase with ProtectedBranchService with CommitStatusService {
class ProtectedBranchServiceSpec extends FunSpec with ServiceSpecBase with ProtectedBranchService with CommitStatusService {
val receiveHook = new ProtectedBranchReceiveHook()
val now = new java.util.Date()
val sha = "0c77148632618b59b6f70004e3084002be2b8804"
val sha2 = "0c77148632618b59b6f70004e3084002be2b8805"
"getProtectedBranchInfo" should {
"empty is disabled" in {
describe("getProtectedBranchInfo") {
it("should empty is disabled") {
withTestDB { implicit session =>
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo.disabled("user1", "repo1")
assert(getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo.disabled("user1", "repo1"))
}
}
"enable and update and disable" in {
it("should enable and update and disable") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
enableBranchProtection("user1", "repo1", "branch", false, Nil)
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo("user1", "repo1", true, Nil, false)
assert(getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo("user1", "repo1", true, Nil, false))
enableBranchProtection("user1", "repo1", "branch", true, Seq("hoge","huge"))
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo("user1", "repo1", true, Seq("hoge","huge"), true)
assert(getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo("user1", "repo1", true, Seq("hoge","huge"), true))
disableBranchProtection("user1", "repo1", "branch")
getProtectedBranchInfo("user1", "repo1", "branch") must_== ProtectedBranchInfo.disabled("user1", "repo1")
assert(getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo.disabled("user1", "repo1"))
}
}
"empty contexts is no-include-administrators" in {
it("should empty contexts is no-include-administrators") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
enableBranchProtection("user1", "repo1", "branch", false, Nil)
getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators must_== false
assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
enableBranchProtection("user1", "repo1", "branch", true, Nil)
getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators must_== false
assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
}
}
"getProtectedBranchList" in {
it("getProtectedBranchList") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
enableBranchProtection("user1", "repo1", "branch", false, Nil)
enableBranchProtection("user1", "repo1", "branch2", false, Seq("fuga"))
enableBranchProtection("user1", "repo1", "branch3", true, Seq("hoge"))
getProtectedBranchList("user1", "repo1").toSet must_== Set("branch", "branch2", "branch3")
assert(getProtectedBranchList("user1", "repo1").toSet == Set("branch", "branch2", "branch3"))
}
}
"getBranchProtectedReason on force push from admin" in {
it("getBranchProtectedReason on force push from admin") {
withTestDB { implicit session =>
withTestRepository { git =>
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(true) }
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE_NONFASTFORWARD)
generateNewUserWithDBRepository("user1", "repo1")
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == None)
enableBranchProtection("user1", "repo1", "branch", false, Nil)
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("Cannot force-push to a protected branch")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == Some("Cannot force-push to a protected branch"))
}
}
}
"getBranchProtectedReason on force push from othre" in {
it("getBranchProtectedReason on force push from other") {
withTestDB { implicit session =>
withTestRepository { git =>
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(true) }
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE_NONFASTFORWARD)
generateNewUserWithDBRepository("user1", "repo1")
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == None)
enableBranchProtection("user1", "repo1", "branch", false, Nil)
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("Cannot force-push to a protected branch")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == Some("Cannot force-push to a protected branch"))
}
}
}
"getBranchProtectedReason check status on push from othre" in {
it("getBranchProtectedReason check status on push from other") {
withTestDB { implicit session =>
withTestRepository { git =>
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(false) }
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE)
val user1 = generateNewUserWithDBRepository("user1", "repo1")
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == None)
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("Required status check \"must\" is expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == Some("Required status check \"must\" is expected"))
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("2 of 2 required status checks are expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == Some("2 of 2 required status checks are expected"))
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("2 of 2 required status checks are expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == Some("2 of 2 required status checks are expected"))
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== Some("Required status check \"must2\" is expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == Some("Required status check \"must2\" is expected"))
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
receiveHook.preReceive("user1", "repo1", rp, rc, "user2") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == None)
}
}
}
"getBranchProtectedReason check status on push from admin" in {
it("getBranchProtectedReason check status on push from admin") {
withTestDB { implicit session =>
withTestRepository { git =>
val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(false) }
val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE)
val user1 = generateNewUserWithDBRepository("user1", "repo1")
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == None)
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == None)
enableBranchProtection("user1", "repo1", "branch", true, Seq("must"))
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("Required status check \"must\" is expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == Some("Required status check \"must\" is expected"))
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == None)
enableBranchProtection("user1", "repo1", "branch", true, Seq("must", "must2"))
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("2 of 2 required status checks are expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == Some("2 of 2 required status checks are expected"))
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("2 of 2 required status checks are expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == Some("2 of 2 required status checks are expected"))
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== Some("Required status check \"must2\" is expected")
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == Some("Required status check \"must2\" is expected"))
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
receiveHook.preReceive("user1", "repo1", rp, rc, "user1") must_== None
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == None)
}
}
}
}
"ProtectedBranchInfo" should {
"administrator is owner" in {
describe("ProtectedBranchInfo") {
it("administrator is owner") {
withTestDB { implicit session =>
generateNewUserWithDBRepository("user1", "repo1")
val x = ProtectedBranchInfo("user1", "repo1", true, Nil, false)
x.isAdministrator("user1") must_== true
x.isAdministrator("user2") must_== false
assert(x.isAdministrator("user1") == true)
assert(x.isAdministrator("user2") == false)
}
}
"administrator is manager" in {
it("administrator is manager") {
withTestDB { implicit session =>
val x = ProtectedBranchInfo("grp1", "repo1", true, Nil, false)
x.createGroup("grp1", None)
@@ -137,49 +137,49 @@ class ProtectedBranchServiceSpec extends Specification with ServiceSpecBase with
generateNewAccount("user3")
x.updateGroupMembers("grp1", List("user1"->true, "user2"->false))
x.isAdministrator("user1") must_== true
x.isAdministrator("user2") must_== false
x.isAdministrator("user3") must_== false
assert(x.isAdministrator("user1") == true)
assert(x.isAdministrator("user2") == false)
assert(x.isAdministrator("user3") == false)
}
}
"unSuccessedContexts" in {
it("unSuccessedContexts") {
withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1", "repo1")
val x = ProtectedBranchInfo("user1", "repo1", true, List("must"), false)
x.unSuccessedContexts(sha) must_== Set("must")
assert(x.unSuccessedContexts(sha) == Set("must"))
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
x.unSuccessedContexts(sha) must_== Set("must")
assert(x.unSuccessedContexts(sha) == Set("must"))
createCommitStatus("user1", "repo1", sha, "must", CommitState.ERROR, None, None, now, user1)
x.unSuccessedContexts(sha) must_== Set("must")
assert(x.unSuccessedContexts(sha) == Set("must"))
createCommitStatus("user1", "repo1", sha, "must", CommitState.PENDING, None, None, now, user1)
x.unSuccessedContexts(sha) must_== Set("must")
assert(x.unSuccessedContexts(sha) == Set("must"))
createCommitStatus("user1", "repo1", sha, "must", CommitState.FAILURE, None, None, now, user1)
x.unSuccessedContexts(sha) must_== Set("must")
assert(x.unSuccessedContexts(sha) == Set("must"))
createCommitStatus("user1", "repo1", sha, "must", CommitState.SUCCESS, None, None, now, user1)
x.unSuccessedContexts(sha) must_== Set()
assert(x.unSuccessedContexts(sha) == Set())
}
}
"unSuccessedContexts when empty" in {
it("unSuccessedContexts when empty") {
withTestDB { implicit session =>
val user1 = generateNewUserWithDBRepository("user1", "repo1")
val x = ProtectedBranchInfo("user1", "repo1", true, Nil, false)
val sha = "0c77148632618b59b6f70004e3084002be2b8804"
x.unSuccessedContexts(sha) must_== Nil
assert(x.unSuccessedContexts(sha) == Set())
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
x.unSuccessedContexts(sha) must_== Nil
assert(x.unSuccessedContexts(sha) == Set())
}
}
"if disabled, needStatusCheck is false" in {
it("if disabled, needStatusCheck is false") {
withTestDB { implicit session =>
ProtectedBranchInfo("user1", "repo1", false, Seq("must"), true).needStatusCheck("user1") must_== false
assert(ProtectedBranchInfo("user1", "repo1", false, Seq("must"), true).needStatusCheck("user1") == false)
}
}
"needStatusCheck includeAdministrators" in {
it("needStatusCheck includeAdministrators") {
withTestDB { implicit session =>
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), false).needStatusCheck("user2") must_== true
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), false).needStatusCheck("user1") must_== false
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), true ).needStatusCheck("user2") must_== true
ProtectedBranchInfo("user1", "repo1", true, Seq("must"), true ).needStatusCheck("user1") must_== true
assert(ProtectedBranchInfo("user1", "repo1", true, Seq("must"), false).needStatusCheck("user2") == true)
assert(ProtectedBranchInfo("user1", "repo1", true, Seq("must"), false).needStatusCheck("user1") == false)
assert(ProtectedBranchInfo("user1", "repo1", true, Seq("must"), true ).needStatusCheck("user2") == true)
assert(ProtectedBranchInfo("user1", "repo1", true, Seq("must"), true ).needStatusCheck("user1") == true)
}
}
}

View File

@@ -1,17 +1,17 @@
package gitbucket.core.service
import gitbucket.core.model._
import gitbucket.core.model.Profile._
import org.scalatest.FunSpec
import org.specs2.mutable.Specification
class PullRequestServiceSpec extends FunSpec with ServiceSpecBase with PullRequestService with IssuesService {
class PullRequestServiceSpec extends Specification with ServiceSpecBase with PullRequestService with IssuesService {
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)
"PullRequestService.getPullRequestFromBranch" should {
"""
describe("PullRequestService.getPullRequestFromBranch") {
it("""should
|return pull request if exists pull request from `branch` to `defaultBranch` and not closed.
|return pull request if exists pull request from `branch` to othre branch and not closed.
|return None if all pull request is closed""".stripMargin.trim in { withTestDB { implicit se =>
|return None if all pull request is closed""".stripMargin.trim) { withTestDB { implicit se =>
generateNewUserWithDBRepository("user1", "repo1")
generateNewUserWithDBRepository("user1", "repo2")
generateNewUserWithDBRepository("user2", "repo1")
@@ -22,12 +22,12 @@ class PullRequestServiceSpec extends Specification with ServiceSpecBase with Pul
val r1 = swap(generateNewPullRequest("user1/repo1/master2", "user1/repo1/head1"))
val r2 = swap(generateNewPullRequest("user1/repo1/master", "user1/repo1/head1"))
val r3 = swap(generateNewPullRequest("user1/repo1/master4", "user1/repo1/head1"))
getPullRequestFromBranch("user1", "repo1", "head1", "master") must_== Some(r2)
assert(getPullRequestFromBranch("user1", "repo1", "head1", "master") == Some(r2))
updateClosed("user1", "repo1", r2._1.issueId, true)
getPullRequestFromBranch("user1", "repo1", "head1", "master").get must beOneOf(r1, r2)
assert(Seq(r1, r2).contains(getPullRequestFromBranch("user1", "repo1", "head1", "master").get))
updateClosed("user1", "repo1", r1._1.issueId, true)
updateClosed("user1", "repo1", r3._1.issueId, true)
getPullRequestFromBranch("user1", "repo1", "head1", "master") must beNone
assert(getPullRequestFromBranch("user1", "repo1", "head1", "master") == None)
} }
}
}

View File

@@ -1,39 +1,33 @@
package gitbucket.core.service
import gitbucket.core.model._
import org.specs2.mutable.Specification
import org.scalatest.FunSuite
class RepositoryServiceSpec extends FunSuite with ServiceSpecBase with RepositoryService with AccountService{
test("renameRepository can rename CommitState, ProtectedBranches") { withTestDB { implicit session =>
val tester = generateNewAccount("tester")
createRepository("repo", "root", None, false)
val service = new CommitStatusService with ProtectedBranchService {}
val id = service.createCommitStatus(
userName = "root",
repositoryName = "repo",
sha = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7",
context = "jenkins/test",
state = CommitState.PENDING,
targetUrl = Some("http://example.com/target"),
description = Some("description"),
creator = tester,
now = new java.util.Date)
class RepositoryServiceSpec extends Specification with ServiceSpecBase with RepositoryService with AccountService{
"RepositoryService" should {
"renameRepository can rename CommitState, ProtectedBranches" in { withTestDB { implicit session =>
val tester = generateNewAccount("tester")
createRepository("repo","root",None,false)
val service = new CommitStatusService with ProtectedBranchService {}
val id = service.createCommitStatus(
userName = "root",
repositoryName = "repo",
sha = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7",
context = "jenkins/test",
state = CommitState.PENDING,
targetUrl = Some("http://example.com/target"),
description = Some("description"),
creator = tester,
now = new java.util.Date)
service.enableBranchProtection("root", "repo", "branch", true, Seq("must1", "must2"))
var orgPbi = service.getProtectedBranchInfo("root", "repo", "branch")
val org = service.getCommitStatus("root","repo", id).get
service.enableBranchProtection("root", "repo", "branch", true, Seq("must1", "must2"))
renameRepository("root","repo","tester","repo2")
val orgPbi = service.getProtectedBranchInfo("root", "repo", "branch")
val org = service.getCommitStatus("root","repo", id).get
val neo = service.getCommitStatus("tester","repo2", org.commitId, org.context).get
neo must_==
org.copy(
commitStatusId=neo.commitStatusId,
repositoryName="repo2",
userName="tester")
service.getProtectedBranchInfo("tester", "repo2", "branch") must_==
orgPbi.copy(owner="tester", repository="repo2")
}}
}
renameRepository("root","repo","tester","repo2")
val neo = service.getCommitStatus("tester","repo2", org.commitId, org.context).get
assert(neo == org.copy(commitStatusId = neo.commitStatusId, repositoryName = "repo2", userName = "tester"))
assert(service.getProtectedBranchInfo("tester", "repo2", "branch") == orgPbi.copy(owner = "tester", repository = "repo2"))
}}
}

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