mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 14:35:52 +01:00
Merge branch 'master'
Conflicts: project/build.scala
This commit is contained in:
160
build.sbt
Normal file
160
build.sbt
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
val Organization = "gitbucket"
|
||||||
|
val Name = "gitbucket"
|
||||||
|
val GitBucketVersion = "3.11.0-SNAPSHOT"
|
||||||
|
val ScalatraVersion = "2.4.0"
|
||||||
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
|
|
||||||
|
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
||||||
|
|
||||||
|
sourcesInBase := false
|
||||||
|
organization := Organization
|
||||||
|
name := Name
|
||||||
|
version := GitBucketVersion
|
||||||
|
scalaVersion := "2.11.6"
|
||||||
|
|
||||||
|
// dependency settings
|
||||||
|
resolvers ++= Seq(
|
||||||
|
Classpaths.typesafeReleases,
|
||||||
|
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
|
||||||
|
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||||
|
)
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
|
||||||
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r",
|
||||||
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
|
"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",
|
||||||
|
"org.apache.commons" % "commons-compress" % "1.10",
|
||||||
|
"org.apache.commons" % "commons-email" % "1.4",
|
||||||
|
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
||||||
|
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
||||||
|
"org.apache.tika" % "tika-core" % "1.11",
|
||||||
|
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||||
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
|
"com.h2database" % "h2" % "1.4.190",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
||||||
|
"com.mchange" % "c3p0" % "0.9.5.2",
|
||||||
|
"com.typesafe" % "config" % "1.3.0",
|
||||||
|
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Twirl settings
|
||||||
|
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
|
||||||
|
|
||||||
|
// Compiler settings
|
||||||
|
scalacOptions := Seq("-deprecation", "-language:postfixOps")
|
||||||
|
javacOptions in compile ++= Seq("-target", "7", "-source", "7")
|
||||||
|
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"
|
||||||
|
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() )
|
||||||
|
fork in Test := true
|
||||||
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
|
|
||||||
|
// Assembly settings
|
||||||
|
test in assembly := {}
|
||||||
|
assemblyMergeStrategy in assembly := {
|
||||||
|
case PathList("META-INF", xs @ _*) =>
|
||||||
|
(xs map {_.toLowerCase}) match {
|
||||||
|
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||||
|
case _ => MergeStrategy.discard
|
||||||
|
}
|
||||||
|
case x => MergeStrategy.first
|
||||||
|
}
|
||||||
|
|
||||||
|
// JRebel
|
||||||
|
jrebel.webLinks += (target in webappPrepare).value
|
||||||
|
jrebel.enabled := System.getenv().get("JREBEL") != null
|
||||||
|
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
||||||
|
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||||
|
}
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 log = streams.value.log
|
||||||
|
log info s"building executable webapp in ${workDir}"
|
||||||
|
|
||||||
|
// initialize temp directory
|
||||||
|
val temp = workDir / "webapp"
|
||||||
|
IO delete temp
|
||||||
|
|
||||||
|
// include jetty classes
|
||||||
|
val jettyJars = Keys.update.value select configurationFilter(name = executableConfig.name)
|
||||||
|
jettyJars foreach { jar =>
|
||||||
|
IO unzip (jar, temp, (name:String) =>
|
||||||
|
(name startsWith "javax/") ||
|
||||||
|
(name startsWith "org/")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// include original war file
|
||||||
|
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" */)
|
||||||
|
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
|
||||||
|
IO jar (contentMappings, outputFile, manifest)
|
||||||
|
|
||||||
|
// generate checksums
|
||||||
|
Seq("md5", "sha1") foreach { algorithm =>
|
||||||
|
IO.write(
|
||||||
|
workDir / (warName + "." + algorithm),
|
||||||
|
ChecksumHelper computeAsString (outputFile, algorithm)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// done
|
||||||
|
log info s"built executable webapp ${outputFile}"
|
||||||
|
outputFile
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Keys.artifact in (Compile, executableKey) ~= {
|
||||||
|
_ copy (`type` = "war", extension = "war"))
|
||||||
|
}
|
||||||
|
addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
|
||||||
|
*/
|
||||||
@@ -37,11 +37,10 @@ Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https
|
|||||||
|
|
||||||
### Make release war file
|
### Make release war file
|
||||||
|
|
||||||
Run `release/make-release-war.sh`. The release war file is generated into `target/scala-2.11/gitbucket.war`.
|
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cd release
|
$sbt executable
|
||||||
$ ./make-release-war.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy assembly jar file
|
### Deploy assembly jar file
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
version=$1
|
|
||||||
output_dir=`dirname $0`
|
|
||||||
git rm -f ${output_dir}/jetty-*.jar
|
|
||||||
for name in 'io' 'servlet' 'xml' 'continuation' 'security' 'util' 'http' 'server' 'webapp'
|
|
||||||
do
|
|
||||||
jar_filename="jetty-${name}-${version}.jar"
|
|
||||||
wget "http://repo1.maven.org/maven2/org/eclipse/jetty/jetty-${name}/${version}/${jar_filename}" -O ${output_dir}/${jar_filename}
|
|
||||||
done
|
|
||||||
git add ${output_dir}/*.jar
|
|
||||||
git commit
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import com.earldouglas.xwp.JettyPlugin
|
|
||||||
import play.twirl.sbt.SbtTwirl
|
|
||||||
import sbt.Keys._
|
|
||||||
import sbt._
|
|
||||||
import sbtassembly.AssemblyKeys._
|
|
||||||
import sbtassembly._
|
|
||||||
import JettyPlugin.autoImport._
|
|
||||||
import fi.gekkio.sbtplugins.jrebel.JRebelPlugin._
|
|
||||||
import com.earldouglas.xwp.WebappPlugin.autoImport.webappPrepare
|
|
||||||
|
|
||||||
object MyBuild extends Build {
|
|
||||||
val Organization = "gitbucket"
|
|
||||||
val Name = "gitbucket"
|
|
||||||
val Version = "3.11.0-SNAPSHOT"
|
|
||||||
val ScalaVersion = "2.11.6"
|
|
||||||
val ScalatraVersion = "2.4.0"
|
|
||||||
|
|
||||||
lazy val project = Project (
|
|
||||||
"gitbucket",
|
|
||||||
file(".")
|
|
||||||
)
|
|
||||||
// .settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
|
||||||
.settings(
|
|
||||||
test in assembly := {},
|
|
||||||
assemblyMergeStrategy in assembly := {
|
|
||||||
case PathList("META-INF", xs @ _*) =>
|
|
||||||
(xs map {_.toLowerCase}) match {
|
|
||||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
|
||||||
case _ => MergeStrategy.discard
|
|
||||||
}
|
|
||||||
case x => MergeStrategy.first
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.settings(
|
|
||||||
sourcesInBase := false,
|
|
||||||
organization := Organization,
|
|
||||||
name := Name,
|
|
||||||
version := Version,
|
|
||||||
scalaVersion := ScalaVersion,
|
|
||||||
resolvers ++= Seq(
|
|
||||||
Classpaths.typesafeReleases,
|
|
||||||
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
|
|
||||||
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
|
||||||
),
|
|
||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
|
||||||
libraryDependencies ++= Seq(
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
|
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r",
|
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
|
||||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
|
||||||
"org.specs2" %% "specs2-junit" % "3.6.6" % "test",
|
|
||||||
"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-SNAPSHOT",
|
|
||||||
"io.github.gitbucket" % "solidbase" % "1.0.0-SNAPSHOT",
|
|
||||||
"org.apache.commons" % "commons-compress" % "1.10",
|
|
||||||
"org.apache.commons" % "commons-email" % "1.4",
|
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
|
||||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
|
||||||
"org.apache.tika" % "tika-core" % "1.11",
|
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
|
||||||
"com.h2database" % "h2" % "1.4.190",
|
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % "9.3.6.v20151106" % "provided",
|
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
|
||||||
"junit" % "junit" % "4.12" % "test",
|
|
||||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
|
||||||
"com.typesafe" % "config" % "1.3.0",
|
|
||||||
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
|
||||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0")
|
|
||||||
),
|
|
||||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
|
||||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
|
||||||
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",
|
|
||||||
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() ),
|
|
||||||
fork in Test := true,
|
|
||||||
packageOptions += Package.MainClass("JettyLauncher")
|
|
||||||
).settings(jrebelSettings: _*).settings(
|
|
||||||
jrebel.webLinks += (target in webappPrepare).value,
|
|
||||||
jrebel.enabled := System.getenv().get("JREBEL") != null,
|
|
||||||
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap(path =>
|
|
||||||
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}"))
|
|
||||||
).enablePlugins(SbtTwirl, JettyPlugin)
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<project name="gitbucket" default="all" basedir="..">
|
|
||||||
|
|
||||||
<property environment="env"/>
|
|
||||||
<property name="target.dir" value="target"/>
|
|
||||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
|
||||||
<property name="jetty.dir" value="embed-jetty"/>
|
|
||||||
<property name="scala.version" value="2.11"/>
|
|
||||||
<property name="gitbucket.version" value="${env.GITBUCKET_VERSION}"/>
|
|
||||||
<property name="jetty.version" value="9.3.6.v20151106"/>
|
|
||||||
<property name="servlet.version" value="3.1.0"/>
|
|
||||||
|
|
||||||
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
|
|
||||||
<os family="windows" />
|
|
||||||
</condition>
|
|
||||||
|
|
||||||
<target name="clean">
|
|
||||||
<delete dir="${embed.classes.dir}"/>
|
|
||||||
<delete file="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="war" depends="clean">
|
|
||||||
<exec executable="${sbt.exec}" resolveexecutable="true" failonerror="true">
|
|
||||||
<arg line="clean compile test package" />
|
|
||||||
</exec>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="embed" depends="war">
|
|
||||||
<mkdir dir="${embed.classes.dir}"/>
|
|
||||||
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/javax.servlet-api-${servlet.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-continuation-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-http-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-io-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-security-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-server-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-servlet-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-util-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-webapp-${jetty.version}.jar" />
|
|
||||||
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-xml-${jetty.version}.jar" />
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${embed.classes.dir}"
|
|
||||||
update = "true"
|
|
||||||
includes="javax/**,org/**"/>
|
|
||||||
|
|
||||||
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
basedir="${target.dir}/scala-${scala.version}/classes"
|
|
||||||
update = "true"
|
|
||||||
includes="JettyLauncher.class,HttpsSupportConnector.class"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="rename" depends="embed">
|
|
||||||
<move file="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
|
|
||||||
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="checksum" depends="rename">
|
|
||||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="MD5" format="MD5SUM" forceOverwrite="yes" fileext=".md5"/>
|
|
||||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="SHA" format="MD5SUM" forceOverwrite="yes" fileext=".sha1"/>
|
|
||||||
</target>
|
|
||||||
|
|
||||||
<target name="all" depends="checksum">
|
|
||||||
</target>
|
|
||||||
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
D="$(dirname "$0")"
|
|
||||||
D="$(cd "${D}"; pwd)"
|
|
||||||
DD="$(dirname "${D}")"
|
|
||||||
(
|
|
||||||
for f in "${D}/env.sh" "${D}/build.xml"; do
|
|
||||||
if [ ! -s "${f}" ]; then
|
|
||||||
echo >&2 "$0: Unable to access file '${f}'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
. "${D}/env.sh"
|
|
||||||
cd "${DD}"
|
|
||||||
ant -f "${D}/build.xml" all
|
|
||||||
)
|
|
||||||
21
src/main/scala/gitbucket/core/api/ApiLabel.scala
Normal file
21
src/main/scala/gitbucket/core/api/ApiLabel.scala
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.Label
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/labels/
|
||||||
|
*/
|
||||||
|
case class ApiLabel(
|
||||||
|
name: String,
|
||||||
|
color: String)(repositoryName: RepositoryName){
|
||||||
|
var url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/labels/${name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiLabel{
|
||||||
|
def apply(label:Label, repositoryName: RepositoryName): ApiLabel =
|
||||||
|
ApiLabel(
|
||||||
|
name = label.labelName,
|
||||||
|
color = label.color
|
||||||
|
)(repositoryName)
|
||||||
|
}
|
||||||
18
src/main/scala/gitbucket/core/api/CreateALabel.scala
Normal file
18
src/main/scala/gitbucket/core/api/CreateALabel.scala
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
* api form
|
||||||
|
*/
|
||||||
|
case class CreateALabel(
|
||||||
|
name: String,
|
||||||
|
color: String
|
||||||
|
) {
|
||||||
|
def isValid: Boolean = {
|
||||||
|
name.length<=100 &&
|
||||||
|
!name.startsWith("_") &&
|
||||||
|
!name.startsWith("-") &&
|
||||||
|
color.length==6 &&
|
||||||
|
color.matches("[a-fA-F0-9+_.]+")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ object JsonFormat {
|
|||||||
FieldSerializer[ApiPullRequest.Commit]() +
|
FieldSerializer[ApiPullRequest.Commit]() +
|
||||||
FieldSerializer[ApiIssue]() +
|
FieldSerializer[ApiIssue]() +
|
||||||
FieldSerializer[ApiComment]() +
|
FieldSerializer[ApiComment]() +
|
||||||
|
FieldSerializer[ApiLabel]() +
|
||||||
ApiBranchProtection.enforcementLevelSerializer
|
ApiBranchProtection.enforcementLevelSerializer
|
||||||
|
|
||||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import gitbucket.core.api.{ApiError, CreateALabel, ApiLabel, JsonFormat}
|
||||||
import gitbucket.core.issues.labels.html
|
import gitbucket.core.issues.labels.html
|
||||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
|
||||||
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
import gitbucket.core.util.{LockUtil, RepositoryName, ReferrerAuthenticator, CollaboratorsAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import io.github.gitbucket.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.scalatra.Ok
|
import org.scalatra.{NoContent, UnprocessableEntity, Created, Ok}
|
||||||
|
|
||||||
class LabelsController extends LabelsControllerBase
|
class LabelsController extends LabelsControllerBase
|
||||||
with LabelsService with IssuesService with RepositoryService with AccountService
|
with LabelsService with IssuesService with RepositoryService with AccountService
|
||||||
@@ -19,7 +20,7 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
case class LabelForm(labelName: String, color: String)
|
case class LabelForm(labelName: String, color: String)
|
||||||
|
|
||||||
val labelForm = mapping(
|
val labelForm = mapping(
|
||||||
"labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
|
"labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
|
||||||
"labelColor" -> trim(label("Color", text(required, color)))
|
"labelColor" -> trim(label("Color", text(required, color)))
|
||||||
)(LabelForm.apply)
|
)(LabelForm.apply)
|
||||||
|
|
||||||
@@ -31,6 +32,26 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all labels for this repository
|
||||||
|
* https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels")(referrersOnly { repository =>
|
||||||
|
JsonFormat(getLabels(repository.owner, repository.name).map { label =>
|
||||||
|
ApiLabel(label, RepositoryName(repository))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#get-a-single-label
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repository/labels/:labelName")(referrersOnly { repository =>
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
JsonFormat(ApiLabel(label, RepositoryName(repository)))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
||||||
html.edit(None, repository)
|
html.edit(None, repository)
|
||||||
})
|
})
|
||||||
@@ -45,6 +66,31 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#create-a-label
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
|
||||||
|
(for{
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
val labelId = createLabel(repository.owner, repository.name, data.name, data.color)
|
||||||
|
getLabel(repository.owner, repository.name, labelId).map { label =>
|
||||||
|
Created(JsonFormat(ApiLabel(label, RepositoryName(repository))))
|
||||||
|
} getOrElse NotFound()
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
||||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||||
html.edit(Some(label), repository)
|
html.edit(Some(label), repository)
|
||||||
@@ -61,11 +107,50 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#update-a-label
|
||||||
|
*/
|
||||||
|
patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||||
|
(for{
|
||||||
|
data <- extractFromJsonBody[CreateALabel] if data.isValid
|
||||||
|
} yield {
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
|
||||||
|
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
|
||||||
|
JsonFormat(ApiLabel(
|
||||||
|
getLabel(repository.owner, repository.name, label.labelId).get,
|
||||||
|
RepositoryName(repository)))
|
||||||
|
} else {
|
||||||
|
// TODO ApiError should support errors field to enhance compatibility of GitHub API
|
||||||
|
UnprocessableEntity(ApiError(
|
||||||
|
"Validation Failed",
|
||||||
|
Some("https://developer.github.com/v3/issues/labels/#create-a-label")))
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
}) getOrElse NotFound()
|
||||||
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
|
||||||
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
|
||||||
Ok()
|
Ok()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a label
|
||||||
|
* https://developer.github.com/v3/issues/labels/#delete-a-label
|
||||||
|
*/
|
||||||
|
delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
|
||||||
|
LockUtil.lock(RepositoryName(repository).fullName) {
|
||||||
|
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
|
||||||
|
deleteLabel(repository.owner, repository.name, label.labelId)
|
||||||
|
NoContent()
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constraint for the identifier such as user name, repository name or page name.
|
* Constraint for the identifier such as user name, repository name or page name.
|
||||||
*/
|
*/
|
||||||
@@ -80,4 +165,12 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def uniqueLabelName: Constraint = new Constraint(){
|
||||||
|
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = {
|
||||||
|
val owner = params("owner")
|
||||||
|
val repository = params("repository")
|
||||||
|
getLabel(owner, repository, value).map(_ => "Name has already been taken.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,16 @@ protected[model] trait TemplateComponent { self: Profile =>
|
|||||||
|
|
||||||
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
|
||||||
val labelId = column[Int]("LABEL_ID")
|
val labelId = column[Int]("LABEL_ID")
|
||||||
|
val labelName = column[String]("LABEL_NAME")
|
||||||
|
|
||||||
def byLabel(owner: String, repository: String, labelId: Int) =
|
def byLabel(owner: String, repository: String, labelId: Int) =
|
||||||
byRepository(owner, repository) && (this.labelId === labelId.bind)
|
byRepository(owner, repository) && (this.labelId === labelId.bind)
|
||||||
|
|
||||||
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
def byLabel(userName: Column[String], repositoryName: Column[String], labelId: Column[Int]) =
|
||||||
byRepository(userName, repositoryName) && (this.labelId === labelId)
|
byRepository(userName, repositoryName) && (this.labelId === labelId)
|
||||||
|
|
||||||
|
def byLabel(owner: String, repository: String, labelName: String) =
|
||||||
|
byRepository(userName, repositoryName) && (this.labelName === labelName.bind)
|
||||||
}
|
}
|
||||||
|
|
||||||
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
|
|||||||
|
|
||||||
class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate {
|
class Labels(tag: Tag) extends Table[Label](tag, "LABEL") with LabelTemplate {
|
||||||
override val labelId = column[Int]("LABEL_ID", O AutoInc)
|
override val labelId = column[Int]("LABEL_ID", O AutoInc)
|
||||||
val labelName = column[String]("LABEL_NAME")
|
override val labelName = column[String]("LABEL_NAME")
|
||||||
val color = column[String]("COLOR")
|
val color = column[String]("COLOR")
|
||||||
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)
|
def * = (userName, repositoryName, labelId, labelName, color) <> (Label.tupled, Label.unapply)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ trait LabelsService {
|
|||||||
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
|
def getLabel(owner: String, repository: String, labelId: Int)(implicit s: Session): Option[Label] =
|
||||||
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
Labels.filter(_.byPrimaryKey(owner, repository, labelId)).firstOption
|
||||||
|
|
||||||
|
def getLabel(owner: String, repository: String, labelName: String)(implicit s: Session): Option[Label] =
|
||||||
|
Labels.filter(_.byLabel(owner, repository, labelName)).firstOption
|
||||||
|
|
||||||
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int =
|
def createLabel(owner: String, repository: String, labelName: String, color: String)(implicit s: Session): Int =
|
||||||
Labels returning Labels.map(_.labelId) += Label(
|
Labels returning Labels.map(_.labelId) += Label(
|
||||||
userName = owner,
|
userName = owner,
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ object GitCommand {
|
|||||||
abstract class GitCommand() extends Command {
|
abstract class GitCommand() extends Command {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||||
protected var err: OutputStream = null
|
@volatile protected var err: OutputStream = null
|
||||||
protected var in: InputStream = null
|
@volatile protected var in: InputStream = null
|
||||||
protected var out: OutputStream = null
|
@volatile protected var out: OutputStream = null
|
||||||
protected var callback: ExitCallback = null
|
@volatile protected var callback: ExitCallback = null
|
||||||
|
|
||||||
protected def runTask(user: String)(implicit session: Session): Unit
|
protected def runTask(user: String)(implicit session: Session): Unit
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ object Markdown {
|
|||||||
|
|
||||||
if(enableAnchor){
|
if(enableAnchor){
|
||||||
out.append(" class=\"markdown-head\">")
|
out.append(" class=\"markdown-head\">")
|
||||||
out.append("<a class=\"markdown-anchor-link\" href=\"#" + id + "\"></a>")
|
out.append("<a class=\"markdown-anchor-link\" href=\"#" + id + "\"><span class=\"octicon octicon-link\"></span></a>")
|
||||||
out.append("<a class=\"markdown-anchor\" name=\"" + id + "\"></a>")
|
out.append("<a class=\"markdown-anchor\" name=\"" + id + "\"></a>")
|
||||||
} else {
|
} else {
|
||||||
out.append(">")
|
out.append(">")
|
||||||
|
|||||||
@@ -75,8 +75,7 @@
|
|||||||
<div class="pull-right" style="margin-top: 6px;">
|
<div class="pull-right" style="margin-top: 6px;">
|
||||||
<div class="btn-group" style="margin-right: 8px;">
|
<div class="btn-group" style="margin-right: 8px;">
|
||||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#">
|
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#">
|
||||||
<i class="octicon octicon-plus" style="color: black; font-size: 20px; vertical-align: middle;height:20px !important;"></i>
|
<i class="octicon octicon-plus" style="color: black; font-size: 20px; vertical-align: middle;height:20px !important;"></i><span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||||
<span class="caret" style="color: black; vertical-align: middle;"></span>
|
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
<li><a href="@path/new">New repository</a></li>
|
<li><a href="@path/new">New repository</a></li>
|
||||||
@@ -84,8 +83,8 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#" data-toggle="tooltip" data-placement="bottom" title="Signed is as @loginAccount.get.userName">@avatar(loginAccount.get.userName, 16)
|
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#" data-toggle="tooltip" data-placement="bottom" title="Signed is as @loginAccount.get.userName">
|
||||||
<span class="caret" style="color: black; vertical-align: middle;"></span>
|
@avatar(loginAccount.get.userName, 16)<span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu pull-right">
|
<ul class="dropdown-menu pull-right">
|
||||||
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
|
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
|
||||||
|
|||||||
@@ -1446,6 +1446,10 @@ div.wiki-sidebar {
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.wiki-sidebar img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
div.wiki-sidebar-dotted {
|
div.wiki-sidebar-dotted {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 1px dashed #ddd;
|
border: 1px dashed #ddd;
|
||||||
@@ -1878,6 +1882,10 @@ div.markdown-body p {
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.markdown-body img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
div.markdown-body pre {
|
div.markdown-body pre {
|
||||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -2035,39 +2043,16 @@ div.markdown-body table colgroup + tbody tr:first-child td:last-child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.markdown-anchor-link {
|
a.markdown-anchor-link {
|
||||||
position: absolute;
|
margin-left: -16px;
|
||||||
left: -18px;
|
margin-right: 2px;
|
||||||
display: none;
|
line-height: 1;
|
||||||
color: #999;
|
color: #999;
|
||||||
/* From octicon style */
|
cursor: pointer;
|
||||||
font: normal normal normal 16px/1 octicons;
|
|
||||||
text-decoration: none;
|
|
||||||
text-rendering: auto;
|
|
||||||
}
|
|
||||||
a.markdown-anchor-link:before { content: '\f05c'} /* */
|
|
||||||
|
|
||||||
h1 a.markdown-anchor-link {
|
|
||||||
top: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 a.markdown-anchor-link {
|
a.markdown-anchor-link span.octicon {
|
||||||
top: 20px;
|
visibility: hidden;
|
||||||
}
|
vertical-align: middle;
|
||||||
|
|
||||||
h3 a.markdown-anchor-link {
|
|
||||||
top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 a.markdown-anchor-link {
|
|
||||||
top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h5 a.markdown-anchor-link {
|
|
||||||
top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h6 a.markdown-anchor-link {
|
|
||||||
top: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
|
|||||||
@@ -19,14 +19,11 @@ $(function(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
// anchor icon for markdown
|
// anchor icon for markdown
|
||||||
$('.markdown-head').mouseenter(function(e){
|
$('.markdown-head').on('mouseenter', function(e){
|
||||||
$(e.target).children('a.markdown-anchor-link').show();
|
$(this).find('span.octicon').css('visibility', 'visible');
|
||||||
});
|
});
|
||||||
$('.markdown-head').mouseleave(function(e){
|
$('.markdown-head').on('mouseleave', function(e){
|
||||||
$(e.target).children('a.markdown-anchor-link').hide();
|
$(this).find('span.octicon').css('visibility', 'hidden');
|
||||||
});
|
|
||||||
$('a.markdown-anchor-link').mouseleave(function(e){
|
|
||||||
$(e.target).hide();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// syntax highlighting by google-code-prettify
|
// syntax highlighting by google-code-prettify
|
||||||
|
|||||||
@@ -209,6 +209,15 @@ class JsonFormatSpec extends Specification {
|
|||||||
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/$sha1/status"
|
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/commits/$sha1/status"
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
|
val apiLabel = ApiLabel(
|
||||||
|
name = "bug",
|
||||||
|
color = "f29513")(RepositoryName("octocat","Hello-World"))
|
||||||
|
val apiLabelJson = s"""{
|
||||||
|
"name": "bug",
|
||||||
|
"color": "f29513",
|
||||||
|
"url": "${context.baseUrl}/api/v3/repos/octocat/Hello-World/labels/bug"
|
||||||
|
}"""
|
||||||
|
|
||||||
val apiIssue = ApiIssue(
|
val apiIssue = ApiIssue(
|
||||||
number = 1347,
|
number = 1347,
|
||||||
title = "Found a bug",
|
title = "Found a bug",
|
||||||
@@ -411,6 +420,9 @@ class JsonFormatSpec extends Specification {
|
|||||||
"apiCombinedCommitStatus" in {
|
"apiCombinedCommitStatus" in {
|
||||||
JsonFormat(apiCombinedCommitStatus) must beFormatted(apiCombinedCommitStatusJson)
|
JsonFormat(apiCombinedCommitStatus) must beFormatted(apiCombinedCommitStatusJson)
|
||||||
}
|
}
|
||||||
|
"apiLabel" in {
|
||||||
|
JsonFormat(apiLabel) must beFormatted(apiLabelJson)
|
||||||
|
}
|
||||||
"apiIssue" in {
|
"apiIssue" in {
|
||||||
JsonFormat(apiIssue) must beFormatted(apiIssueJson)
|
JsonFormat(apiIssue) must beFormatted(apiIssueJson)
|
||||||
JsonFormat(apiIssuePR) must beFormatted(apiIssuePRJson)
|
JsonFormat(apiIssuePR) must beFormatted(apiIssuePRJson)
|
||||||
|
|||||||
114
src/test/scala/gitbucket/core/service/LabelsServiceSpec.scala
Normal file
114
src/test/scala/gitbucket/core/service/LabelsServiceSpec.scala
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package gitbucket.core.service
|
||||||
|
|
||||||
|
import gitbucket.core.model._
|
||||||
|
|
||||||
|
import org.specs2.mutable.Specification
|
||||||
|
|
||||||
|
class LabelsServiceSpec extends Specification with ServiceSpecBase {
|
||||||
|
"getLabels" should {
|
||||||
|
"be empty when not have any labels" in { withTestDB { implicit session =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
|
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
|
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||||
|
|
||||||
|
dummyService.getLabels("user1", "repo1") must haveSize(0)
|
||||||
|
}}
|
||||||
|
"return contained labels" in { withTestDB { implicit session =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||||
|
val labelId2 = dummyService.createLabel("user1", "repo1", "label2", "ffffff")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
|
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
|
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||||
|
|
||||||
|
def getLabels = dummyService.getLabels("user1", "repo1")
|
||||||
|
|
||||||
|
getLabels must haveSize(2)
|
||||||
|
getLabels must_== 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 =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
|
||||||
|
dummyService.getLabel("user1", "repo1", 1) must beNone
|
||||||
|
dummyService.getLabel("user1", "repo1", "label1") must beNone
|
||||||
|
}}
|
||||||
|
"return a label fetched by label id" in { withTestDB { implicit session =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||||
|
dummyService.createLabel("user1", "repo1", "label2", "ffffff")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
|
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
|
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||||
|
|
||||||
|
def getLabel = dummyService.getLabel("user1", "repo1", labelId1)
|
||||||
|
getLabel must_== Some(Label("user1", "repo1", labelId1, "label1", "000000"))
|
||||||
|
}}
|
||||||
|
"return a label fetched by label name" in { withTestDB { implicit session =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
val labelId1 = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||||
|
dummyService.createLabel("user1", "repo1", "label2", "ffffff")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
|
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||||
|
|
||||||
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
|
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||||
|
|
||||||
|
def getLabel = dummyService.getLabel("user1", "repo1", "label1")
|
||||||
|
getLabel must_== Some(Label("user1", "repo1", labelId1, "label1", "000000"))
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
"createLabel" should {
|
||||||
|
"return accurate label id" in { withTestDB { implicit session =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
|
dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||||
|
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||||
|
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||||
|
val labelId = dummyService.createLabel("user1", "repo1", "label2", "000000")
|
||||||
|
labelId must_== 4
|
||||||
|
def getLabel = dummyService.getLabel("user1", "repo1", labelId)
|
||||||
|
getLabel must_== Some(Label("user1", "repo1", labelId, "label2", "000000"))
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
"updateLabel" should {
|
||||||
|
"change target label" in { withTestDB { implicit session =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
|
val labelId = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||||
|
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||||
|
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"))
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
"deleteLabel" should {
|
||||||
|
"remove target label" in { withTestDB { implicit session =>
|
||||||
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
|
val labelId = dummyService.createLabel("user1", "repo1", "label1", "000000")
|
||||||
|
dummyService.createLabel("user1", "repo2", "label1", "000000")
|
||||||
|
dummyService.createLabel("user2", "repo1", "label1", "000000")
|
||||||
|
dummyService.deleteLabel("user1", "repo1", labelId)
|
||||||
|
dummyService.getLabel("user1", "repo1", labelId) must beNone
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ trait ServiceSpecBase {
|
|||||||
def user(name:String)(implicit s:Session):Account = AccountService.getAccountByUserName(name).get
|
def user(name:String)(implicit s:Session):Account = AccountService.getAccountByUserName(name).get
|
||||||
|
|
||||||
lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService
|
lazy val dummyService = new RepositoryService with AccountService with IssuesService with PullRequestService
|
||||||
with CommitStatusService (){}
|
with CommitStatusService with LabelsService (){}
|
||||||
|
|
||||||
def generateNewUserWithDBRepository(userName:String, repositoryName:String)(implicit s:Session):Account = {
|
def generateNewUserWithDBRepository(userName:String, repositoryName:String)(implicit s:Session):Account = {
|
||||||
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))
|
val ac = AccountService.getAccountByUserName(userName).getOrElse(generateNewAccount(userName))
|
||||||
|
|||||||
Reference in New Issue
Block a user