mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-07 21:17:22 +02:00
Compare commits
151 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7876a60106 | ||
|
|
38e71001cb | ||
|
|
a1307b7464 | ||
|
|
fd413d36ad | ||
|
|
509dfc57ca | ||
|
|
95bdd6228e | ||
|
|
fd1430371a | ||
|
|
437f944c6e | ||
|
|
f64b6e10bb | ||
|
|
5925bd3772 | ||
|
|
fe8d4616db | ||
|
|
99b40974c3 | ||
|
|
c463590ede | ||
|
|
82163eebc2 | ||
|
|
f1e427f926 | ||
|
|
c21dcdca80 | ||
|
|
2ce51472c3 | ||
|
|
514b1aeec1 | ||
|
|
3f34622fe0 | ||
|
|
8c6d5b8178 | ||
|
|
1971c29fd0 | ||
|
|
40ca9b6682 | ||
|
|
81aeed6f6c | ||
|
|
bf50b1bf82 | ||
|
|
1094e8ca2d | ||
|
|
860ccce89b | ||
|
|
59e5993eba | ||
|
|
c08627e5d6 | ||
|
|
3e0bb46699 | ||
|
|
9a972e40ef | ||
|
|
99ad0db1f6 | ||
|
|
12d11bc80c | ||
|
|
dc98079b55 | ||
|
|
88f56126a6 | ||
|
|
313c9976c8 | ||
|
|
5b6a1d0adc | ||
|
|
6db43e6ca7 | ||
|
|
46177e814c | ||
|
|
2fe79baed8 | ||
|
|
02e17c76a7 | ||
|
|
4d8acfd286 | ||
|
|
4a5c287b8f | ||
|
|
0d4047b4ee | ||
|
|
2b730ef180 | ||
|
|
ecbe7228b9 | ||
|
|
e36d0f65d6 | ||
|
|
30818fb797 | ||
|
|
6d7685fcce | ||
|
|
2cec5be3d9 | ||
|
|
038b70ff0b | ||
|
|
23a5f7dcf9 | ||
|
|
baa27d6090 | ||
|
|
f71acfcbe8 | ||
|
|
c65e843491 | ||
|
|
9103c88f0e | ||
|
|
90170f0fcc | ||
|
|
2e3de336cb | ||
|
|
da50d317e7 | ||
|
|
7a91e14b03 | ||
|
|
58eff0a1a3 | ||
|
|
8559f3e354 | ||
|
|
7606097a4d | ||
|
|
dfbace3d26 | ||
|
|
d61ab632f1 | ||
|
|
c3b341d945 | ||
|
|
59f063627c | ||
|
|
b4aba76005 | ||
|
|
81afea350d | ||
|
|
5c5da60dd6 | ||
|
|
89c69cdfc2 | ||
|
|
0789010248 | ||
|
|
37c23f615f | ||
|
|
b4d3573a84 | ||
|
|
5161ece63b | ||
|
|
5b7955cee6 | ||
|
|
60099e2b0d | ||
|
|
f04c486251 | ||
|
|
7b23bbf9ba | ||
|
|
0a532d9774 | ||
|
|
208b98285c | ||
|
|
56c9aa32a4 | ||
|
|
35080a9f33 | ||
|
|
6c41505c91 | ||
|
|
05bfaafe32 | ||
|
|
7534a88607 | ||
|
|
d7817d3d88 | ||
|
|
37780d467d | ||
|
|
ad4af67b30 | ||
|
|
29b0c22b0e | ||
|
|
c400678550 | ||
|
|
3c40e93346 | ||
|
|
247b664654 | ||
|
|
b3db0a6a7b | ||
|
|
0a03e41f1c | ||
|
|
74f3f6bf2e | ||
|
|
a4bf73724d | ||
|
|
8d91253ede | ||
|
|
b7355af49a | ||
|
|
7ec85cbf99 | ||
|
|
a6790b049d | ||
|
|
dce747b1e8 | ||
|
|
c22ee8acfd | ||
|
|
30b8b738b6 | ||
|
|
1accafa8b1 | ||
|
|
11700f4cb4 | ||
|
|
c9de8dd323 | ||
|
|
fd694e38aa | ||
|
|
8822c36b5f | ||
|
|
e614e31162 | ||
|
|
ad47ad4269 | ||
|
|
0c50545cbd | ||
|
|
53cbc36a01 | ||
|
|
85b2053004 | ||
|
|
eba240de65 | ||
|
|
1e5114cd54 | ||
|
|
90cb5de5f0 | ||
|
|
11d33e9389 | ||
|
|
c71e9331ae | ||
|
|
f37b5fa682 | ||
|
|
8cb1ac734d | ||
|
|
05ff2a854c | ||
|
|
d956ade5e3 | ||
|
|
73228506a5 | ||
|
|
2525bbafa8 | ||
|
|
338946dd3a | ||
|
|
2d225641ee | ||
|
|
3c727fe678 | ||
|
|
523ea0d437 | ||
|
|
9eff8f248b | ||
|
|
d50c858a26 | ||
|
|
6f4e94ba9a | ||
|
|
f2750c20a2 | ||
|
|
2da2b426a1 | ||
|
|
5a0bc127b7 | ||
|
|
23a482bbba | ||
|
|
6c2fce1b16 | ||
|
|
c8686f4b34 | ||
|
|
43097b4c1c | ||
|
|
539751a1d9 | ||
|
|
40e36e3f8b | ||
|
|
3d1c9bc9de | ||
|
|
5a5bf34fe0 | ||
|
|
af7043f4bf | ||
|
|
249b27593e | ||
|
|
1201271949 | ||
|
|
19e74a4fe1 | ||
|
|
130aa1e515 | ||
|
|
e92d1eae5a | ||
|
|
8b44a00299 | ||
|
|
f5c1c0703d | ||
|
|
5036b1a1aa |
@@ -1,6 +1,11 @@
|
|||||||
language: scala
|
language: scala
|
||||||
sudo: false
|
sudo: true
|
||||||
script:
|
script:
|
||||||
- sbt test
|
- sbt test
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
before_script:
|
||||||
|
- sudo apt-get install libaio1
|
||||||
|
- sudo /etc/init.d/mysql stop
|
||||||
|
- sudo /etc/init.d/postgresql stop
|
||||||
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -49,6 +49,7 @@ GitBucket has the plug-in system to extend GitBucket from outside of GitBucket.
|
|||||||
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
|
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
|
||||||
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
|
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
|
||||||
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
|
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
|
||||||
|
- [gitbucket-network-plugin](https://github.com/mrkm4ntr/gitbucket-network-plugin)
|
||||||
|
|
||||||
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
|
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
|
||||||
|
|
||||||
@@ -62,13 +63,28 @@ Support
|
|||||||
- First priority of GitBucket is easy installation and API compatibility with GitHub, so we might reject if your request is against it.
|
- First priority of GitBucket is easy installation and API compatibility with GitHub, so we might reject if your request is against it.
|
||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
-------------
|
||||||
|
### 4.2 - 2 Jul 2016
|
||||||
|
|
||||||
|
- New UI based on [AdminLTE](https://github.com/almasaeed2010/AdminLTE)
|
||||||
|
- git gc
|
||||||
|
- Issues and Wiki have been possible to be disabled
|
||||||
|
- SMTP configuration test mail
|
||||||
|
|
||||||
|
### 4.1 - 4 Jun 2016
|
||||||
|
|
||||||
|
- Generic ssh user
|
||||||
|
- Improve branch protection UI
|
||||||
|
- Default value of pull request title
|
||||||
|
|
||||||
### 4.0 - 30 Apr 2016
|
### 4.0 - 30 Apr 2016
|
||||||
|
|
||||||
- MySQL and PostgreSQL support
|
- MySQL and PostgreSQL support
|
||||||
- Data export and import
|
- Data export and import
|
||||||
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
|
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
|
||||||
|
|
||||||
|
**Note:** You can upgrade to GitBucket 4.0 from 3.14. If your GitBucket is 3.13 or before, you have to upgrade 3.14 at first.
|
||||||
|
|
||||||
### 3.14 - 30 Apr 2016
|
### 3.14 - 30 Apr 2016
|
||||||
|
|
||||||
- File attachment and search for wiki pages
|
- File attachment and search for wiki pages
|
||||||
|
|||||||
44
build.sbt
44
build.sbt
@@ -1,8 +1,8 @@
|
|||||||
val Organization = "gitbucket"
|
val Organization = "gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "3.14.0"
|
val GitBucketVersion = "4.2.0"
|
||||||
val ScalatraVersion = "2.4.0"
|
val ScalatraVersion = "2.4.1"
|
||||||
val JettyVersion = "9.3.6.v20151106"
|
val JettyVersion = "9.3.9.v20160517"
|
||||||
|
|
||||||
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
lazy val root = (project in file(".")).enablePlugins(SbtTwirl, JettyPlugin)
|
||||||
|
|
||||||
@@ -15,37 +15,44 @@ scalaVersion := "2.11.8"
|
|||||||
// dependency settings
|
// dependency settings
|
||||||
resolvers ++= Seq(
|
resolvers ++= Seq(
|
||||||
Classpaths.typesafeReleases,
|
Classpaths.typesafeReleases,
|
||||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
|
"amateras" at "http://amateras.sourceforge.jp/mvn/",
|
||||||
|
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
|
||||||
|
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||||
)
|
)
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0",
|
"org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0",
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.1.201511131810-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.1.2.201602141800-r",
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.1.201511131810-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.1.2.201602141800-r",
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
"org.json4s" %% "json4s-jackson" % "3.3.0",
|
"org.json4s" %% "json4s-jackson" % "3.3.0",
|
||||||
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
|
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
|
||||||
"commons-io" % "commons-io" % "2.4",
|
"commons-io" % "commons-io" % "2.4",
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.8",
|
"io.github.gitbucket" % "solidbase" % "1.0.0",
|
||||||
"org.apache.commons" % "commons-compress" % "1.10",
|
"io.github.gitbucket" % "markedj" % "1.0.9-SNAPSHOT",
|
||||||
|
"org.apache.commons" % "commons-compress" % "1.11",
|
||||||
"org.apache.commons" % "commons-email" % "1.4",
|
"org.apache.commons" % "commons-email" % "1.4",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
|
||||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
||||||
"org.apache.tika" % "tika-core" % "1.11",
|
"org.apache.tika" % "tika-core" % "1.13",
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.190",
|
"com.h2database" % "h2" % "1.4.192",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
"mysql" % "mysql-connector-java" % "5.1.39",
|
||||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
"org.postgresql" % "postgresql" % "9.4.1208",
|
||||||
|
"ch.qos.logback" % "logback-classic" % "1.1.7",
|
||||||
|
"com.zaxxer" % "HikariCP" % "2.4.6",
|
||||||
"com.typesafe" % "config" % "1.3.0",
|
"com.typesafe" % "config" % "1.3.0",
|
||||||
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
"com.typesafe.akka" %% "akka-actor" % "2.3.15",
|
||||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
|
"com.enragedginger" %% "akka-quartz-scheduler" % "1.4.0-akka-2.3.x" exclude("c3p0","c3p0"),
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
"junit" % "junit" % "4.12" % "test",
|
"junit" % "junit" % "4.12" % "test",
|
||||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||||
"org.scalaz" %% "scalaz-core" % "7.2.0" % "test"
|
"org.scalaz" %% "scalaz-core" % "7.2.4" % "test",
|
||||||
|
"com.wix" % "wix-embedded-mysql" % "1.0.3" % "test",
|
||||||
|
"ru.yandex.qatools.embed" % "postgresql-embedded" % "1.14" % "test"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Twirl settings
|
// Twirl settings
|
||||||
@@ -55,10 +62,14 @@ play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
|
|||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-Ybackend:GenBCode", "-Ydelambdafy:method", "-target:jvm-1.8")
|
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-Ybackend:GenBCode", "-Ydelambdafy:method", "-target:jvm-1.8")
|
||||||
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
||||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console")
|
|
||||||
|
// Test settings
|
||||||
|
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
||||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
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() )
|
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() )
|
||||||
fork in Test := true
|
fork in Test := true
|
||||||
|
|
||||||
|
// Packaging options
|
||||||
packageOptions += Package.MainClass("JettyLauncher")
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
|
|
||||||
// Assembly settings
|
// Assembly settings
|
||||||
@@ -73,12 +84,13 @@ assemblyMergeStrategy in assembly := {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// JRebel
|
// JRebel
|
||||||
|
Seq(jrebelSettings: _*)
|
||||||
|
|
||||||
jrebel.webLinks += (target in webappPrepare).value
|
jrebel.webLinks += (target in webappPrepare).value
|
||||||
jrebel.enabled := System.getenv().get("JREBEL") != null
|
jrebel.enabled := System.getenv().get("JREBEL") != null
|
||||||
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
javaOptions in Jetty ++= Option(System.getenv().get("JREBEL")).toSeq.flatMap { path =>
|
||||||
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
Seq("-noverify", "-XX:+UseConcMarkSweepGC", "-XX:+CMSClassUnloadingEnabled", s"-javaagent:${path}")
|
||||||
}
|
}
|
||||||
jrebelSettings
|
|
||||||
|
|
||||||
// Create executable war file
|
// Create executable war file
|
||||||
val executableConfig = config("executable").hide
|
val executableConfig = config("executable").hide
|
||||||
|
|||||||
@@ -1,37 +1,54 @@
|
|||||||
Automatic Schema Updating
|
Automatic Schema Updating
|
||||||
========
|
========
|
||||||
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
|
GitBucket updates database schema automatically using [Solidbase](https://github.com/gitbucket/solidbase) in the first run after the upgrading.
|
||||||
|
|
||||||
To release a new version of GitBucket, add the version definition to the [gitbucket.core.servlet.AutoUpdate](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala) at first.
|
To release a new version of GitBucket, add the version definition to the [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object AutoUpdate {
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
...
|
new Version("4.0.0",
|
||||||
/**
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
new Version("4.1.0"),
|
||||||
Version(1, 0)
|
new Version("4.2.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
|
||||||
)
|
)
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, add a SQL file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) as ```MAJOR_MINOR.sql```.
|
|
||||||
|
|
||||||
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
|
|
||||||
|
|
||||||
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
|
||||||
|
|
||||||
```scala
|
|
||||||
val versions = Seq(
|
|
||||||
new Version(1, 3){
|
|
||||||
override def update(conn: Connection): Unit = {
|
|
||||||
super.update(conn)
|
|
||||||
// Add any code here!
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Version(1, 2),
|
|
||||||
Version(1, 1),
|
|
||||||
Version(1, 0)
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filenane defined in `GitBucketCoreModule`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="ENABLE_WIKI" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="ENABLE_ISSUES" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="EXTERNAL_WIKI_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="EXTERNAL_ISSUES_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
```
|
||||||
|
|
||||||
|
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version differs from the actual version, it executes differences between the stored version and the actual version.
|
||||||
|
|
||||||
|
We can add the SQL file instead of the XML file using `SqlMigration`. It try to load a SQL file from classpath as following order:
|
||||||
|
|
||||||
|
1. Specified path (if specified)
|
||||||
|
2. `${moduleId}_${version}_${database}.sql`
|
||||||
|
3. `${moduleId}_${version}.sql`
|
||||||
|
|
||||||
|
Also we can add any code by extending `Migration`:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0", new Migration(){
|
||||||
|
override def migrate(moduleId: String, version: String, context: java.util.Map[String, String]): Unit = {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
See more details [README of Solidbase](https://github.com/gitbucket/solidbase).
|
||||||
|
|||||||
@@ -11,22 +11,24 @@ Note to update version number in files below:
|
|||||||
```scala
|
```scala
|
||||||
val Organization = "gitbucket"
|
val Organization = "gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "3.12.0" // <---- update version!!
|
val GitBucketVersion = "4.0.0" // <---- update version!!
|
||||||
val ScalatraVersion = "2.4.0"
|
val ScalatraVersion = "2.4.0"
|
||||||
val JettyVersion = "9.3.6.v20151106"
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
```
|
```
|
||||||
|
|
||||||
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
|
### src/main/scala/gitbucket/core/GitBucketCoreModule.scala
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object AutoUpdate {
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0",
|
||||||
/**
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
// add new version definition
|
||||||
new Version(3, 12), // <---- add this line!!
|
new Version("4.1.0",
|
||||||
new Version(3, 11),
|
new LiquibaseMigration("update/gitbucket-core_4.1.xml")
|
||||||
|
)
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate release files
|
Generate release files
|
||||||
@@ -39,7 +41,7 @@ Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https
|
|||||||
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$sbt executable
|
$ sbt executable
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy assembly jar file
|
### Deploy assembly jar file
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
sbt.version=0.13.9
|
sbt.version=0.13.11
|
||||||
|
|||||||
2
sbt.bat
2
sbt.bat
@@ -1,2 +1,2 @@
|
|||||||
set SCRIPT_DIR=%~dp0
|
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.9.jar" %*
|
java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -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
2
sbt.sh
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/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.9.jar "$@"
|
java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.9.jar "$@"
|
||||||
|
|||||||
@@ -43,10 +43,9 @@ public class JettyLauncher {
|
|||||||
WebAppContext context = new WebAppContext();
|
WebAppContext context = new WebAppContext();
|
||||||
|
|
||||||
File tmpDir = new File(getGitBucketHome(), "tmp");
|
File tmpDir = new File(getGitBucketHome(), "tmp");
|
||||||
if(tmpDir.exists()){
|
if(!tmpDir.exists()){
|
||||||
deleteDirectory(tmpDir);
|
tmpDir.mkdirs();
|
||||||
}
|
}
|
||||||
tmpDir.mkdirs();
|
|
||||||
context.setTempDirectory(tmpDir);
|
context.setTempDirectory(tmpDir);
|
||||||
|
|
||||||
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||||
|
|||||||
52
src/main/java/org/postgresql/Driver2.java
Normal file
52
src/main/java/org/postgresql/Driver2.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package org.postgresql;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the PostgreSQL JDBC driver to convert the returning column names to lower case.
|
||||||
|
*/
|
||||||
|
public class Driver2 extends Driver {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public java.sql.Connection connect(String url, Properties info) throws SQLException {
|
||||||
|
Connection conn = super.connect(url, info);
|
||||||
|
|
||||||
|
Object proxy = Proxy.newProxyInstance(
|
||||||
|
conn.getClass().getClassLoader(),
|
||||||
|
new Class[]{ Connection.class },
|
||||||
|
new ConnectionProxyHandler(conn)
|
||||||
|
);
|
||||||
|
|
||||||
|
return Connection.class.cast(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class ConnectionProxyHandler implements InvocationHandler {
|
||||||
|
|
||||||
|
private Connection conn;
|
||||||
|
|
||||||
|
public ConnectionProxyHandler(Connection conn){
|
||||||
|
this.conn = conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||||
|
if(method.getName().equals("prepareStatement")){
|
||||||
|
if(args != null && args.length == 2 && args[1].getClass().isArray()){
|
||||||
|
String[] keys = (String[]) args[1];
|
||||||
|
for(int i = 0; i < keys.length; i++){
|
||||||
|
keys[i] = keys[i].toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return method.invoke(conn, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
db {
|
|
||||||
driver = "org.h2.Driver"
|
|
||||||
url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
|
||||||
user = "sa"
|
|
||||||
password = "sa"
|
|
||||||
}
|
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<!--
|
<!--
|
||||||
<logger name="service.WebHookService" level="DEBUG" />
|
<logger name="service.WebHookService" level="DEBUG" />
|
||||||
<logger name="servlet" level="DEBUG" />
|
<logger name="servlet" level="DEBUG" />
|
||||||
-->
|
|
||||||
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||||
|
-->
|
||||||
|
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
c3p0 {
|
|
||||||
privilegeSpawnedThreads=true
|
|
||||||
contextClassLoaderSource=library
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
CREATE TABLE ACCOUNT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MAIL_ADDRESS VARCHAR(100) NOT NULL,
|
|
||||||
PASSWORD VARCHAR(40) NOT NULL,
|
|
||||||
ADMINISTRATOR BOOLEAN NOT NULL,
|
|
||||||
URL VARCHAR(200),
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
LAST_LOGIN_DATE TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE REPOSITORY(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
PRIVATE BOOLEAN NOT NULL,
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
DEFAULT_BRANCH VARCHAR(100),
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE COLLABORATOR(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COLLABORATOR_NAME VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
OPENED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MILESTONE_ID INT,
|
|
||||||
ASSIGNED_USER_NAME VARCHAR(100),
|
|
||||||
TITLE TEXT NOT NULL,
|
|
||||||
CONTENT TEXT,
|
|
||||||
CLOSED BOOLEAN NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_ID(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_COMMENT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
COMMENT_ID INT AUTO_INCREMENT,
|
|
||||||
ACTION VARCHAR(10),
|
|
||||||
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
CONTENT TEXT NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE LABEL(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
LABEL_ID INT AUTO_INCREMENT,
|
|
||||||
LABEL_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COLOR CHAR(6) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE ISSUE_LABEL(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
LABEL_ID INT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE MILESTONE(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
MILESTONE_ID INT AUTO_INCREMENT,
|
|
||||||
TITLE VARCHAR(100) NOT NULL,
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
DUE_DATE TIMESTAMP,
|
|
||||||
CLOSED_DATE TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
|
|
||||||
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
|
|
||||||
|
|
||||||
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
|
|
||||||
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
|
|
||||||
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
|
|
||||||
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
|
|
||||||
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
INSERT INTO ACCOUNT (
|
|
||||||
USER_NAME,
|
|
||||||
MAIL_ADDRESS,
|
|
||||||
PASSWORD,
|
|
||||||
ADMINISTRATOR,
|
|
||||||
URL,
|
|
||||||
REGISTERED_DATE,
|
|
||||||
UPDATED_DATE,
|
|
||||||
LAST_LOGIN_DATE
|
|
||||||
) VALUES (
|
|
||||||
'root',
|
|
||||||
'root@localhost',
|
|
||||||
'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
|
|
||||||
true,
|
|
||||||
'https://github.com/gitbucket/gitbucket',
|
|
||||||
SYSDATE,
|
|
||||||
SYSDATE,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
-- Fix COLLABORATOR constraints
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_FK1 IF EXISTS;
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_FK0 IF EXISTS;
|
|
||||||
ALTER TABLE COLLABORATOR DROP CONSTRAINT IDX_COLLABORATOR_PK IF EXISTS;
|
|
||||||
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, COLLABORATOR_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE TABLE SSH_KEY (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
SSH_KEY_ID INT AUTO_INCREMENT,
|
|
||||||
TITLE VARCHAR(100) NOT NULL,
|
|
||||||
PUBLIC_KEY TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_PK PRIMARY KEY (USER_NAME, SSH_KEY_ID);
|
|
||||||
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
DROP TABLE COMMIT_LOG;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE ACTIVITY(
|
|
||||||
ACTIVITY_ID INT AUTO_INCREMENT,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ACTIVITY_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ACTIVITY_TYPE VARCHAR(100) NOT NULL,
|
|
||||||
MESSAGE TEXT NOT NULL,
|
|
||||||
ADDITIONAL_INFO TEXT,
|
|
||||||
ACTIVITY_DATE TIMESTAMP NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE COMMIT_LOG (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(40) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_PK PRIMARY KEY (ACTIVITY_ID);
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE ACTIVITY ADD CONSTRAINT IDX_ACTIVITY_FK1 FOREIGN KEY (ACTIVITY_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_LOG ADD CONSTRAINT IDX_COMMIT_LOG_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, COMMIT_ID);
|
|
||||||
ALTER TABLE COMMIT_LOG ADD CONSTRAINT IDX_COMMIT_LOG_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN IMAGE VARCHAR(100);
|
|
||||||
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'comment' WHERE ACTION IS NULL;
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE_COMMENT ALTER COLUMN ACTION VARCHAR(20) NOT NULL;
|
|
||||||
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'close_comment' WHERE ACTION = 'close';
|
|
||||||
UPDATE ISSUE_COMMENT SET ACTION = 'reopen_comment' WHERE ACTION = 'reopen';
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
CREATE TABLE GROUP_MEMBER(
|
|
||||||
GROUP_NAME VARCHAR(100) NOT NULL,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_PK PRIMARY KEY (GROUP_NAME, USER_NAME);
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK0 FOREIGN KEY (GROUP_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
ALTER TABLE GROUP_MEMBER ADD CONSTRAINT IDX_GROUP_MEMBER_FK1 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ADD COLUMN GROUP_ACCOUNT BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
|
||||||
SELECT
|
|
||||||
A.USER_NAME,
|
|
||||||
A.REPOSITORY_NAME,
|
|
||||||
A.ISSUE_ID,
|
|
||||||
NVL(B.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
|
||||||
FROM ISSUE A
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
|
||||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) B
|
|
||||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_USER_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN ORIGIN_REPOSITORY_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN PARENT_USER_NAME VARCHAR(100);
|
|
||||||
ALTER TABLE REPOSITORY ADD COLUMN PARENT_REPOSITORY_NAME VARCHAR(100);
|
|
||||||
|
|
||||||
CREATE TABLE PULL_REQUEST(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
ISSUE_ID INT NOT NULL,
|
|
||||||
BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REQUEST_BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID_FROM VARCHAR(40) NOT NULL,
|
|
||||||
COMMIT_ID_TO VARCHAR(40) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
ALTER TABLE PULL_REQUEST ADD CONSTRAINT IDX_PULL_REQUEST_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
|
|
||||||
|
|
||||||
ALTER TABLE ISSUE ADD COLUMN PULL_REQUEST BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
CREATE TABLE WEB_HOOK (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
URL VARCHAR(200) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL);
|
|
||||||
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN FULL_NAME VARCHAR(100);
|
|
||||||
|
|
||||||
UPDATE ACCOUNT SET FULL_NAME = USER_NAME WHERE FULL_NAME IS NULL;
|
|
||||||
|
|
||||||
ALTER TABLE ACCOUNT ALTER COLUMN FULL_NAME SET NOT NULL;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE ACCOUNT ADD COLUMN REMOVED BOOLEAN DEFAULT FALSE;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE PLUGIN (
|
|
||||||
PLUGIN_ID VARCHAR(100) NOT NULL,
|
|
||||||
VERSION VARCHAR(100) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PLUGIN ADD CONSTRAINT IDX_PLUGIN_PK PRIMARY KEY (PLUGIN_ID);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
CREATE TABLE COMMIT_COMMENT (
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(100) NOT NULL,
|
|
||||||
COMMENT_ID INT AUTO_INCREMENT,
|
|
||||||
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
CONTENT TEXT NOT NULL,
|
|
||||||
FILE_NAME NVARCHAR(100),
|
|
||||||
OLD_LINE_NUMBER INT,
|
|
||||||
NEW_LINE_NUMBER INT,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL,
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL,
|
|
||||||
PULL_REQUEST BOOLEAN NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_PK PRIMARY KEY (COMMENT_ID);
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, COMMENT_ID);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS ACCESS_TOKEN;
|
|
||||||
|
|
||||||
CREATE TABLE ACCESS_TOKEN (
|
|
||||||
ACCESS_TOKEN_ID INT NOT NULL AUTO_INCREMENT,
|
|
||||||
TOKEN_HASH VARCHAR(40) NOT NULL,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
NOTE TEXT NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_PK PRIMARY KEY (ACCESS_TOKEN_ID);
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_TOKEN_HASH UNIQUE(TOKEN_HASH);
|
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS COMMIT_STATUS;
|
|
||||||
CREATE TABLE COMMIT_STATUS(
|
|
||||||
COMMIT_STATUS_ID INT AUTO_INCREMENT,
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
COMMIT_ID VARCHAR(40) NOT NULL,
|
|
||||||
CONTEXT VARCHAR(255) NOT NULL, -- context is too long (maximum is 255 characters)
|
|
||||||
STATE VARCHAR(10) NOT NULL, -- pending, success, error, or failure
|
|
||||||
TARGET_URL VARCHAR(200),
|
|
||||||
DESCRIPTION TEXT,
|
|
||||||
CREATOR VARCHAR(100) NOT NULL,
|
|
||||||
REGISTERED_DATE TIMESTAMP NOT NULL, -- CREATED_AT
|
|
||||||
UPDATED_DATE TIMESTAMP NOT NULL -- UPDATED_AT
|
|
||||||
);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_PK PRIMARY KEY (COMMIT_STATUS_ID);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_1
|
|
||||||
UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT);
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK1
|
|
||||||
FOREIGN KEY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK2
|
|
||||||
FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK3
|
|
||||||
FOREIGN KEY (CREATOR) REFERENCES ACCOUNT (USER_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS PROTECTED_BRANCH;
|
|
||||||
|
|
||||||
CREATE TABLE PROTECTED_BRANCH(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
STATUS_CHECK_ADMIN BOOLEAN NOT NULL DEFAULT false
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PROTECTED_BRANCH ADD CONSTRAINT IDX_PROTECTED_BRANCH_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, BRANCH);
|
|
||||||
ALTER TABLE PROTECTED_BRANCH ADD CONSTRAINT IDX_PROTECTED_BRANCH_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS PROTECTED_BRANCH_REQUIRE_CONTEXT;
|
|
||||||
CREATE TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
BRANCH VARCHAR(100) NOT NULL,
|
|
||||||
CONTEXT VARCHAR(255) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT ADD CONSTRAINT IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT);
|
|
||||||
ALTER TABLE PROTECTED_BRANCH_REQUIRE_CONTEXT ADD CONSTRAINT IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, BRANCH) REFERENCES PROTECTED_BRANCH (USER_NAME, REPOSITORY_NAME, BRANCH)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100);
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE WEB_HOOK ADD COLUMN CTYPE VARCHAR(10);
|
|
||||||
|
|
||||||
UPDATE WEB_HOOK SET CTYPE = 'form';
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
DROP TABLE IF EXISTS WEB_HOOK_EVENT;
|
|
||||||
|
|
||||||
CREATE TABLE WEB_HOOK_EVENT(
|
|
||||||
USER_NAME VARCHAR(100) NOT NULL,
|
|
||||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
|
||||||
URL VARCHAR(200) NOT NULL,
|
|
||||||
EVENT VARCHAR(30) NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL, EVENT);
|
|
||||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, URL) REFERENCES WEB_HOOK (USER_NAME, REPOSITORY_NAME, URL)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
CREATE TEMPORARY TABLE TMP_EVENTS (EVENT VARCHAR(30));
|
|
||||||
|
|
||||||
INSERT INTO TMP_EVENTS VALUES ('push'),('issue_comment'),('issues'),('pull_request');
|
|
||||||
|
|
||||||
INSERT INTO WEB_HOOK_EVENT (USER_NAME, REPOSITORY_NAME, URL, EVENT)
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, URL, EVENT
|
|
||||||
FROM WEB_HOOK, TMP_EVENTS;
|
|
||||||
|
|
||||||
DROP TABLE TMP_EVENTS;
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT ADD COLUMN ISSUE_ID INT;
|
|
||||||
|
|
||||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
|
||||||
SELECT
|
|
||||||
A.USER_NAME,
|
|
||||||
A.REPOSITORY_NAME,
|
|
||||||
A.ISSUE_ID,
|
|
||||||
NVL(B.COMMENT_COUNT, 0) + NVL(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
|
||||||
FROM ISSUE A
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
|
||||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) B
|
|
||||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
|
||||||
LEFT OUTER JOIN (
|
|
||||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
|
||||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
|
||||||
) C
|
|
||||||
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
|
|
||||||
|
|
||||||
|
|
||||||
UPDATE COMMIT_COMMENT C SET (ISSUE_ID) = (
|
|
||||||
SELECT MAX(P.ISSUE_ID)
|
|
||||||
FROM PULL_REQUEST P
|
|
||||||
WHERE
|
|
||||||
C.USER_NAME = P.USER_NAME AND
|
|
||||||
C.REPOSITORY_NAME = P.REPOSITORY_NAME AND
|
|
||||||
C.COMMIT_ID = P.COMMIT_ID_TO
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE COMMIT_COMMENT DROP COLUMN PULL_REQUEST;
|
|
||||||
18
src/main/resources/update/gitbucket-core_4.0.sql
Normal file
18
src/main/resources/update/gitbucket-core_4.0.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
||||||
|
SELECT
|
||||||
|
A.USER_NAME,
|
||||||
|
A.REPOSITORY_NAME,
|
||||||
|
A.ISSUE_ID,
|
||||||
|
COALESCE(B.COMMENT_COUNT, 0) + COALESCE(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
||||||
|
FROM ISSUE A
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
||||||
|
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
||||||
|
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||||
|
) B
|
||||||
|
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
||||||
|
LEFT OUTER JOIN (
|
||||||
|
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
||||||
|
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||||
|
) C
|
||||||
|
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
|
||||||
351
src/main/resources/update/gitbucket-core_4.0.xml
Normal file
351
src/main/resources/update/gitbucket-core_4.0.xml
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACCOUNT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACCOUNT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MAIL_ADDRESS" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PASSWORD" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="ADMINISTRATOR" type="boolean" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="LAST_LOGIN_DATE" type="datetime" nullable="true"/>
|
||||||
|
<column name="IMAGE" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="GROUP_ACCOUNT" type="boolean" nullable="false"/>
|
||||||
|
<column name="FULL_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REMOVED" type="boolean" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACCOUNT_PK" tableName="ACCOUNT" columnNames="USER_NAME"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_ACCOUNT_1" tableName="ACCOUNT" columnNames="MAIL_ADDRESS"/>
|
||||||
|
|
||||||
|
<insert tableName="ACCOUNT">
|
||||||
|
<column name="USER_NAME" value="root"/>
|
||||||
|
<column name="FULL_NAME" value="root"/>
|
||||||
|
<column name="MAIL_ADDRESS" value="root@localhost"/>
|
||||||
|
<column name="PASSWORD" value="dc76e9f0c0006e8f919e0c515c66dbba3982f785"/>
|
||||||
|
<column name="ADMINISTRATOR" valueBoolean="true"/>
|
||||||
|
<column name="URL" value="https://github.com/gitbucket/gitbucket"/>
|
||||||
|
<column name="GROUP_ACCOUNT" valueBoolean="false"/>
|
||||||
|
<column name="REMOVED" valueBoolean="false"/>
|
||||||
|
<column name="REGISTERED_DATE" valueDate="${currentDateTime}"/>
|
||||||
|
<column name="UPDATED_DATE" valueDate="${currentDateTime}"/>
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- REPOSITORY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="REPOSITORY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PRIVATE" type="boolean" nullable="false"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="DEFAULT_BRANCH" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="LAST_ACTIVITY_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="ORIGIN_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="ORIGIN_REPOSITORY_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="PARENT_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="PARENT_REPOSITORY_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_REPOSITORY_PK" tableName="REPOSITORY" columnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_REPOSITORY_FK0" baseTableName="REPOSITORY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACCESS_TOKEN -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACCESS_TOKEN">
|
||||||
|
<column name="ACCESS_TOKEN_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TOKEN_HASH" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="NOTE" type="text" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACCESS_TOKEN_PK" tableName="ACCESS_TOKEN" columnNames="ACCESS_TOKEN_ID"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_ACCESS_TOKEN_TOKEN_HASH" tableName="ACCESS_TOKEN" columnNames="TOKEN_HASH"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACCESS_TOKEN_FK0" baseTableName="ACCESS_TOKEN" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ACTIVITY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ACTIVITY">
|
||||||
|
<column name="ACTIVITY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ACTIVITY_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ACTIVITY_TYPE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MESSAGE" type="text" nullable="false"/>
|
||||||
|
<column name="ADDITIONAL_INFO" type="text" nullable="true"/>
|
||||||
|
<column name="ACTIVITY_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ACTIVITY_PK" tableName="ACTIVITY" columnNames="ACTIVITY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK1" baseTableName="ACTIVITY" baseColumnNames="ACTIVITY_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK0" baseTableName="ACTIVITY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COLLABORATOR -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COLLABORATOR">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COLLABORATOR_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COLLABORATOR_PK" tableName="COLLABORATOR" columnNames="USER_NAME, REPOSITORY_NAME, COLLABORATOR_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COLLABORATOR_FK1" baseTableName="COLLABORATOR" baseColumnNames="COLLABORATOR_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COLLABORATOR_FK0" baseTableName="COLLABORATOR" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COMMIT_COMMENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COMMIT_COMMENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="false"/>
|
||||||
|
<column name="FILE_NAME" type="varchar(260)" nullable="true"/>
|
||||||
|
<column name="OLD_LINE_NUMBER" type="int" nullable="true"/>
|
||||||
|
<column name="NEW_LINE_NUMBER" type="int" nullable="true"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COMMIT_COMMENT_PK" tableName="COMMIT_COMMENT" columnNames="COMMENT_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_COMMENT_FK0" baseTableName="COMMIT_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- COMMIT_STATUS -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="COMMIT_STATUS">
|
||||||
|
<column name="COMMIT_STATUS_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="CONTEXT" type="varchar(255)" nullable="false"/>
|
||||||
|
<column name="STATE" type="varchar(10)" nullable="false"/>
|
||||||
|
<column name="TARGET_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="CREATOR" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_COMMIT_STATUS_PK" tableName="COMMIT_STATUS" columnNames="COMMIT_STATUS_ID"/>
|
||||||
|
<addUniqueConstraint constraintName="IDX_COMMIT_STATUS_1" tableName="COMMIT_STATUS" columnNames="USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK3" baseTableName="COMMIT_STATUS" baseColumnNames="CREATOR" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK2" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK1" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- GROUP_MEMBER -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="GROUP_MEMBER">
|
||||||
|
<column name="GROUP_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MANAGER" type="boolean" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_GROUP_MEMBER_PK" tableName="GROUP_MEMBER" columnNames="GROUP_NAME, USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_GROUP_MEMBER_FK1" baseTableName="GROUP_MEMBER" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_GROUP_MEMBER_FK0" baseTableName="GROUP_MEMBER" baseColumnNames="GROUP_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- LABEL -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="LABEL">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="LABEL_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="LABEL_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COLOR" type="char(6)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_LABEL_PK" tableName="LABEL" columnNames="USER_NAME, REPOSITORY_NAME, LABEL_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_LABEL_FK0" baseTableName="LABEL" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- MILESTONE -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="MILESTONE">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MILESTONE_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="DESCRIPTION" type="text" nullable="true"/>
|
||||||
|
<column name="DUE_DATE" type="datetime" nullable="true"/>
|
||||||
|
<column name="CLOSED_DATE" type="datetime" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_MILESTONE_PK" tableName="MILESTONE" columnNames="USER_NAME, REPOSITORY_NAME, MILESTONE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_MILESTONE_FK0" baseTableName="MILESTONE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="OPENED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="MILESTONE_ID" type="int" nullable="true"/>
|
||||||
|
<column name="ASSIGNED_USER_NAME" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="TITLE" type="text" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="true"/>
|
||||||
|
<column name="CLOSED" type="boolean" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="PULL_REQUEST" type="boolean" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_PK" tableName="ISSUE" columnNames="ISSUE_ID, USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK0" baseTableName="ISSUE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK2" baseTableName="ISSUE" baseColumnNames="MILESTONE_ID" referencedTableName="MILESTONE" referencedColumnNames="MILESTONE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK1" baseTableName="ISSUE" baseColumnNames="OPENED_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_COMMENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_COMMENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="ACTION" type="varchar(20)" nullable="false"/>
|
||||||
|
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTENT" type="text" nullable="false"/>
|
||||||
|
<column name="REGISTERED_DATE" type="datetime" nullable="false"/>
|
||||||
|
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_COMMENT_PK" tableName="ISSUE_COMMENT" columnNames="COMMENT_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_COMMENT_FK0" baseTableName="ISSUE_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_ID -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_ID">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_ID_PK" tableName="ISSUE_ID" columnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_ID_FK1" baseTableName="ISSUE_ID" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- ISSUE_ID -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="ISSUE_LABEL">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="LABEL_ID" type="int" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_ISSUE_LABEL_PK" tableName="ISSUE_LABEL" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_ISSUE_LABEL_FK0" baseTableName="ISSUE_LABEL" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PLUGIN -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PLUGIN">
|
||||||
|
<column name="PLUGIN_ID" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="VERSION" type="varchar(100)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PLUGIN_PK" tableName="PLUGIN" columnNames="PLUGIN_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PULL_REQUEST -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PULL_REQUEST">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REQUEST_BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID_FROM" type="varchar(40)" nullable="false"/>
|
||||||
|
<column name="COMMIT_ID_TO" type="varchar(40)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PULL_REQUEST_PK" tableName="PULL_REQUEST" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PULL_REQUEST_FK0" baseTableName="PULL_REQUEST" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- SSH_KEY -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="SSH_KEY">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="SSH_KEY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||||
|
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="PUBLIC_KEY" type="text" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_SSH_KEY_PK" tableName="SSH_KEY" columnNames="USER_NAME, SSH_KEY_ID"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_SSH_KEY_FK0" baseTableName="SSH_KEY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- WEB_HOOK -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="WEB_HOOK">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="false"/>
|
||||||
|
<column name="TOKEN" type="varchar(100)" nullable="true"/>
|
||||||
|
<column name="CTYPE" type="varchar(10)" nullable="true"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- WEB_HOOK_EVENT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="WEB_HOOK_EVENT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="URL" type="varchar(200)" nullable="false"/>
|
||||||
|
<column name="EVENT" type="varchar(30)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_WEB_HOOK_EVENT_PK" tableName="WEB_HOOK_EVENT" columnNames="USER_NAME, REPOSITORY_NAME, URL, EVENT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PROTECTED_BRANCH -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PROTECTED_BRANCH">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="STATUS_CHECK_ADMIN" type="boolean" nullable="false" defaultValueBoolean="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_PK" tableName="PROTECTED_BRANCH" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_FK0" baseTableName="PROTECTED_BRANCH" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<!-- PROTECTED_BRANCH_REQUIRE_CONTEXT -->
|
||||||
|
<!--================================================================================================-->
|
||||||
|
<createTable tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT">
|
||||||
|
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||||
|
<column name="CONTEXT" type="varchar(255)" nullable="false"/>
|
||||||
|
</createTable>
|
||||||
|
|
||||||
|
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0" baseTableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
|
||||||
|
</changeSet>
|
||||||
10
src/main/resources/update/gitbucket-core_4.2.xml
Normal file
10
src/main/resources/update/gitbucket-core_4.2.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="ENABLE_ISSUES" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="EXTERNAL_ISSUES_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
<column name="ENABLE_WIKI" type="boolean" nullable="false" defaultValueBoolean="true"/>
|
||||||
|
<column name="ALLOW_WIKI_EDITING" type="boolean" nullable="false" defaultValueBoolean="false"/>
|
||||||
|
<column name="EXTERNAL_WIKI_URL" type="varchar(200)" nullable="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
15
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
15
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package gitbucket.core
|
||||||
|
|
||||||
|
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
|
||||||
|
import io.github.gitbucket.solidbase.model.{Version, Module}
|
||||||
|
|
||||||
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
|
new Version("4.0.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
|
),
|
||||||
|
new Version("4.1.0"),
|
||||||
|
new Version("4.2.0",
|
||||||
|
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -14,7 +14,7 @@ object ApiBranchProtection{
|
|||||||
|
|
||||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection = ApiBranchProtection(
|
||||||
enabled = info.enabled,
|
enabled = info.enabled,
|
||||||
required_status_checks = Some(Status(EnforcementLevel(info.enabled, info.includeAdministrators), info.contexts)))
|
required_status_checks = Some(Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.contexts)))
|
||||||
val statusNone = Status(Off, Seq.empty)
|
val statusNone = Status(Off, Seq.empty)
|
||||||
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
||||||
sealed class EnforcementLevel(val name: String)
|
sealed class EnforcementLevel(val name: String)
|
||||||
@@ -44,4 +44,3 @@ object ApiBranchProtection{
|
|||||||
}
|
}
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ case class ApiPullRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
object ApiPullRequest{
|
object ApiPullRequest{
|
||||||
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest = ApiPullRequest(
|
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest =
|
||||||
|
ApiPullRequest(
|
||||||
number = issue.issueId,
|
number = issue.issueId,
|
||||||
updated_at = issue.updatedDate,
|
updated_at = issue.updatedDate,
|
||||||
created_at = issue.registeredDate,
|
created_at = issue.registeredDate,
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ case class ApiRepository(
|
|||||||
`private`: Boolean,
|
`private`: Boolean,
|
||||||
default_branch: String,
|
default_branch: String,
|
||||||
owner: ApiUser)(urlIsHtmlUrl: Boolean) {
|
owner: ApiUser)(urlIsHtmlUrl: Boolean) {
|
||||||
val forks_count = forks
|
val forks_count = forks
|
||||||
val watchers_count = watchers
|
val watchers_count = watchers
|
||||||
val url = if(urlIsHtmlUrl){
|
val url = if(urlIsHtmlUrl){
|
||||||
ApiPath(s"/${full_name}")
|
ApiPath(s"/${full_name}")
|
||||||
}else{
|
} else {
|
||||||
ApiPath(s"/api/v3/repos/${full_name}")
|
ApiPath(s"/api/v3/repos/${full_name}")
|
||||||
}
|
}
|
||||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||||
@@ -34,14 +34,14 @@ object ApiRepository{
|
|||||||
watchers: Int = 0,
|
watchers: Int = 0,
|
||||||
urlIsHtmlUrl: Boolean = false): ApiRepository =
|
urlIsHtmlUrl: Boolean = false): ApiRepository =
|
||||||
ApiRepository(
|
ApiRepository(
|
||||||
name = repository.repositoryName,
|
name = repository.repositoryName,
|
||||||
full_name = s"${repository.userName}/${repository.repositoryName}",
|
full_name = s"${repository.userName}/${repository.repositoryName}",
|
||||||
description = repository.description.getOrElse(""),
|
description = repository.description.getOrElse(""),
|
||||||
watchers = 0,
|
watchers = 0,
|
||||||
forks = forkedCount,
|
forks = forkedCount,
|
||||||
`private` = repository.isPrivate,
|
`private` = repository.isPrivate,
|
||||||
default_branch = repository.defaultBranch,
|
default_branch = repository.defaultBranch,
|
||||||
owner = owner
|
owner = owner
|
||||||
)(urlIsHtmlUrl)
|
)(urlIsHtmlUrl)
|
||||||
|
|
||||||
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
get("/:userName/_edit")(oneselfOnly {
|
get("/:userName/_edit")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).map { x =>
|
||||||
html.edit(x, flash.get("info"))
|
html.edit(x, flash.get("info"), flash.get("error"))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -178,7 +178,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
get("/:userName/_delete")(oneselfOnly {
|
get("/:userName/_delete")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
|
|
||||||
getAccountByUserName(userName, true).foreach { account =>
|
getAccountByUserName(userName, true).map { account =>
|
||||||
|
if(isLastAdministrator(account)){
|
||||||
|
flash += "error" -> "Account can't be removed because this is last one administrator."
|
||||||
|
redirect(s"/${userName}/_edit")
|
||||||
|
} else {
|
||||||
// // Remove repositories
|
// // Remove repositories
|
||||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||||
// deleteRepository(userName, repositoryName)
|
// deleteRepository(userName, repositoryName)
|
||||||
@@ -187,14 +191,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||||
// }
|
// }
|
||||||
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||||
// removeUserRelatedData(userName)
|
removeUserRelatedData(userName)
|
||||||
|
updateAccount(account.copy(isRemoved = true))
|
||||||
removeUserRelatedData(userName)
|
session.invalidate
|
||||||
updateAccount(account.copy(isRemoved = true))
|
redirect("/")
|
||||||
}
|
}
|
||||||
|
} getOrElse NotFound
|
||||||
session.invalidate
|
|
||||||
redirect("/")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:userName/_ssh")(oneselfOnly {
|
get("/:userName/_ssh")(oneselfOnly {
|
||||||
@@ -447,8 +449,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
private def validPublicKey: Constraint = new Constraint(){
|
private def validPublicKey: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
|
||||||
case Some(_) => None
|
case Some(_) if !getAllKeys().exists(_.publicKey == value) => None
|
||||||
case None => Some("Key is invalid.")
|
case _ => Some("Key is invalid.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
}) getOrElse NotFound
|
}) getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
|
*/
|
||||||
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
(for{
|
(for{
|
||||||
@@ -261,18 +263,28 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
* https://developer.github.com/v3/pulls/#list-pull-requests
|
* https://developer.github.com/v3/pulls/#list-pull-requests
|
||||||
*/
|
*/
|
||||||
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
|
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
// TODO: more api spec condition
|
// TODO: more api spec condition
|
||||||
val condition = IssueSearchCondition(request)
|
val condition = IssueSearchCondition(request)
|
||||||
val baseOwner = getAccountByUserName(repository.owner).get
|
val baseOwner = getAccountByUserName(repository.owner).get
|
||||||
val issues:List[(Issue, Account, Int, PullRequest, Repository, Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name)
|
|
||||||
JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
|
val issues: List[(Issue, Account, Int, PullRequest, Repository, Account)] =
|
||||||
|
searchPullRequestByApi(
|
||||||
|
condition = condition,
|
||||||
|
offset = (page - 1) * PullRequestLimit,
|
||||||
|
limit = PullRequestLimit,
|
||||||
|
repos = repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
|
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
|
||||||
ApiPullRequest(
|
ApiPullRequest(
|
||||||
issue,
|
issue,
|
||||||
pullRequest,
|
pullRequest,
|
||||||
ApiRepository(headRepo, ApiUser(headOwner)),
|
ApiRepository(headRepo, ApiUser(headOwner)),
|
||||||
ApiRepository(repository, ApiUser(baseOwner)),
|
ApiRepository(repository, ApiUser(baseOwner)),
|
||||||
ApiUser(issueUser)) })
|
ApiUser(issueUser)
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -83,9 +83,9 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
|
||||||
|
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
|
case "assigned" => condition.copy(assigned = Some(Some(userName)), author = None, mentioned = None)
|
||||||
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
|
case "mentioned" => condition.copy(assigned = None, author = None, mentioned = Some(userName))
|
||||||
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
|
case _ => condition.copy(assigned = None, author = Some(userName), mentioned = None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
case _ => condition.copy(author = Some(userName))
|
case _ => condition.copy(author = Some(userName))
|
||||||
},
|
},
|
||||||
@@ -128,7 +128,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||||
filter match {
|
filter match {
|
||||||
case "assigned" => condition.copy(assigned = Some(userName))
|
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||||
case _ => condition.copy(author = Some(userName))
|
case _ => condition.copy(author = Some(userName))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import gitbucket.core.servlet.Database
|
|||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
import org.eclipse.jgit.dircache.DirCache
|
||||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
import org.eclipse.jgit.lib.{FileMode, Constants}
|
||||||
|
import org.scalatra
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
||||||
import org.apache.commons.io.{IOUtils, FileUtils}
|
import org.apache.commons.io.{IOUtils, FileUtils}
|
||||||
@@ -71,11 +73,26 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
|
|||||||
fileName
|
fileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, FileUtil.isImage)
|
}, FileUtil.isUploadableType)
|
||||||
}
|
}
|
||||||
} getOrElse BadRequest
|
} getOrElse BadRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post("/import") {
|
||||||
|
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
if(file.getName.endsWith(".xml")){
|
||||||
|
import JDBCUtil._
|
||||||
|
val conn = request2Session(request).conn
|
||||||
|
conn.importAsXML(file.getInputStream)
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Import is available for only the XML file.")
|
||||||
|
}
|
||||||
|
}, _ => true)
|
||||||
|
}
|
||||||
|
redirect("/admin/data")
|
||||||
|
}
|
||||||
|
|
||||||
private def collaboratorsOnly(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
private def collaboratorsOnly(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||||
implicit val session = Database.getSession(request)
|
implicit val session = Database.getSession(request)
|
||||||
loginAccount match {
|
loginAccount match {
|
||||||
|
|||||||
@@ -117,7 +117,9 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
* JSON API for checking user existence.
|
* JSON API for checking user existence.
|
||||||
*/
|
*/
|
||||||
post("/_user/existence")(usersOnly {
|
post("/_user/existence")(usersOnly {
|
||||||
getAccountByUserName(params("userName")).isDefined
|
getAccountByUserName(params("userName")).map { account =>
|
||||||
|
if(params.get("userOnly").isDefined) !account.isGroupAccount else true
|
||||||
|
} getOrElse false
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO Move to RepositoryViwerController?
|
// TODO Move to RepositoryViwerController?
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
// close issue by commit message
|
// close issue by commit message
|
||||||
if(pullreq.requestBranch == repository.repository.defaultBranch){
|
if(pullreq.requestBranch == repository.repository.defaultBranch){
|
||||||
commits.map{ commit =>
|
commits.map { commit =>
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,10 +255,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
commits.flatten.foreach { commit =>
|
commits.flatten.foreach { commit =>
|
||||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
|
||||||
}
|
}
|
||||||
issue.content match {
|
closeIssuesFromMessage(issue.title + " " + issue.content.getOrElse(""), loginAccount.userName, owner, name)
|
||||||
case Some(content) => closeIssuesFromMessage(content, loginAccount.userName, owner, name)
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +351,15 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
originRepository.owner, originRepository.name, oldId.getName,
|
originRepository.owner, originRepository.name, oldId.getName,
|
||||||
forkedRepository.owner, forkedRepository.name, newId.getName)
|
forkedRepository.owner, forkedRepository.name, newId.getName)
|
||||||
|
|
||||||
|
val title = if(commits.flatten.length == 1){
|
||||||
|
commits.flatten.head.shortMessage
|
||||||
|
} else {
|
||||||
|
val text = forkedId.replaceAll("[\\-_]", " ")
|
||||||
|
text.substring(0, 1).toUpperCase + text.substring(1)
|
||||||
|
}
|
||||||
|
|
||||||
html.compare(
|
html.compare(
|
||||||
|
title,
|
||||||
commits,
|
commits,
|
||||||
diffs,
|
diffs,
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||||
|
|||||||
@@ -27,12 +27,26 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
with OwnerAuthenticator with UsersAuthenticator =>
|
with OwnerAuthenticator with UsersAuthenticator =>
|
||||||
|
|
||||||
// for repository options
|
// for repository options
|
||||||
case class OptionsForm(repositoryName: String, description: Option[String], isPrivate: Boolean)
|
case class OptionsForm(
|
||||||
|
repositoryName: String,
|
||||||
|
description: Option[String],
|
||||||
|
isPrivate: Boolean,
|
||||||
|
enableIssues: Boolean,
|
||||||
|
externalIssuesUrl: Option[String],
|
||||||
|
enableWiki: Boolean,
|
||||||
|
allowWikiEditing: Boolean,
|
||||||
|
externalWikiUrl: Option[String]
|
||||||
|
)
|
||||||
|
|
||||||
val optionsForm = mapping(
|
val optionsForm = mapping(
|
||||||
"repositoryName" -> trim(label("Repository Name", text(required, maxlength(40), identifier, renameRepositoryName))),
|
"repositoryName" -> trim(label("Repository Name" , text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||||
"description" -> trim(label("Description" , optional(text()))),
|
"description" -> trim(label("Description" , optional(text()))),
|
||||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
"isPrivate" -> trim(label("Repository Type" , boolean())),
|
||||||
|
"enableIssues" -> trim(label("Enable Issues" , boolean())),
|
||||||
|
"externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))),
|
||||||
|
"enableWiki" -> trim(label("Enable Wiki" , boolean())),
|
||||||
|
"allowWikiEditing" -> trim(label("Allow Wiki Editing" , boolean())),
|
||||||
|
"externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200)))))
|
||||||
)(OptionsForm.apply)
|
)(OptionsForm.apply)
|
||||||
|
|
||||||
// for default branch
|
// for default branch
|
||||||
@@ -92,7 +106,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
form.description,
|
form.description,
|
||||||
repository.repository.parentUserName.map { _ =>
|
repository.repository.parentUserName.map { _ =>
|
||||||
repository.repository.isPrivate
|
repository.repository.isPrivate
|
||||||
} getOrElse form.isPrivate
|
} getOrElse form.isPrivate,
|
||||||
|
form.enableIssues,
|
||||||
|
form.externalIssuesUrl,
|
||||||
|
form.enableWiki,
|
||||||
|
form.allowWikiEditing,
|
||||||
|
form.externalWikiUrl
|
||||||
)
|
)
|
||||||
// Change repository name
|
// Change repository name
|
||||||
if(repository.name != form.repositoryName){
|
if(repository.name != form.repositoryName){
|
||||||
@@ -294,7 +313,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the danger zone.
|
* Display the danger zone.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/danger")(ownerOnly {
|
get("/:owner/:repository/settings/danger")(ownerOnly {
|
||||||
html.danger(_)
|
html.danger(_, flash.get("info"))
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -333,6 +352,19 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
redirect(s"/${repository.owner}")
|
redirect(s"/${repository.owner}")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run GC
|
||||||
|
*/
|
||||||
|
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
|
||||||
|
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
git.gc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flash += "info" -> "Garbage collection has been executed."
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/settings/danger")
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides duplication check for web hook url.
|
* Provides duplication check for web hook url.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -691,8 +691,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
|
override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
import gitbucket.core.admin.html
|
import gitbucket.core.admin.html
|
||||||
import gitbucket.core.service.{AccountService, SystemSettingsService, RepositoryService}
|
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||||
import gitbucket.core.util.AdminAuthenticator
|
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||||
import gitbucket.core.ssh.SshServer
|
import gitbucket.core.ssh.SshServer
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
@@ -11,7 +13,8 @@ import gitbucket.core.util.ControlUtil._
|
|||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import io.github.gitbucket.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||||
|
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
@@ -68,12 +71,22 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
).flatten
|
).flatten
|
||||||
}
|
}
|
||||||
|
|
||||||
private val pluginForm = mapping(
|
private val sendMailForm = mapping(
|
||||||
"pluginId" -> list(trim(label("", text())))
|
"smtp" -> mapping(
|
||||||
)(PluginForm.apply)
|
"host" -> trim(label("SMTP Host", text(required))),
|
||||||
|
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||||
|
"user" -> trim(label("SMTP User", optional(text()))),
|
||||||
|
"password" -> trim(label("SMTP Password", optional(text()))),
|
||||||
|
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||||
|
"fromAddress" -> trim(label("FROM Address", optional(text()))),
|
||||||
|
"fromName" -> trim(label("FROM Name", optional(text())))
|
||||||
|
)(Smtp.apply),
|
||||||
|
"testAddress" -> trim(label("", text(required)))
|
||||||
|
)(SendMailForm.apply)
|
||||||
|
|
||||||
case class PluginForm(pluginIds: List[String])
|
case class SendMailForm(smtp: Smtp, testAddress: String)
|
||||||
|
|
||||||
|
case class DataExportForm(tableNames: List[String])
|
||||||
|
|
||||||
case class NewUserForm(userName: String, password: String, fullName: String,
|
case class NewUserForm(userName: String, password: String, fullName: String,
|
||||||
mailAddress: String, isAdmin: Boolean,
|
mailAddress: String, isAdmin: Boolean,
|
||||||
@@ -149,6 +162,18 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
redirect("/admin/system")
|
redirect("/admin/system")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
||||||
|
try {
|
||||||
|
new Mailer(form.smtp).send(form.testAddress,
|
||||||
|
"Test message from GitBucket", "This is a test message from GitBucket.")
|
||||||
|
|
||||||
|
"Test mail has been sent to: " + form.testAddress
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
case e: Exception => "[Error] " + e.toString
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
get("/admin/plugins")(adminOnly {
|
get("/admin/plugins")(adminOnly {
|
||||||
html.plugins(PluginRegistry().getPlugins())
|
html.plugins(PluginRegistry().getPlugins())
|
||||||
})
|
})
|
||||||
@@ -176,36 +201,39 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
get("/admin/users/:userName/_edituser")(adminOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
html.user(getAccountByUserName(userName, true))
|
html.user(getAccountByUserName(userName, true), flash.get("error"))
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName, true).map { account =>
|
getAccountByUserName(userName, true).map { account =>
|
||||||
|
if(account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)){
|
||||||
|
flash += "error" -> "Account can't be turned off because this is last one administrator."
|
||||||
|
redirect(s"/admin/users/${userName}/_edituser")
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
if(form.isRemoved){
|
updateAccount(account.copy(
|
||||||
// Remove repositories
|
password = form.password.map(sha1).getOrElse(account.password),
|
||||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
fullName = form.fullName,
|
||||||
// deleteRepository(userName, repositoryName)
|
mailAddress = form.mailAddress,
|
||||||
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
isAdmin = form.isAdmin,
|
||||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
url = form.url,
|
||||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
isRemoved = form.isRemoved))
|
||||||
// }
|
|
||||||
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
updateImage(userName, form.fileId, form.clearImage)
|
||||||
removeUserRelatedData(userName)
|
redirect("/admin/users")
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -268,6 +296,32 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/admin/data")(adminOnly {
|
||||||
|
import gitbucket.core.util.JDBCUtil._
|
||||||
|
val session = request2Session(request)
|
||||||
|
html.data(session.conn.allTableNames())
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/export")(adminOnly {
|
||||||
|
import gitbucket.core.util.JDBCUtil._
|
||||||
|
val session = request2Session(request)
|
||||||
|
val file = if(params("type") == "sql"){
|
||||||
|
session.conn.exportAsSQL(request.getParameterValues("tableNames").toSeq)
|
||||||
|
} else {
|
||||||
|
session.conn.exportAsXML(request.getParameterValues("tableNames").toSeq)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType = "application/octet-stream"
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=" + file.getName)
|
||||||
|
response.setContentLength(file.length.toInt)
|
||||||
|
|
||||||
|
using(new FileInputStream(file)){ in =>
|
||||||
|
IOUtils.copy(in, response.outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
()
|
||||||
|
})
|
||||||
|
|
||||||
private def members: Constraint = new Constraint(){
|
private def members: Constraint = new Constraint(){
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
if(value.split(",").exists {
|
if(value.split(",").exists {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.wiki.html
|
import gitbucket.core.wiki.html
|
||||||
import gitbucket.core.service.{RepositoryService, WikiService, ActivityService, AccountService}
|
import gitbucket.core.service.{AccountService, ActivityService, RepositoryService, WikiService}
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
@@ -39,7 +40,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||||
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
|
||||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
repository, isEditable(repository),
|
||||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||||
@@ -50,7 +51,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||||
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
|
||||||
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
repository, isEditable(repository),
|
||||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||||
getWikiPage(repository.owner, repository.name, "_Footer"))
|
getWikiPage(repository.owner, repository.name, "_Footer"))
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||||
@@ -62,7 +63,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||||
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
|
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -73,7 +74,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
isEditable(repository), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -82,102 +83,115 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
isEditable(repository), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_revert/:commitId")(referrersOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
if(isEditable(repository)){
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))){
|
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))){
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||||
} else {
|
|
||||||
flash += "info" -> "This patch was not able to be reversed."
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_revert/:commitId")(collaboratorsOnly { repository =>
|
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
|
||||||
|
|
||||||
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/")
|
|
||||||
} else {
|
|
||||||
flash += "info" -> "This patch was not able to be reversed."
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
|
||||||
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
|
||||||
})
|
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
|
||||||
saveWikiPage(
|
|
||||||
repository.owner,
|
|
||||||
repository.name,
|
|
||||||
form.currentPageName,
|
|
||||||
form.pageName,
|
|
||||||
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
|
|
||||||
loginAccount,
|
|
||||||
form.message.getOrElse(""),
|
|
||||||
Some(form.id)
|
|
||||||
).map { commitId =>
|
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
|
||||||
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
|
||||||
}
|
|
||||||
if(notReservedPageName(form.pageName)) {
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
|
||||||
} else {
|
} else {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
flash += "info" -> "This patch was not able to be reversed."
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}")
|
||||||
}
|
}
|
||||||
}
|
} else Unauthorized()
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/:owner/:repository/wiki/_revert/:commitId")(referrersOnly { repository =>
|
||||||
|
if(isEditable(repository)){
|
||||||
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
|
if(revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)){
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki/")
|
||||||
|
} else {
|
||||||
|
flash += "info" -> "This patch was not able to be reversed."
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||||
|
}
|
||||||
|
} else Unauthorized()
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/:owner/:repository/wiki/:page/_edit")(referrersOnly { repository =>
|
||||||
|
if(isEditable(repository)){
|
||||||
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
post("/:owner/:repository/wiki/_edit", editForm)(referrersOnly { (form, repository) =>
|
||||||
html.edit("", None, _)
|
if(isEditable(repository)){
|
||||||
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
|
saveWikiPage(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
form.currentPageName,
|
||||||
|
form.pageName,
|
||||||
|
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
|
||||||
|
loginAccount,
|
||||||
|
form.message.getOrElse(""),
|
||||||
|
Some(form.id)
|
||||||
|
).map { commitId =>
|
||||||
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
|
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
||||||
|
}
|
||||||
|
if(notReservedPageName(form.pageName)) {
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||||
|
} else {
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
get("/:owner/:repository/wiki/_new")(referrersOnly { repository =>
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
if(isEditable(repository)){
|
||||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
html.edit("", None, repository)
|
||||||
|
} else Unauthorized()
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/:owner/:repository/wiki/_new", newForm)(referrersOnly { (form, repository) =>
|
||||||
|
if(isEditable(repository)){
|
||||||
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
|
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
||||||
form.content, loginAccount, form.message.getOrElse(""), None)
|
form.content, loginAccount, form.message.getOrElse(""), None)
|
||||||
|
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||||
|
|
||||||
if(notReservedPageName(form.pageName)) {
|
if(notReservedPageName(form.pageName)) {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
|
||||||
} else {
|
} else {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_delete")(referrersOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
if(isEditable(repository)){
|
||||||
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
deleteWikiPage(repository.owner, repository.name, pageName, loginAccount.fullName, loginAccount.mailAddress, s"Destroyed ${pageName}")
|
deleteWikiPage(repository.owner, repository.name, pageName, loginAccount.fullName, loginAccount.mailAddress, s"Destroyed ${pageName}")
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
}
|
}
|
||||||
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||||
html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository))
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master") match {
|
JGitUtil.getCommitLog(git, "master") match {
|
||||||
case Right((logs, hasNext)) => html.history(None, logs, repository)
|
case Right((logs, hasNext)) => html.history(None, logs, repository)
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -226,4 +240,9 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
||||||
|
|
||||||
|
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean =
|
||||||
|
repository.repository.allowWikiEditing || (
|
||||||
|
hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package gitbucket.core.model
|
|
||||||
|
|
||||||
trait PluginComponent extends TemplateComponent { self: Profile =>
|
|
||||||
import profile.simple._
|
|
||||||
import self._
|
|
||||||
|
|
||||||
lazy val Plugins = TableQuery[Plugins]
|
|
||||||
|
|
||||||
class Plugins(tag: Tag) extends Table[Plugin](tag, "PLUGIN"){
|
|
||||||
val pluginId = column[String]("PLUGIN_ID", O PrimaryKey)
|
|
||||||
val version = column[String]("VERSION")
|
|
||||||
def * = (pluginId, version) <> (Plugin.tupled, Plugin.unapply)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Plugin(
|
|
||||||
pluginId: String,
|
|
||||||
version: String
|
|
||||||
)
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package gitbucket.core.model
|
package gitbucket.core.model
|
||||||
|
|
||||||
|
import gitbucket.core.util.DatabaseConfig
|
||||||
|
|
||||||
trait Profile {
|
trait Profile {
|
||||||
val profile: slick.driver.JdbcProfile
|
val profile: slick.driver.JdbcProfile
|
||||||
@@ -28,7 +29,9 @@ trait Profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trait ProfileProvider { self: Profile =>
|
trait ProfileProvider { self: Profile =>
|
||||||
val profile = slick.driver.H2Driver
|
|
||||||
|
lazy val profile = DatabaseConfig.slickDriver
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trait CoreProfile extends ProfileProvider with Profile
|
trait CoreProfile extends ProfileProvider with Profile
|
||||||
@@ -49,7 +52,6 @@ trait CoreProfile extends ProfileProvider with Profile
|
|||||||
with SshKeyComponent
|
with SshKeyComponent
|
||||||
with WebHookComponent
|
with WebHookComponent
|
||||||
with WebHookEventComponent
|
with WebHookEventComponent
|
||||||
with PluginComponent
|
|
||||||
with ProtectedBranchComponent
|
with ProtectedBranchComponent
|
||||||
|
|
||||||
object Profile extends CoreProfile
|
object Profile extends CoreProfile
|
||||||
|
|||||||
@@ -17,7 +17,14 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
|||||||
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
|
||||||
val parentUserName = column[String]("PARENT_USER_NAME")
|
val parentUserName = column[String]("PARENT_USER_NAME")
|
||||||
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
|
||||||
def * = (userName, repositoryName, isPrivate, description.?, defaultBranch, registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?) <> (Repository.tupled, Repository.unapply)
|
val enableIssues = column[Boolean]("ENABLE_ISSUES")
|
||||||
|
val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL")
|
||||||
|
val enableWiki = column[Boolean]("ENABLE_WIKI")
|
||||||
|
val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING")
|
||||||
|
val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL")
|
||||||
|
def * = (userName, repositoryName, isPrivate, description.?, defaultBranch,
|
||||||
|
registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?,
|
||||||
|
enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?) <> (Repository.tupled, Repository.unapply)
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository)
|
||||||
}
|
}
|
||||||
@@ -35,5 +42,10 @@ case class Repository(
|
|||||||
originUserName: Option[String],
|
originUserName: Option[String],
|
||||||
originRepositoryName: Option[String],
|
originRepositoryName: Option[String],
|
||||||
parentUserName: Option[String],
|
parentUserName: Option[String],
|
||||||
parentRepositoryName: Option[String]
|
parentRepositoryName: Option[String],
|
||||||
|
enableIssues: Boolean,
|
||||||
|
externalIssuesUrl: Option[String],
|
||||||
|
enableWiki: Boolean,
|
||||||
|
allowWikiEditing: Boolean,
|
||||||
|
externalWikiUrl: Option[String]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import gitbucket.core.model.Account
|
|||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.Version
|
import io.github.gitbucket.solidbase.model.Version
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trait for define plugin interface.
|
* Trait for define plugin interface.
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
|||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.DatabaseConfig
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.JDBCUtil._
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
import gitbucket.core.util.{Version, Versions}
|
import io.github.gitbucket.solidbase.model.Module
|
||||||
|
import liquibase.database.core.H2Database
|
||||||
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
@@ -187,30 +189,15 @@ object PluginRegistry {
|
|||||||
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin]
|
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin]
|
||||||
|
|
||||||
// Migration
|
// Migration
|
||||||
val headVersion = plugin.versions.head
|
val solidbase = new Solidbase()
|
||||||
val currentVersion = conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match {
|
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
|
||||||
case Some(x) => {
|
|
||||||
val dim = x.split("\\.")
|
|
||||||
Version(dim(0).toInt, dim(1).toInt)
|
|
||||||
}
|
|
||||||
case None => Version(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
Versions.update(conn, headVersion, currentVersion, plugin.versions, new URLClassLoader(Array(pluginJar.toURI.toURL))){ conn =>
|
|
||||||
currentVersion.versionString match {
|
|
||||||
case "0.0" =>
|
|
||||||
conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, headVersion.versionString)
|
|
||||||
case _ =>
|
|
||||||
conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", headVersion.versionString, plugin.pluginId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
plugin.initialize(instance, context, settings)
|
plugin.initialize(instance, context, settings)
|
||||||
instance.addPlugin(PluginInfo(
|
instance.addPlugin(PluginInfo(
|
||||||
pluginId = plugin.pluginId,
|
pluginId = plugin.pluginId,
|
||||||
pluginName = plugin.pluginName,
|
pluginName = plugin.pluginName,
|
||||||
version = plugin.versions.head.versionString,
|
version = plugin.versions.head.getVersion,
|
||||||
description = plugin.description,
|
description = plugin.description,
|
||||||
pluginClass = plugin
|
pluginClass = plugin
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ trait AccountService {
|
|||||||
Accounts filter (_.removed === false.bind) sortBy(_.userName) list
|
Accounts filter (_.removed === false.bind) sortBy(_.userName) list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def isLastAdministrator(account: Account)(implicit s: Session): Boolean = {
|
||||||
|
if(account.isAdmin){
|
||||||
|
(Accounts filter (_.removed === false.bind) filter (_.isAdmin === true.bind) map (_.userName.length)).first == 1
|
||||||
|
} else false
|
||||||
|
}
|
||||||
|
|
||||||
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
|
def createAccount(userName: String, password: String, fullName: String, mailAddress: String, isAdmin: Boolean, url: Option[String])
|
||||||
(implicit s: Session): Unit =
|
(implicit s: Session): Unit =
|
||||||
Accounts insert Account(
|
Accounts insert Account(
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ trait IssuesService {
|
|||||||
import IssuesService._
|
import IssuesService._
|
||||||
|
|
||||||
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
||||||
if (issueId forall (_.isDigit))
|
if (isInteger(issueId))
|
||||||
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
||||||
else None
|
else None
|
||||||
|
|
||||||
@@ -108,25 +108,41 @@ trait IssuesService {
|
|||||||
}
|
}
|
||||||
import gitbucket.core.model.Profile.commitStateColumnType
|
import gitbucket.core.model.Profile.commitStateColumnType
|
||||||
val query = Q.query[Seq[(String, String, Int)], (String, String, Int, Int, Int, Option[String], Option[CommitState], Option[String], Option[String])](s"""
|
val query = Q.query[Seq[(String, String, Int)], (String, String, Int, Int, Int, Option[String], Option[CommitState], Option[String], Option[String])](s"""
|
||||||
SELECT SUMM.USER_NAME, SUMM.REPOSITORY_NAME, SUMM.ISSUE_ID, CS_ALL, CS_SUCCESS
|
SELECT
|
||||||
, CSD.CONTEXT, CSD.STATE, CSD.TARGET_URL, CSD.DESCRIPTION
|
SUMM.USER_NAME,
|
||||||
FROM (SELECT
|
SUMM.REPOSITORY_NAME,
|
||||||
PR.USER_NAME
|
SUMM.ISSUE_ID,
|
||||||
, PR.REPOSITORY_NAME
|
CS_ALL,
|
||||||
, PR.ISSUE_ID
|
CS_SUCCESS,
|
||||||
, COUNT(CS.STATE) AS CS_ALL
|
CSD.CONTEXT,
|
||||||
, SUM(CS.STATE='success') AS CS_SUCCESS
|
CSD.STATE,
|
||||||
, PR.COMMIT_ID_TO AS COMMIT_ID
|
CSD.TARGET_URL,
|
||||||
|
CSD.DESCRIPTION
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
PR.USER_NAME,
|
||||||
|
PR.REPOSITORY_NAME,
|
||||||
|
PR.ISSUE_ID,
|
||||||
|
COUNT(CS.STATE) AS CS_ALL,
|
||||||
|
CSS.CS_SUCCESS AS CS_SUCCESS,
|
||||||
|
PR.COMMIT_ID_TO AS COMMIT_ID
|
||||||
FROM PULL_REQUEST PR
|
FROM PULL_REQUEST PR
|
||||||
JOIN COMMIT_STATUS CS
|
JOIN COMMIT_STATUS CS
|
||||||
ON PR.USER_NAME=CS.USER_NAME
|
ON PR.USER_NAME = CS.USER_NAME AND PR.REPOSITORY_NAME = CS.REPOSITORY_NAME AND PR.COMMIT_ID_TO = CS.COMMIT_ID
|
||||||
AND PR.REPOSITORY_NAME=CS.REPOSITORY_NAME
|
JOIN (
|
||||||
AND PR.COMMIT_ID_TO=CS.COMMIT_ID
|
SELECT
|
||||||
WHERE $issueIdQuery
|
COUNT(*) AS CS_SUCCESS,
|
||||||
GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID) as SUMM
|
USER_NAME,
|
||||||
|
REPOSITORY_NAME,
|
||||||
|
COMMIT_ID
|
||||||
|
FROM COMMIT_STATUS WHERE STATE = 'success' GROUP BY USER_NAME, REPOSITORY_NAME, COMMIT_ID
|
||||||
|
) CSS ON PR.USER_NAME = CSS.USER_NAME AND PR.REPOSITORY_NAME = CSS.REPOSITORY_NAME AND PR.COMMIT_ID_TO = CSS.COMMIT_ID
|
||||||
|
WHERE $issueIdQuery
|
||||||
|
GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID, CSS.CS_SUCCESS
|
||||||
|
) as SUMM
|
||||||
LEFT OUTER JOIN COMMIT_STATUS CSD
|
LEFT OUTER JOIN COMMIT_STATUS CSD
|
||||||
ON SUMM.CS_ALL = 1 AND SUMM.COMMIT_ID = CSD.COMMIT_ID""");
|
ON SUMM.CS_ALL = 1 AND SUMM.COMMIT_ID = CSD.COMMIT_ID""");
|
||||||
query(issueList).list.map{
|
query(issueList).list.map {
|
||||||
case(userName, repositoryName, issueId, count, successCount, context, state, targetUrl, description) =>
|
case(userName, repositoryName, issueId, count, successCount, context, state, targetUrl, description) =>
|
||||||
(userName, repositoryName, issueId) -> CommitStatusInfo(count, successCount, context, state, targetUrl, description)
|
(userName, repositoryName, issueId) -> CommitStatusInfo(count, successCount, context, state, targetUrl, description)
|
||||||
}.toMap
|
}.toMap
|
||||||
@@ -218,9 +234,8 @@ trait IssuesService {
|
|||||||
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
.map { case (owner, repository) => t1.byRepository(owner, repository) }
|
||||||
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
.foldLeft[Column[Boolean]](false) ( _ || _ ) &&
|
||||||
(t1.closed === (condition.state == "closed").bind) &&
|
(t1.closed === (condition.state == "closed").bind) &&
|
||||||
//(t1.milestoneId === condition.milestoneId.get.get.bind, condition.milestoneId.flatten.isDefined) &&
|
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
|
||||||
(t1.milestoneId.? isEmpty, condition.milestone == Some(None)) &&
|
(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) &&
|
||||||
(t1.assignedUserName === condition.assigned.get.bind, condition.assigned.isDefined) &&
|
|
||||||
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||||
(t1.pullRequest === pullRequest.bind) &&
|
(t1.pullRequest === pullRequest.bind) &&
|
||||||
// Milestone filter
|
// Milestone filter
|
||||||
@@ -228,6 +243,8 @@ trait IssuesService {
|
|||||||
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
|
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
|
||||||
(t2.title === condition.milestone.get.get.bind)
|
(t2.title === condition.milestone.get.get.bind)
|
||||||
} exists, condition.milestone.flatten.isDefined) &&
|
} exists, condition.milestone.flatten.isDefined) &&
|
||||||
|
// Assignee filter
|
||||||
|
(t1.assignedUserName === condition.assigned.get.get.bind, condition.assigned.flatten.isDefined) &&
|
||||||
// Label filter
|
// Label filter
|
||||||
(IssueLabels filter { t2 =>
|
(IssueLabels filter { t2 =>
|
||||||
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
(t2.byIssue(t1.userName, t1.repositoryName, t1.issueId)) &&
|
||||||
@@ -431,7 +448,7 @@ object IssuesService {
|
|||||||
labels: Set[String] = Set.empty,
|
labels: Set[String] = Set.empty,
|
||||||
milestone: Option[Option[String]] = None,
|
milestone: Option[Option[String]] = None,
|
||||||
author: Option[String] = None,
|
author: Option[String] = None,
|
||||||
assigned: Option[String] = None,
|
assigned: Option[Option[String]] = None,
|
||||||
mentioned: Option[String] = None,
|
mentioned: Option[String] = None,
|
||||||
state: String = "open",
|
state: String = "open",
|
||||||
sort: String = "created",
|
sort: String = "created",
|
||||||
@@ -475,12 +492,15 @@ object IssuesService {
|
|||||||
def toURL: String =
|
def toURL: String =
|
||||||
"?" + List(
|
"?" + List(
|
||||||
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
if(labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||||
milestone.map { _ match {
|
milestone.map {
|
||||||
case Some(x) => "milestone=" + urlEncode(x)
|
case Some(x) => "milestone=" + urlEncode(x)
|
||||||
case None => "milestone=none"
|
case None => "milestone=none"
|
||||||
}},
|
},
|
||||||
author .map(x => "author=" + urlEncode(x)),
|
author .map(x => "author=" + urlEncode(x)),
|
||||||
assigned .map(x => "assigned=" + urlEncode(x)),
|
assigned.map {
|
||||||
|
case Some(x) => "assigned=" + urlEncode(x)
|
||||||
|
case None => "assigned=none"
|
||||||
|
},
|
||||||
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
||||||
Some("state=" + urlEncode(state)),
|
Some("state=" + urlEncode(state)),
|
||||||
Some("sort=" + urlEncode(sort)),
|
Some("sort=" + urlEncode(sort)),
|
||||||
@@ -525,10 +545,14 @@ object IssuesService {
|
|||||||
conditions.get("milestone").flatMap(_.headOption) match {
|
conditions.get("milestone").flatMap(_.headOption) match {
|
||||||
case None => None
|
case None => None
|
||||||
case Some("none") => Some(None)
|
case Some("none") => Some(None)
|
||||||
case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x))
|
case Some(x) => Some(Some(x))
|
||||||
},
|
},
|
||||||
conditions.get("author").flatMap(_.headOption),
|
conditions.get("author").flatMap(_.headOption),
|
||||||
conditions.get("assignee").flatMap(_.headOption),
|
conditions.get("assignee").flatMap(_.headOption) match {
|
||||||
|
case None => None
|
||||||
|
case Some("none") => Some(None)
|
||||||
|
case Some(x) => Some(Some(x))
|
||||||
|
},
|
||||||
conditions.get("mentions").flatMap(_.headOption),
|
conditions.get("mentions").flatMap(_.headOption),
|
||||||
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
|
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
|
||||||
sort,
|
sort,
|
||||||
@@ -549,7 +573,10 @@ object IssuesService {
|
|||||||
case x => Some(x)
|
case x => Some(x)
|
||||||
},
|
},
|
||||||
param(request, "author"),
|
param(request, "author"),
|
||||||
param(request, "assigned"),
|
param(request, "assigned").map {
|
||||||
|
case "none" => None
|
||||||
|
case x => Some(x)
|
||||||
|
},
|
||||||
param(request, "mentioned"),
|
param(request, "mentioned"),
|
||||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||||
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
param(request, "sort", Seq("created", "comments", "updated")).getOrElse("created"),
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package gitbucket.core.service
|
|
||||||
|
|
||||||
import gitbucket.core.model.Plugin
|
|
||||||
import gitbucket.core.model.Profile._
|
|
||||||
import profile.simple._
|
|
||||||
|
|
||||||
trait PluginService {
|
|
||||||
|
|
||||||
def getPlugins()(implicit s: Session): List[Plugin] =
|
|
||||||
Plugins.sortBy(_.pluginId).list
|
|
||||||
|
|
||||||
def registerPlugin(plugin: Plugin)(implicit s: Session): Unit =
|
|
||||||
Plugins.insert(plugin)
|
|
||||||
|
|
||||||
def updatePlugin(plugin: Plugin)(implicit s: Session): Unit =
|
|
||||||
Plugins.filter(_.pluginId === plugin.pluginId.bind).map(_.version).update(plugin.version)
|
|
||||||
|
|
||||||
def deletePlugin(pluginId: String)(implicit s: Session): Unit =
|
|
||||||
Plugins.filter(_.pluginId === pluginId.bind).delete
|
|
||||||
|
|
||||||
def getPlugin(pluginId: String)(implicit s: Session): Option[Plugin] =
|
|
||||||
Plugins.filter(_.pluginId === pluginId.bind).firstOption
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -36,7 +36,13 @@ trait RepositoryService { self: AccountService =>
|
|||||||
originUserName = originUserName,
|
originUserName = originUserName,
|
||||||
originRepositoryName = originRepositoryName,
|
originRepositoryName = originRepositoryName,
|
||||||
parentUserName = parentUserName,
|
parentUserName = parentUserName,
|
||||||
parentRepositoryName = parentRepositoryName)
|
parentRepositoryName = parentRepositoryName,
|
||||||
|
enableIssues = true,
|
||||||
|
externalIssuesUrl = None,
|
||||||
|
enableWiki = true,
|
||||||
|
allowWikiEditing = true,
|
||||||
|
externalWikiUrl = None
|
||||||
|
)
|
||||||
|
|
||||||
IssueId insert (userName, repositoryName, 0)
|
IssueId insert (userName, repositoryName, 0)
|
||||||
}
|
}
|
||||||
@@ -222,7 +228,7 @@ trait RepositoryService { self: AccountService =>
|
|||||||
* Include public repository, private own repository and private but collaborator repository.
|
* Include public repository, private own repository and private but collaborator repository.
|
||||||
*
|
*
|
||||||
* @param userName the user name of collaborator
|
* @param userName the user name of collaborator
|
||||||
* @return the repository infomation list
|
* @return the repository information list
|
||||||
*/
|
*/
|
||||||
def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = {
|
def getAllRepositories(userName: String)(implicit s: Session): List[(String, String)] = {
|
||||||
Repositories.filter { t1 =>
|
Repositories.filter { t1 =>
|
||||||
@@ -313,10 +319,12 @@ trait RepositoryService { self: AccountService =>
|
|||||||
* Save repository options.
|
* Save repository options.
|
||||||
*/
|
*/
|
||||||
def saveRepositoryOptions(userName: String, repositoryName: String,
|
def saveRepositoryOptions(userName: String, repositoryName: String,
|
||||||
description: Option[String], isPrivate: Boolean)(implicit s: Session): Unit =
|
description: Option[String], isPrivate: Boolean,
|
||||||
|
enableIssues: Boolean, externalIssuesUrl: Option[String],
|
||||||
|
enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String])(implicit s: Session): Unit =
|
||||||
Repositories.filter(_.byRepository(userName, repositoryName))
|
Repositories.filter(_.byRepository(userName, repositoryName))
|
||||||
.map { r => (r.description.?, r.isPrivate, r.updatedDate) }
|
.map { r => (r.description.?, r.isPrivate, r.enableIssues, r.externalIssuesUrl.?, r.enableWiki, r.allowWikiEditing, r.externalWikiUrl.?, r.updatedDate) }
|
||||||
.update (description, isPrivate, currentDate)
|
.update (description, isPrivate, enableIssues, externalIssuesUrl, enableWiki, allowWikiEditing, externalWikiUrl, currentDate)
|
||||||
|
|
||||||
def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
|
def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
|
||||||
defaultBranch: String)(implicit s: Session): Unit =
|
defaultBranch: String)(implicit s: Session): Unit =
|
||||||
@@ -417,9 +425,7 @@ object RepositoryService {
|
|||||||
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
|
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
|
||||||
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
|
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
|
||||||
if(context.settings.ssh){
|
if(context.settings.ssh){
|
||||||
context.loginAccount.flatMap { loginAccount =>
|
context.settings.sshAddress.map { x => s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" }
|
||||||
context.settings.sshAddress.map { x => s"ssh://${loginAccount.userName}@${x.host}:${x.port}/${owner}/${name}.git" }
|
|
||||||
}
|
|
||||||
} else None
|
} else None
|
||||||
def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}"
|
def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}"
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ trait SshKeyService {
|
|||||||
def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
|
def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
|
||||||
SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
|
SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
|
||||||
|
|
||||||
|
def getAllKeys()(implicit s: Session): List[SshKey] =
|
||||||
|
SshKeys.filter(_.publicKey.trim =!= "").list
|
||||||
|
|
||||||
def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
|
def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
|
||||||
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
|
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
|
||||||
|
|
||||||
|
|||||||
@@ -141,7 +141,11 @@ object SystemSettingsService {
|
|||||||
for {
|
for {
|
||||||
host <- sshHost if ssh
|
host <- sshHost if ssh
|
||||||
}
|
}
|
||||||
yield SshAddress(host, sshPort.getOrElse(DefaultSshPort))
|
yield SshAddress(
|
||||||
|
host,
|
||||||
|
sshPort.getOrElse(DefaultSshPort),
|
||||||
|
"git"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Ldap(
|
case class Ldap(
|
||||||
@@ -169,7 +173,8 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
case class SshAddress(
|
case class SshAddress(
|
||||||
host:String,
|
host:String,
|
||||||
port:Int)
|
port:Int,
|
||||||
|
genericUser:String)
|
||||||
|
|
||||||
val DefaultSshPort = 29418
|
val DefaultSshPort = 29418
|
||||||
val DefaultSmtpPort = 25
|
val DefaultSmtpPort = 25
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ trait WebHookService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try{
|
try{
|
||||||
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
|
val httpClient = HttpClientBuilder.create.useSystemProperties.addInterceptorLast(itcp).build
|
||||||
logger.debug(s"start web hook invocation for ${webHook.url}")
|
logger.debug(s"start web hook invocation for ${webHook.url}")
|
||||||
val httpPost = new HttpPost(webHook.url)
|
val httpPost = new HttpPost(webHook.url)
|
||||||
logger.info(s"Content-Type: ${webHook.ctype.ctype}")
|
logger.info(s"Content-Type: ${webHook.ctype.ctype}")
|
||||||
@@ -119,7 +119,7 @@ trait WebHookService {
|
|||||||
}
|
}
|
||||||
case WebHookContentType.JSON => {
|
case WebHookContentType.JSON => {
|
||||||
httpPost.setEntity(EntityBuilder.create().setText(json).build())
|
httpPost.setEntity(EntityBuilder.create().setText(json).build())
|
||||||
if (!webHook.token.isEmpty) {
|
if (webHook.token.exists(_.trim.nonEmpty)) {
|
||||||
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8")))
|
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8")))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
package gitbucket.core.servlet
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.sql.{DriverManager, Connection}
|
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
|
||||||
import gitbucket.core.util._
|
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import Directory._
|
|
||||||
import ControlUtil._
|
|
||||||
import JDBCUtil._
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import gitbucket.core.util.Versions
|
|
||||||
import gitbucket.core.util.Directory
|
|
||||||
|
|
||||||
object AutoUpdate {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
|
||||||
*/
|
|
||||||
val versions = Seq(
|
|
||||||
new Version(3, 14),
|
|
||||||
new Version(3, 13),
|
|
||||||
new Version(3, 12),
|
|
||||||
new Version(3, 11),
|
|
||||||
new Version(3, 10),
|
|
||||||
new Version(3, 9),
|
|
||||||
new Version(3, 8),
|
|
||||||
new Version(3, 7) with SystemSettingsService {
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
val settings = loadSystemSettings()
|
|
||||||
if(settings.notification){
|
|
||||||
saveSystemSettings(settings.copy(useSMTP = true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Version(3, 6),
|
|
||||||
new Version(3, 5),
|
|
||||||
new Version(3, 4),
|
|
||||||
new Version(3, 3),
|
|
||||||
new Version(3, 2),
|
|
||||||
new Version(3, 1),
|
|
||||||
new Version(3, 0),
|
|
||||||
new Version(2, 8),
|
|
||||||
new Version(2, 7) {
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
conn.select("SELECT * FROM REPOSITORY"){ rs =>
|
|
||||||
// Rename attached files directory from /issues to /comments
|
|
||||||
val userName = rs.getString("USER_NAME")
|
|
||||||
val repoName = rs.getString("REPOSITORY_NAME")
|
|
||||||
defining(Directory.getAttachedDir(userName, repoName)){ newDir =>
|
|
||||||
val oldDir = new File(newDir.getParentFile, "issues")
|
|
||||||
if(oldDir.exists && oldDir.isDirectory){
|
|
||||||
oldDir.renameTo(newDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME if it does not exist
|
|
||||||
val originalUserName = rs.getString("ORIGIN_USER_NAME")
|
|
||||||
val originalRepoName = rs.getString("ORIGIN_REPOSITORY_NAME")
|
|
||||||
if(originalUserName != null && originalRepoName != null){
|
|
||||||
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
|
|
||||||
originalUserName, originalRepoName) == 0){
|
|
||||||
conn.update("UPDATE REPOSITORY SET ORIGIN_USER_NAME = NULL, ORIGIN_REPOSITORY_NAME = NULL " +
|
|
||||||
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Update PARENT_USER_NAME and PARENT_REPOSITORY_NAME if it does not exist
|
|
||||||
val parentUserName = rs.getString("PARENT_USER_NAME")
|
|
||||||
val parentRepoName = rs.getString("PARENT_REPOSITORY_NAME")
|
|
||||||
if(parentUserName != null && parentRepoName != null){
|
|
||||||
if(conn.selectInt("SELECT COUNT(*) FROM REPOSITORY WHERE USER_NAME = ? AND REPOSITORY_NAME = ?",
|
|
||||||
parentUserName, parentRepoName) == 0){
|
|
||||||
conn.update("UPDATE REPOSITORY SET PARENT_USER_NAME = NULL, PARENT_REPOSITORY_NAME = NULL " +
|
|
||||||
"WHERE USER_NAME = ? AND REPOSITORY_NAME = ?", userName, repoName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Version(2, 6),
|
|
||||||
new Version(2, 5),
|
|
||||||
new Version(2, 4),
|
|
||||||
new Version(2, 3) {
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
conn.select("SELECT ACTIVITY_ID, ADDITIONAL_INFO FROM ACTIVITY WHERE ACTIVITY_TYPE='push'"){ rs =>
|
|
||||||
val curInfo = rs.getString("ADDITIONAL_INFO")
|
|
||||||
val newInfo = curInfo.split("\n").filter(_ matches "^[0-9a-z]{40}:.*").mkString("\n")
|
|
||||||
if (curInfo != newInfo) {
|
|
||||||
conn.update("UPDATE ACTIVITY SET ADDITIONAL_INFO = ? WHERE ACTIVITY_ID = ?", newInfo, rs.getInt("ACTIVITY_ID"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ignore {
|
|
||||||
FileUtils.deleteDirectory(Directory.getPluginCacheDir())
|
|
||||||
//FileUtils.deleteDirectory(new File(Directory.PluginHome))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Version(2, 2),
|
|
||||||
new Version(2, 1),
|
|
||||||
new Version(2, 0){
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
import eu.medsea.mimeutil.{MimeUtil2, MimeType}
|
|
||||||
|
|
||||||
val mimeUtil = new MimeUtil2()
|
|
||||||
mimeUtil.registerMimeDetector("eu.medsea.mimeutil.detector.MagicMimeMimeDetector")
|
|
||||||
|
|
||||||
super.update(conn, cl)
|
|
||||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
|
||||||
defining(Directory.getAttachedDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME"))){ dir =>
|
|
||||||
if(dir.exists && dir.isDirectory){
|
|
||||||
dir.listFiles.foreach { file =>
|
|
||||||
if(file.getName.indexOf('.') < 0){
|
|
||||||
val mimeType = MimeUtil2.getMostSpecificMimeType(mimeUtil.getMimeTypes(file, new MimeType("application/octet-stream"))).toString
|
|
||||||
if(mimeType.startsWith("image/")){
|
|
||||||
file.renameTo(new File(file.getParent, file.getName + "." + mimeType.split("/")(1)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Version(1, 13),
|
|
||||||
Version(1, 12),
|
|
||||||
Version(1, 11),
|
|
||||||
Version(1, 10),
|
|
||||||
Version(1, 9),
|
|
||||||
Version(1, 8),
|
|
||||||
Version(1, 7),
|
|
||||||
Version(1, 6),
|
|
||||||
Version(1, 5),
|
|
||||||
Version(1, 4),
|
|
||||||
new Version(1, 3){
|
|
||||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
super.update(conn, cl)
|
|
||||||
// Fix wiki repository configuration
|
|
||||||
conn.select("SELECT USER_NAME, REPOSITORY_NAME FROM REPOSITORY"){ rs =>
|
|
||||||
using(Git.open(getWikiRepositoryDir(rs.getString("USER_NAME"), rs.getString("REPOSITORY_NAME")))){ git =>
|
|
||||||
defining(git.getRepository.getConfig){ config =>
|
|
||||||
if(!config.getBoolean("http", "receivepack", false)){
|
|
||||||
config.setBoolean("http", null, "receivepack", true)
|
|
||||||
config.save
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Version(1, 2),
|
|
||||||
Version(1, 1),
|
|
||||||
Version(1, 0),
|
|
||||||
Version(0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The head version of GitBucket.
|
|
||||||
*/
|
|
||||||
val headVersion = versions.head
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The version file (GITBUCKET_HOME/version).
|
|
||||||
*/
|
|
||||||
lazy val versionFile = new File(GitBucketHome, "version")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current version from the version file.
|
|
||||||
*/
|
|
||||||
def getCurrentVersion(): Version = {
|
|
||||||
if(versionFile.exists){
|
|
||||||
FileUtils.readFileToString(versionFile, "UTF-8").trim.split("\\.") match {
|
|
||||||
case Array(majorVersion, minorVersion) => {
|
|
||||||
versions.find { v =>
|
|
||||||
v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt
|
|
||||||
}.getOrElse(Version(0, 0))
|
|
||||||
}
|
|
||||||
case _ => Version(0, 0)
|
|
||||||
}
|
|
||||||
} else Version(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
package gitbucket.core.servlet
|
package gitbucket.core.servlet
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
import akka.event.Logging
|
import akka.event.Logging
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import gitbucket.core.GitBucketCoreModule
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
||||||
import org.apache.commons.io.FileUtils
|
import gitbucket.core.util.DatabaseConfig
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.JDBCUtil._
|
||||||
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import gitbucket.core.util.Versions
|
|
||||||
import akka.actor.{Actor, Props, ActorSystem}
|
import akka.actor.{Actor, Props, ActorSystem}
|
||||||
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
||||||
import AutoUpdate._
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize GitBucket system.
|
* Initialize GitBucket system.
|
||||||
@@ -30,14 +36,49 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
|||||||
Database() withTransaction { session =>
|
Database() withTransaction { session =>
|
||||||
val conn = session.conn
|
val conn = session.conn
|
||||||
|
|
||||||
// Migration
|
// Check version
|
||||||
logger.debug("Start schema update")
|
val versionFile = new File(GitBucketHome, "version")
|
||||||
Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn =>
|
|
||||||
FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
|
if(versionFile.exists()){
|
||||||
|
val version = FileUtils.readFileToString(versionFile, "UTF-8")
|
||||||
|
if(version == "3.14"){
|
||||||
|
// Initialization for GitBucket 3.14
|
||||||
|
logger.info("Migration to GitBucket 4.x start")
|
||||||
|
|
||||||
|
// Backup current data
|
||||||
|
val dataMvFile = new File(GitBucketHome, "data.mv.db")
|
||||||
|
if(dataMvFile.exists) {
|
||||||
|
FileUtils.copyFile(dataMvFile, new File(GitBucketHome, "data.mv.db_3.14"))
|
||||||
|
}
|
||||||
|
val dataTraceFile = new File(GitBucketHome, "data.trace.db")
|
||||||
|
if(dataTraceFile.exists) {
|
||||||
|
FileUtils.copyFile(dataTraceFile, new File(GitBucketHome, "data.trace.db_3.14"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change form
|
||||||
|
val manager = new JDBCVersionManager(conn)
|
||||||
|
manager.initialize()
|
||||||
|
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0")
|
||||||
|
conn.select("SELECT PLUGIN_ID, VERSION FROM PLUGIN"){ rs =>
|
||||||
|
manager.updateVersion(rs.getString("PLUGIN_ID"), rs.getString("VERSION"))
|
||||||
|
}
|
||||||
|
conn.update("DROP TABLE PLUGIN")
|
||||||
|
versionFile.delete()
|
||||||
|
|
||||||
|
logger.info("Migration to GitBucket 4.x completed")
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Exception("GitBucket can't migrate from this version. Please update to 3.14 at first.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run normal migration
|
||||||
|
logger.info("Start schema update")
|
||||||
|
val solidbase = new Solidbase()
|
||||||
|
solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
||||||
|
|
||||||
// Load plugins
|
// Load plugins
|
||||||
logger.debug("Initialize plugins")
|
logger.info("Initialize plugins")
|
||||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package gitbucket.core.servlet
|
|||||||
|
|
||||||
import javax.servlet._
|
import javax.servlet._
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import com.mchange.v2.c3p0.ComboPooledDataSource
|
import com.zaxxer.hikari._
|
||||||
import gitbucket.core.util.DatabaseConfig
|
import gitbucket.core.util.DatabaseConfig
|
||||||
import org.scalatra.ScalatraBase
|
import org.scalatra.ScalatraBase
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -46,14 +46,14 @@ object Database {
|
|||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(Database.getClass)
|
private val logger = LoggerFactory.getLogger(Database.getClass)
|
||||||
|
|
||||||
private val dataSource: ComboPooledDataSource = {
|
private val dataSource: HikariDataSource = {
|
||||||
val ds = new ComboPooledDataSource
|
val config = new HikariConfig()
|
||||||
ds.setDriverClass(DatabaseConfig.driver)
|
config.setDriverClassName(DatabaseConfig.jdbcDriver)
|
||||||
ds.setJdbcUrl(DatabaseConfig.url)
|
config.setJdbcUrl(DatabaseConfig.url)
|
||||||
ds.setUser(DatabaseConfig.user)
|
config.setUsername(DatabaseConfig.user)
|
||||||
ds.setPassword(DatabaseConfig.password)
|
config.setPassword(DatabaseConfig.password)
|
||||||
logger.debug("load database connection pool")
|
logger.debug("load database connection pool")
|
||||||
ds
|
new HikariDataSource(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val db: SlickDatabase = {
|
private val db: SlickDatabase = {
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
|||||||
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
|
||||||
import gitbucket.core.servlet.{Database, CommitLogHook}
|
import gitbucket.core.servlet.{Database, CommitLogHook}
|
||||||
import gitbucket.core.util.{Directory, ControlUtil}
|
import gitbucket.core.util.{Directory, ControlUtil}
|
||||||
import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command}
|
import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command, SessionAware}
|
||||||
|
import org.apache.sshd.server.session.ServerSession
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.{File, InputStream, OutputStream}
|
import java.io.{File, InputStream, OutputStream}
|
||||||
import ControlUtil._
|
import ControlUtil._
|
||||||
@@ -20,37 +21,44 @@ object GitCommand {
|
|||||||
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GitCommand() extends Command {
|
abstract class GitCommand extends Command with SessionAware {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||||
@volatile protected var err: OutputStream = null
|
@volatile protected var err: OutputStream = null
|
||||||
@volatile protected var in: InputStream = null
|
@volatile protected var in: InputStream = null
|
||||||
@volatile protected var out: OutputStream = null
|
@volatile protected var out: OutputStream = null
|
||||||
@volatile protected var callback: ExitCallback = null
|
@volatile protected var callback: ExitCallback = null
|
||||||
|
@volatile private var authUser:Option[String] = None
|
||||||
|
|
||||||
protected def runTask(user: String)(implicit session: Session): Unit
|
protected def runTask(authUser: String)(implicit session: Session): Unit
|
||||||
|
|
||||||
private def newTask(user: String): Runnable = new Runnable {
|
private def newTask(): Runnable = new Runnable {
|
||||||
override def run(): Unit = {
|
override def run(): Unit = {
|
||||||
Database() withSession { implicit session =>
|
authUser match {
|
||||||
try {
|
case Some(authUser) =>
|
||||||
runTask(user)
|
Database() withSession { implicit session =>
|
||||||
callback.onExit(0)
|
try {
|
||||||
} catch {
|
runTask(authUser)
|
||||||
case e: RepositoryNotFoundException =>
|
callback.onExit(0)
|
||||||
logger.info(e.getMessage)
|
} catch {
|
||||||
callback.onExit(1, "Repository Not Found")
|
case e: RepositoryNotFoundException =>
|
||||||
case e: Throwable =>
|
logger.info(e.getMessage)
|
||||||
logger.error(e.getMessage, e)
|
callback.onExit(1, "Repository Not Found")
|
||||||
callback.onExit(1)
|
case e: Throwable =>
|
||||||
}
|
logger.error(e.getMessage, e)
|
||||||
|
callback.onExit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
val message = "User not authenticated"
|
||||||
|
logger.error(message)
|
||||||
|
callback.onExit(1, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def start(env: Environment): Unit = {
|
final override def start(env: Environment): Unit = {
|
||||||
val user = env.getEnv.get("USER")
|
val thread = new Thread(newTask())
|
||||||
val thread = new Thread(newTask(user))
|
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +80,10 @@ abstract class GitCommand() extends Command {
|
|||||||
this.in = in
|
this.in = in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def setSession(serverSession:ServerSession) {
|
||||||
|
this.authUser = PublicKeyAuthenticator.getUserName(serverSession)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
|
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class NoShell(sshAddress:SshAddress) extends Factory[Command] {
|
|||||||
private var callback: ExitCallback = null
|
private var callback: ExitCallback = null
|
||||||
|
|
||||||
override def start(env: Environment): Unit = {
|
override def start(env: Environment): Unit = {
|
||||||
val user = env.getEnv.get("USER")
|
|
||||||
val message =
|
val message =
|
||||||
"""
|
"""
|
||||||
| Welcome to
|
| Welcome to
|
||||||
@@ -32,7 +31,7 @@ class NoShell(sshAddress:SshAddress) extends Factory[Command] {
|
|||||||
| Please use:
|
| Please use:
|
||||||
|
|
|
|
||||||
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
|
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
|
||||||
""".stripMargin.format(user, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
|
""".stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
|
||||||
err.write(Constants.encode(message))
|
err.write(Constants.encode(message))
|
||||||
err.flush()
|
err.flush()
|
||||||
in.close()
|
in.close()
|
||||||
|
|||||||
@@ -2,22 +2,73 @@ package gitbucket.core.ssh
|
|||||||
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
|
||||||
|
import gitbucket.core.model.SshKey
|
||||||
import gitbucket.core.service.SshKeyService
|
import gitbucket.core.service.SshKeyService
|
||||||
import gitbucket.core.servlet.Database
|
import gitbucket.core.servlet.Database
|
||||||
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
||||||
import org.apache.sshd.server.session.ServerSession
|
import org.apache.sshd.server.session.ServerSession
|
||||||
|
import org.apache.sshd.common.session.Session
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
class PublicKeyAuthenticator extends PublickeyAuthenticator with SshKeyService {
|
object PublicKeyAuthenticator {
|
||||||
|
// put in the ServerSession here to be read by GitCommand later
|
||||||
|
private val userNameSessionKey = new Session.AttributeKey[String]
|
||||||
|
|
||||||
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
|
def putUserName(serverSession:ServerSession, userName:String):Unit =
|
||||||
Database() withSession { implicit session =>
|
serverSession.setAttribute(userNameSessionKey, userName)
|
||||||
getPublicKeys(username).exists { sshKey =>
|
|
||||||
SshUtil.str2PublicKey(sshKey.publicKey) match {
|
def getUserName(serverSession:ServerSession):Option[String] =
|
||||||
case Some(publicKey) => key.equals(publicKey)
|
Option(serverSession.getAttribute(userNameSessionKey))
|
||||||
case _ => false
|
}
|
||||||
}
|
|
||||||
}
|
class PublicKeyAuthenticator(genericUser:String) extends PublickeyAuthenticator with SshKeyService {
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[PublicKeyAuthenticator])
|
||||||
|
|
||||||
|
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean =
|
||||||
|
if (username == genericUser) authenticateGenericUser(username, key, session, genericUser)
|
||||||
|
else authenticateLoginUser(username, key, session)
|
||||||
|
|
||||||
|
private def authenticateLoginUser(username: String, key: PublicKey, session: ServerSession): Boolean = {
|
||||||
|
val authenticated =
|
||||||
|
Database()
|
||||||
|
.withSession { implicit dbSession => getPublicKeys(username) }
|
||||||
|
.map(_.publicKey)
|
||||||
|
.flatMap(SshUtil.str2PublicKey)
|
||||||
|
.contains(key)
|
||||||
|
if (authenticated) {
|
||||||
|
logger.info(s"authentication as ssh user ${username} succeeded")
|
||||||
|
PublicKeyAuthenticator.putUserName(session, username)
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
logger.info(s"authentication as ssh user ${username} failed")
|
||||||
|
}
|
||||||
|
authenticated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def authenticateGenericUser(username: String, key: PublicKey, session: ServerSession, genericUser:String): Boolean = {
|
||||||
|
// find all users having the key we got from ssh
|
||||||
|
val possibleUserNames =
|
||||||
|
Database()
|
||||||
|
.withSession { implicit dbSession => getAllKeys() }
|
||||||
|
.filter { sshKey =>
|
||||||
|
SshUtil.str2PublicKey(sshKey.publicKey).exists(_ == key)
|
||||||
|
}
|
||||||
|
.map(_.userName)
|
||||||
|
.distinct
|
||||||
|
// determine the user - if different accounts share the same key, tough luck
|
||||||
|
val uniqueUserName =
|
||||||
|
possibleUserNames match {
|
||||||
|
case List() =>
|
||||||
|
logger.info(s"authentication as generic user ${genericUser} failed, public key not found")
|
||||||
|
None
|
||||||
|
case List(name) =>
|
||||||
|
logger.info(s"authentication as generic user ${genericUser} succeeded, identified ${name}")
|
||||||
|
Some(name)
|
||||||
|
case _ =>
|
||||||
|
logger.info(s"authentication as generic user ${genericUser} failed, public key is ambiguous")
|
||||||
|
None
|
||||||
|
}
|
||||||
|
uniqueUserName.foreach(PublicKeyAuthenticator.putUserName(session, _))
|
||||||
|
uniqueUserName.isDefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ object SshServer {
|
|||||||
provider.setAlgorithm("RSA")
|
provider.setAlgorithm("RSA")
|
||||||
provider.setOverwriteAllowed(false)
|
provider.setOverwriteAllowed(false)
|
||||||
server.setKeyPairProvider(provider)
|
server.setKeyPairProvider(provider)
|
||||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator)
|
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
|
||||||
server.setCommandFactory(new GitCommandFactory(baseUrl))
|
server.setCommandFactory(new GitCommandFactory(baseUrl))
|
||||||
server.setShellFactory(new NoShell(sshAddress))
|
server.setShellFactory(new NoShell(sshAddress))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,7 @@ object SshUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def fingerPrint(key: String): Option[String] = str2PublicKey(key) match {
|
def fingerPrint(key: String): Option[String] =
|
||||||
case Some(publicKey) => Some(KeyUtils.getFingerPrint(publicKey))
|
str2PublicKey(key) map KeyUtils.getFingerPrint
|
||||||
case None => None
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,83 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import Directory.DatabaseHome
|
import java.io.File
|
||||||
|
import Directory._
|
||||||
|
import liquibase.database.AbstractJdbcDatabase
|
||||||
|
import liquibase.database.core.{PostgresDatabase, MySQLDatabase, H2Database}
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
object DatabaseConfig {
|
object DatabaseConfig {
|
||||||
|
|
||||||
private val config = ConfigFactory.load("database")
|
private lazy val config = {
|
||||||
private val dbUrl = config.getString("db.url")
|
val file = new File(GitBucketHome, "database.conf")
|
||||||
|
if(!file.exists){
|
||||||
|
FileUtils.write(file,
|
||||||
|
"""db {
|
||||||
|
| url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
||||||
|
| user = "sa"
|
||||||
|
| password = "sa"
|
||||||
|
|}
|
||||||
|
|""".stripMargin, "UTF-8")
|
||||||
|
}
|
||||||
|
ConfigFactory.parseFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy val dbUrl = config.getString("db.url")
|
||||||
|
|
||||||
def url(directory: Option[String]): String =
|
def url(directory: Option[String]): String =
|
||||||
dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome))
|
dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome))
|
||||||
|
|
||||||
val url: String = url(None)
|
lazy val url: String = url(None)
|
||||||
val user: String = config.getString("db.user")
|
lazy val user: String = config.getString("db.user")
|
||||||
val password: String = config.getString("db.password")
|
lazy val password: String = config.getString("db.password")
|
||||||
val driver: String = config.getString("db.driver")
|
lazy val jdbcDriver: String = DatabaseType(url).jdbcDriver
|
||||||
|
lazy val slickDriver: slick.driver.JdbcProfile = DatabaseType(url).slickDriver
|
||||||
|
lazy val liquiDriver: AbstractJdbcDatabase = DatabaseType(url).liquiDriver
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed trait DatabaseType {
|
||||||
|
val jdbcDriver: String
|
||||||
|
val slickDriver: slick.driver.JdbcProfile
|
||||||
|
val liquiDriver: AbstractJdbcDatabase
|
||||||
|
}
|
||||||
|
|
||||||
|
object DatabaseType {
|
||||||
|
|
||||||
|
def apply(url: String): DatabaseType = {
|
||||||
|
if(url.startsWith("jdbc:h2:")){
|
||||||
|
H2
|
||||||
|
} else if(url.startsWith("jdbc:mysql:")){
|
||||||
|
MySQL
|
||||||
|
} else if(url.startsWith("jdbc:postgresql:")){
|
||||||
|
PostgreSQL
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(s"${url} is not supported.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object H2 extends DatabaseType {
|
||||||
|
val jdbcDriver = "org.h2.Driver"
|
||||||
|
val slickDriver = slick.driver.H2Driver
|
||||||
|
val liquiDriver = new H2Database()
|
||||||
|
}
|
||||||
|
|
||||||
|
object MySQL extends DatabaseType {
|
||||||
|
val jdbcDriver = "com.mysql.jdbc.Driver"
|
||||||
|
val slickDriver = slick.driver.MySQLDriver
|
||||||
|
val liquiDriver = new MySQLDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
object PostgreSQL extends DatabaseType {
|
||||||
|
val jdbcDriver = "org.postgresql.Driver2"
|
||||||
|
val slickDriver = new slick.driver.PostgresDriver {
|
||||||
|
override def quoteIdentifier(id: String): String = {
|
||||||
|
val s = new StringBuilder(id.length + 4) append '"'
|
||||||
|
for(c <- id) if(c == '"') s append "\"\"" else s append c.toLower
|
||||||
|
(s append '"').toString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val liquiDriver = new PostgresDatabase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
import java.io._
|
||||||
import java.sql._
|
import java.sql._
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import javax.xml.stream.{XMLStreamConstants, XMLInputFactory, XMLOutputFactory}
|
||||||
import ControlUtil._
|
import ControlUtil._
|
||||||
|
import scala.StringBuilder
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
import scala.collection.mutable
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,6 +64,265 @@ object JDBCUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def importAsXML(in: InputStream): Unit = {
|
||||||
|
conn.setAutoCommit(false)
|
||||||
|
try {
|
||||||
|
val factory = XMLInputFactory.newInstance()
|
||||||
|
using(factory.createXMLStreamReader(in, "UTF-8")){ reader =>
|
||||||
|
// stateful objects
|
||||||
|
var elementName = ""
|
||||||
|
var insertTable = ""
|
||||||
|
var insertColumns = Map.empty[String, (String, String)]
|
||||||
|
|
||||||
|
while(reader.hasNext){
|
||||||
|
reader.next()
|
||||||
|
|
||||||
|
reader.getEventType match {
|
||||||
|
case XMLStreamConstants.START_ELEMENT =>
|
||||||
|
elementName = reader.getName.getLocalPart
|
||||||
|
if(elementName == "insert"){
|
||||||
|
insertTable = reader.getAttributeValue(null, "table")
|
||||||
|
} else if(elementName == "delete"){
|
||||||
|
val tableName = reader.getAttributeValue(null, "table")
|
||||||
|
conn.update(s"DELETE FROM ${tableName}")
|
||||||
|
} else if(elementName == "column"){
|
||||||
|
val columnName = reader.getAttributeValue(null, "name")
|
||||||
|
val columnType = reader.getAttributeValue(null, "type")
|
||||||
|
val columnValue = reader.getElementText
|
||||||
|
insertColumns = insertColumns + (columnName -> (columnType, columnValue))
|
||||||
|
}
|
||||||
|
case XMLStreamConstants.END_ELEMENT =>
|
||||||
|
// Execute insert statement
|
||||||
|
reader.getName.getLocalPart match {
|
||||||
|
case "insert" => {
|
||||||
|
val sb = new StringBuilder()
|
||||||
|
sb.append(s"INSERT INTO ${insertTable} (")
|
||||||
|
sb.append(insertColumns.map { case (columnName, _) => columnName }.mkString(", "))
|
||||||
|
sb.append(") VALUES (")
|
||||||
|
sb.append(insertColumns.map { case (_, (columnType, columnValue)) =>
|
||||||
|
if(columnType == null || columnValue == null){
|
||||||
|
"NULL"
|
||||||
|
} else if(columnType == "string"){
|
||||||
|
"'" + columnValue.replace("'", "''") + "'"
|
||||||
|
} else if(columnType == "timestamp"){
|
||||||
|
"'" + columnValue + "'"
|
||||||
|
} else {
|
||||||
|
columnValue.toString
|
||||||
|
}
|
||||||
|
}.mkString(", "))
|
||||||
|
sb.append(")")
|
||||||
|
|
||||||
|
conn.update(sb.toString)
|
||||||
|
|
||||||
|
insertColumns = Map.empty[String, (String, String)] // Clear column information
|
||||||
|
}
|
||||||
|
case _ => // Nothing to do
|
||||||
|
}
|
||||||
|
case _ => // Nothing to do
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
case e: Exception => {
|
||||||
|
conn.rollback()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def exportAsXML(targetTables: Seq[String]): File = {
|
||||||
|
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
|
||||||
|
val file = File.createTempFile("gitbucket-export-", ".xml")
|
||||||
|
|
||||||
|
val factory = XMLOutputFactory.newInstance()
|
||||||
|
using(factory.createXMLStreamWriter(new FileOutputStream(file), "UTF-8")){ writer =>
|
||||||
|
val dbMeta = conn.getMetaData
|
||||||
|
val allTablesInDatabase = allTablesOrderByDependencies(dbMeta)
|
||||||
|
|
||||||
|
writer.writeStartDocument("UTF-8", "1.0")
|
||||||
|
writer.writeStartElement("tables")
|
||||||
|
|
||||||
|
println(allTablesInDatabase.mkString(", "))
|
||||||
|
|
||||||
|
allTablesInDatabase.reverse.foreach { tableName =>
|
||||||
|
if (targetTables.contains(tableName)) {
|
||||||
|
writer.writeStartElement("delete")
|
||||||
|
writer.writeAttribute("table", tableName)
|
||||||
|
writer.writeEndElement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allTablesInDatabase.foreach { tableName =>
|
||||||
|
if (targetTables.contains(tableName)) {
|
||||||
|
select(s"SELECT * FROM ${tableName}") { rs =>
|
||||||
|
writer.writeStartElement("insert")
|
||||||
|
writer.writeAttribute("table", tableName)
|
||||||
|
val rsMeta = rs.getMetaData
|
||||||
|
(1 to rsMeta.getColumnCount).foreach { i =>
|
||||||
|
val columnName = rsMeta.getColumnName(i)
|
||||||
|
val (columnType, columnValue) = if(rs.getObject(columnName) == null){
|
||||||
|
(null, null)
|
||||||
|
} else {
|
||||||
|
rsMeta.getColumnType(i) match {
|
||||||
|
case Types.BOOLEAN | Types.BIT => ("boolean", rs.getBoolean(columnName))
|
||||||
|
case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => ("string", rs.getString(columnName))
|
||||||
|
case Types.INTEGER => ("int", rs.getInt(columnName))
|
||||||
|
case Types.TIMESTAMP => ("timestamp", dateFormat.format(rs.getTimestamp(columnName)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.writeStartElement("column")
|
||||||
|
writer.writeAttribute("name", columnName)
|
||||||
|
if(columnType != null){
|
||||||
|
writer.writeAttribute("type", columnType)
|
||||||
|
}
|
||||||
|
if(columnValue != null){
|
||||||
|
writer.writeCharacters(columnValue.toString)
|
||||||
|
}
|
||||||
|
writer.writeEndElement()
|
||||||
|
}
|
||||||
|
writer.writeEndElement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.writeEndElement()
|
||||||
|
writer.writeEndDocument()
|
||||||
|
}
|
||||||
|
|
||||||
|
file
|
||||||
|
}
|
||||||
|
|
||||||
|
def exportAsSQL(targetTables: Seq[String]): File = {
|
||||||
|
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
|
||||||
|
val file = File.createTempFile("gitbucket-export-", ".sql")
|
||||||
|
|
||||||
|
using(new FileOutputStream(file)) { out =>
|
||||||
|
val dbMeta = conn.getMetaData
|
||||||
|
val allTablesInDatabase = allTablesOrderByDependencies(dbMeta)
|
||||||
|
|
||||||
|
allTablesInDatabase.reverse.foreach { tableName =>
|
||||||
|
if (targetTables.contains(tableName)) {
|
||||||
|
out.write(s"DELETE FROM ${tableName};\n".getBytes("UTF-8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allTablesInDatabase.foreach { tableName =>
|
||||||
|
if (targetTables.contains(tableName)) {
|
||||||
|
val sb = new StringBuilder()
|
||||||
|
select(s"SELECT * FROM ${tableName}") { rs =>
|
||||||
|
sb.append(s"INSERT INTO ${tableName} (")
|
||||||
|
|
||||||
|
val rsMeta = rs.getMetaData
|
||||||
|
val columns = (1 to rsMeta.getColumnCount).map { i =>
|
||||||
|
(rsMeta.getColumnName(i), rsMeta.getColumnType(i))
|
||||||
|
}
|
||||||
|
sb.append(columns.map(_._1).mkString(", "))
|
||||||
|
sb.append(") VALUES (")
|
||||||
|
|
||||||
|
val values = columns.map { case (columnName, columnType) =>
|
||||||
|
if(rs.getObject(columnName) == null){
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
columnType match {
|
||||||
|
case Types.BOOLEAN | Types.BIT => rs.getBoolean(columnName)
|
||||||
|
case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => rs.getString(columnName)
|
||||||
|
case Types.INTEGER => rs.getInt(columnName)
|
||||||
|
case Types.TIMESTAMP => rs.getTimestamp(columnName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val columnValues = values.map { value =>
|
||||||
|
value match {
|
||||||
|
case x: String => "'" + x.replace("'", "''") + "'"
|
||||||
|
case x: Timestamp => "'" + dateFormat.format(x) + "'"
|
||||||
|
case null => "NULL"
|
||||||
|
case x => x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append(columnValues.mkString(", "))
|
||||||
|
sb.append(");\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
out.write(sb.toString.getBytes("UTF-8"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file
|
||||||
|
}
|
||||||
|
|
||||||
|
def allTableNames(): Seq[String] = {
|
||||||
|
using(conn.getMetaData.getTables(null, null, "%", Seq("TABLE").toArray)) { rs =>
|
||||||
|
val tableNames = new ListBuffer[String]
|
||||||
|
while (rs.next) {
|
||||||
|
val name = rs.getString("TABLE_NAME").toUpperCase
|
||||||
|
if (name != "VERSIONS" && name != "PLUGIN") {
|
||||||
|
tableNames += name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableNames.toSeq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def childTables(meta: DatabaseMetaData, tableName: String): Seq[String] = {
|
||||||
|
val normalizedTableName =
|
||||||
|
if(meta.getDatabaseProductName == "PostgreSQL"){
|
||||||
|
tableName.toLowerCase
|
||||||
|
} else {
|
||||||
|
tableName
|
||||||
|
}
|
||||||
|
|
||||||
|
using(meta.getExportedKeys(null, null, normalizedTableName)) { rs =>
|
||||||
|
val children = new ListBuffer[String]
|
||||||
|
while (rs.next) {
|
||||||
|
val childTableName = rs.getString("FKTABLE_NAME").toUpperCase
|
||||||
|
if(!children.contains(childTableName)){
|
||||||
|
children += childTableName
|
||||||
|
children ++= childTables(meta, childTableName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
children.distinct.toSeq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private def allTablesOrderByDependencies(meta: DatabaseMetaData): Seq[String] = {
|
||||||
|
val tables = allTableNames.map { tableName =>
|
||||||
|
val result = TableDependency(tableName, childTables(meta, tableName))
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
val edges = tables.flatMap { table =>
|
||||||
|
table.children.map { child => (table.tableName, child) }
|
||||||
|
}
|
||||||
|
|
||||||
|
tsort(edges).toSeq
|
||||||
|
}
|
||||||
|
|
||||||
|
case class TableDependency(tableName: String, children: Seq[String])
|
||||||
|
|
||||||
|
|
||||||
|
def tsort[A](edges: Traversable[(A, A)]): Iterable[A] = {
|
||||||
|
@tailrec
|
||||||
|
def tsort(toPreds: Map[A, Set[A]], done: Iterable[A]): Iterable[A] = {
|
||||||
|
val (noPreds, hasPreds) = toPreds.partition { _._2.isEmpty }
|
||||||
|
if (noPreds.isEmpty) {
|
||||||
|
if (hasPreds.isEmpty) done else sys.error(hasPreds.toString)
|
||||||
|
} else {
|
||||||
|
val found = noPreds.map { _._1 }
|
||||||
|
tsort(hasPreds.mapValues { _ -- found }, done ++ found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val toPred = edges.foldLeft(Map[A, Set[A]]()) { (acc, e) =>
|
||||||
|
acc + (e._1 -> acc.getOrElse(e._1, Set())) + (e._2 -> (acc.getOrElse(e._2, Set()) + e._1))
|
||||||
|
}
|
||||||
|
tsort(toPred, Seq())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -896,17 +896,13 @@ object JGitUtil {
|
|||||||
git.branchList.call.asScala.map { ref =>
|
git.branchList.call.asScala.map { ref =>
|
||||||
val walk = new RevWalk(repo)
|
val walk = new RevWalk(repo)
|
||||||
try {
|
try {
|
||||||
val defaultCommit = walk.parseCommit(defaultObject)
|
val defaultCommit = walk.parseCommit(defaultObject)
|
||||||
val branchName = ref.getName.stripPrefix("refs/heads/")
|
val branchName = ref.getName.stripPrefix("refs/heads/")
|
||||||
val branchCommit = if(branchName == defaultBranch){
|
val branchCommit = walk.parseCommit(ref.getObjectId)
|
||||||
defaultCommit
|
val when = branchCommit.getCommitterIdent.getWhen
|
||||||
} else {
|
val committer = branchCommit.getCommitterIdent.getName
|
||||||
walk.parseCommit(ref.getObjectId)
|
|
||||||
}
|
|
||||||
val when = branchCommit.getCommitterIdent.getWhen
|
|
||||||
val committer = branchCommit.getCommitterIdent.getName
|
|
||||||
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
|
val committerEmail = branchCommit.getCommitterIdent.getEmailAddress
|
||||||
val mergeInfo = if(origin && branchName == defaultBranch){
|
val mergeInfo = if(origin && branchName == defaultBranch){
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
walk.reset()
|
walk.reset()
|
||||||
|
|||||||
@@ -84,26 +84,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
enableLineBreaks = false
|
enableLineBreaks = false
|
||||||
))) { case (subject, msg) =>
|
))) { case (subject, msg) =>
|
||||||
recipients(issue) { to =>
|
recipients(issue) { to =>
|
||||||
val email = new HtmlEmail
|
send(to, subject, msg)
|
||||||
email.setHostName(smtp.host)
|
|
||||||
email.setSmtpPort(smtp.port.get)
|
|
||||||
smtp.user.foreach { user =>
|
|
||||||
email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
|
|
||||||
}
|
|
||||||
smtp.ssl.foreach { ssl =>
|
|
||||||
email.setSSLOnConnect(ssl)
|
|
||||||
}
|
|
||||||
smtp.fromAddress
|
|
||||||
.map (_ -> smtp.fromName.getOrElse(context.loginAccount.get.userName))
|
|
||||||
.orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
|
|
||||||
.foreach { case (address, name) =>
|
|
||||||
email.setFrom(address, name)
|
|
||||||
}
|
|
||||||
email.setCharset("UTF-8")
|
|
||||||
email.setSubject(subject)
|
|
||||||
email.setHtmlMsg(msg)
|
|
||||||
|
|
||||||
email.addTo(to).send
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,6 +97,30 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
case t => logger.error("Notifications Failed.", t)
|
case t => logger.error("Notifications Failed.", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def send(to: String, subject: String, msg: String)(implicit context: Context): Unit = {
|
||||||
|
val email = new HtmlEmail
|
||||||
|
email.setHostName(smtp.host)
|
||||||
|
email.setSmtpPort(smtp.port.get)
|
||||||
|
smtp.user.foreach { user =>
|
||||||
|
email.setAuthenticator(new DefaultAuthenticator(user, smtp.password.getOrElse("")))
|
||||||
|
}
|
||||||
|
smtp.ssl.foreach { ssl =>
|
||||||
|
email.setSSLOnConnect(ssl)
|
||||||
|
}
|
||||||
|
smtp.fromAddress
|
||||||
|
.map (_ -> smtp.fromName.getOrElse(context.loginAccount.get.userName))
|
||||||
|
.orElse (Some("notifications@gitbucket.com" -> context.loginAccount.get.userName))
|
||||||
|
.foreach { case (address, name) =>
|
||||||
|
email.setFrom(address, name)
|
||||||
|
}
|
||||||
|
email.setCharset("UTF-8")
|
||||||
|
email.setSubject(subject)
|
||||||
|
email.setHtmlMsg(msg)
|
||||||
|
|
||||||
|
email.addTo(to).send
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
class MockMailer extends Notifier {
|
class MockMailer extends Notifier {
|
||||||
def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String)
|
def toNotify(r: RepositoryService.RepositoryInfo, issue: Issue, content: String)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
|
// TODO Move to gitbucket.core.api package?
|
||||||
case class RepositoryName(owner:String, name:String){
|
case class RepositoryName(owner:String, name:String){
|
||||||
val fullName = s"${owner}/${name}"
|
val fullName = s"${owner}/${name}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.mozilla.universalchardet.UniversalDetector
|
|||||||
import ControlUtil._
|
import ControlUtil._
|
||||||
import org.apache.commons.io.input.BOMInputStream
|
import org.apache.commons.io.input.BOMInputStream
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
|
import scala.util.control.Exception._
|
||||||
|
|
||||||
object StringUtil {
|
object StringUtil {
|
||||||
|
|
||||||
@@ -26,6 +27,8 @@ object StringUtil {
|
|||||||
|
|
||||||
def splitWords(value: String): Array[String] = value.split("[ \\t ]+")
|
def splitWords(value: String): Array[String] = value.split("[ \\t ]+")
|
||||||
|
|
||||||
|
def isInteger(value: String): Boolean = allCatch opt { value.toInt } map(_ => true) getOrElse(false)
|
||||||
|
|
||||||
def escapeHtml(value: String): String =
|
def escapeHtml(value: String): String =
|
||||||
value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """)
|
value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """)
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package gitbucket.core.util
|
|
||||||
|
|
||||||
import java.sql.Connection
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import ControlUtil._
|
|
||||||
|
|
||||||
case class Version(majorVersion: Int, minorVersion: Int) {
|
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[Version])
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute update/MAJOR_MINOR.sql to update schema to this version.
|
|
||||||
* If corresponding SQL file does not exist, this method do nothing.
|
|
||||||
*/
|
|
||||||
def update(conn: Connection, cl: ClassLoader): Unit = {
|
|
||||||
val sqlPath = s"update/${majorVersion}_${minorVersion}.sql"
|
|
||||||
|
|
||||||
using(cl.getResourceAsStream(sqlPath)){ in =>
|
|
||||||
if(in != null){
|
|
||||||
val sql = IOUtils.toString(in, "UTF-8")
|
|
||||||
using(conn.createStatement()){ stmt =>
|
|
||||||
logger.debug(sqlPath + "=" + sql)
|
|
||||||
stmt.executeUpdate(sql)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MAJOR.MINOR
|
|
||||||
*/
|
|
||||||
val versionString = s"${majorVersion}.${minorVersion}"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object Versions {
|
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(Versions.getClass)
|
|
||||||
|
|
||||||
def update(conn: Connection, headVersion: Version, currentVersion: Version, versions: Seq[Version], cl: ClassLoader)
|
|
||||||
(save: Connection => Unit): Unit = {
|
|
||||||
logger.debug("Start schema update")
|
|
||||||
try {
|
|
||||||
if(currentVersion == headVersion){
|
|
||||||
logger.debug("No update")
|
|
||||||
} else if(currentVersion.versionString != "0.0" && !versions.contains(currentVersion)){
|
|
||||||
logger.warn(s"Skip migration because ${currentVersion.versionString} is illegal version.")
|
|
||||||
} else {
|
|
||||||
versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn, cl))
|
|
||||||
save(conn)
|
|
||||||
logger.debug(s"Updated from ${currentVersion.versionString} to ${headVersion.versionString}")
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case ex: Throwable => {
|
|
||||||
logger.error("Failed to schema update", ex)
|
|
||||||
ex.printStackTrace()
|
|
||||||
conn.rollback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.debug("End schema update")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -222,8 +222,14 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
* Generates the avatar link to the account page.
|
* Generates the avatar link to the account page.
|
||||||
* If user does not exist or disabled, this method returns avatar image without link.
|
* If user does not exist or disabled, this method returns avatar image without link.
|
||||||
*/
|
*/
|
||||||
def avatarLink(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false)(implicit context: Context): Html =
|
def avatarLink(userName: String, size: Int, mailAddress: String = "", tooltip: Boolean = false, label: Boolean = false)
|
||||||
userWithContent(userName, mailAddress)(avatar(userName, size, tooltip, mailAddress))
|
(implicit context: Context): Html = {
|
||||||
|
|
||||||
|
val avatarHtml = avatar(userName, size, tooltip, mailAddress)
|
||||||
|
val contentHtml = if(label == true) Html(avatarHtml.body + " " + userName) else avatarHtml
|
||||||
|
|
||||||
|
userWithContent(userName, mailAddress)(contentHtml)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the avatar link to the account page.
|
* Generates the avatar link to the account page.
|
||||||
@@ -232,7 +238,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
def avatarLink(commit: JGitUtil.CommitInfo, size: Int)(implicit context: Context): Html =
|
def avatarLink(commit: JGitUtil.CommitInfo, size: Int)(implicit context: Context): Html =
|
||||||
userWithContent(commit.authorName, commit.authorEmailAddress)(avatar(commit, size))
|
userWithContent(commit.authorName, commit.authorEmailAddress)(avatar(commit, size))
|
||||||
|
|
||||||
private def userWithContent(userName: String, mailAddress: String = "", styleClass: String = "")(content: Html)(implicit context: Context): Html =
|
private def userWithContent(userName: String, mailAddress: String = "", styleClass: String = "")(content: Html)
|
||||||
|
(implicit context: Context): Html =
|
||||||
(if(mailAddress.isEmpty){
|
(if(mailAddress.isEmpty){
|
||||||
getAccountByUserName(userName)
|
getAccountByUserName(userName)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@(account: gitbucket.core.model.Account, info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
@(account: gitbucket.core.model.Account, info: Option[Any], error: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||||
@import gitbucket.core.util.LDAPUtil
|
@import gitbucket.core.util.LDAPUtil
|
||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<div class="container body">
|
<div class="container body">
|
||||||
@menu("profile", settings.ssh){
|
@menu("profile", settings.ssh){
|
||||||
@helper.html.information(info)
|
@helper.html.information(info)
|
||||||
|
@helper.html.error(error)
|
||||||
@if(LDAPUtil.isDummyMailAddress(account)){<div class="alert alert-danger">Please register your mail address.</div>}
|
@if(LDAPUtil.isDummyMailAddress(account)){<div class="alert alert-danger">Please register your mail address.</div>}
|
||||||
<form action="@url(account.userName)/_edit" method="POST" validate="true">
|
<form action="@url(account.userName)/_edit" method="POST" validate="true">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.main(if(account.isEmpty) "Create group" else "Edit group"){
|
@html.main(if(account.isEmpty) "Create group" else "Edit group"){
|
||||||
<div class="body main-center">
|
<div class="content-wrapper main-center">
|
||||||
<form id="form" method="post" action="@if(account.isEmpty){@path/groups/new} else {@path/@account.get.userName/_editgroup}" validate="true">
|
<div class="content body">
|
||||||
|
<h2>@{if(account.isEmpty) "Create group" else "Edit group"}</h2>
|
||||||
|
<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="row">
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
@@ -39,7 +41,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="margin">
|
<fieldset class="border-top">
|
||||||
@if(account.isDefined){
|
@if(account.isDefined){
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="@url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a>
|
<a href="@url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a>
|
||||||
@@ -50,7 +52,8 @@
|
|||||||
<a href="@url(account.get.userName)" class="btn btn-default">Cancel</a>
|
<a href="@url(account.get.userName)" class="btn btn-default">Cancel</a>
|
||||||
}
|
}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -3,29 +3,37 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.main(account.userName){
|
@html.main(account.userName){
|
||||||
<div class="container body">
|
<div class="main-sidebar">
|
||||||
<div class="main-sidebar">
|
<div class="sidebar">
|
||||||
<div class="block">
|
<div class="user-panel">
|
||||||
<div class="account-image">@avatar(account.userName, 240)</div>
|
<div class="pull-left image">@avatar(account.userName, 40)</div>
|
||||||
<div class="account-fullname">@account.fullName</div>
|
<div class="pull-left info">
|
||||||
<div class="account-username">@account.userName</div>
|
<p>@account.userName</p>
|
||||||
|
@account.fullName
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="block">
|
<div style="padding-left: 10px; padding-right: 10px;">
|
||||||
@if(account.url.isDefined){
|
@if(account.url.isDefined){
|
||||||
<div><i class="octicon octicon-home"></i> <a href="@account.url">@account.url</a></div>
|
<p style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
|
||||||
|
<i class="octicon octicon-home"></i> <a href="@account.url">@account.url</a>
|
||||||
|
</p>
|
||||||
}
|
}
|
||||||
<div><i class="octicon octicon-clock"></i> <span class="muted">Joined on</span> @date(account.registeredDate)</div>
|
<p style="color: white;">
|
||||||
|
<i class="octicon octicon-clock"></i> Joined on @date(account.registeredDate)
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@if(groupNames.nonEmpty){
|
@if(groupNames.nonEmpty){
|
||||||
<div>
|
<ul class="sidebar-menu">
|
||||||
<div>Groups</div>
|
<li class="header">Groups</li>
|
||||||
@groupNames.map { groupName =>
|
@groupNames.map { groupName =>
|
||||||
@avatarLink(groupName, 36, tooltip = true)
|
<li>@avatarLink(groupName, 20, tooltip = true, label = true)</li>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content body">
|
||||||
<ul class="nav nav-tabs" style="margin-bottom: 5px;">
|
<ul class="nav nav-tabs" style="margin-bottom: 5px;">
|
||||||
<li@if(active == "repositories"){ class="active"}><a href="@url(account.userName)?tab=repositories">Repositories</a></li>
|
<li@if(active == "repositories"){ class="active"}><a href="@url(account.userName)?tab=repositories">Repositories</a></li>
|
||||||
@if(account.isGroupAccount){
|
@if(account.isGroupAccount){
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
@(active: String, ssh: Boolean)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
@(active: String, ssh: Boolean)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import context._
|
@import context._
|
||||||
<div class="main-sidebar">
|
<div class="main-sidebar">
|
||||||
<ul class="nav nav-pills nav-stacked">
|
<div class="sidebar">
|
||||||
<li@if(active=="profile"){ class="active"}>
|
<ul class="sidebar-menu">
|
||||||
<a href="@path/@loginAccount.get.userName/_edit">Profile</a>
|
<li@if(active=="profile"){ class="active"}>
|
||||||
</li>
|
<a href="@path/@loginAccount.get.userName/_edit">Profile</a>
|
||||||
@if(ssh){
|
</li>
|
||||||
<li@if(active=="ssh"){ class="active"}>
|
@if(ssh){
|
||||||
<a href="@path/@loginAccount.get.userName/_ssh">SSH Keys</a>
|
<li@if(active=="ssh"){ class="active"}>
|
||||||
</li>
|
<a href="@path/@loginAccount.get.userName/_ssh">SSH Keys</a>
|
||||||
}
|
</li>
|
||||||
<li@if(active=="application"){ class="active"}>
|
|
||||||
<a href="@path/@loginAccount.get.userName/_application">Applications</a>
|
|
||||||
</li>
|
|
||||||
@gitbucket.core.plugin.PluginRegistry().getAccountSettingMenus.map { menu =>
|
|
||||||
@menu(context).map { link =>
|
|
||||||
<li@if(active==link.id){ class="active"}>
|
|
||||||
<a href="@path/@link.path">@link.label</a>
|
|
||||||
</li>
|
|
||||||
}
|
}
|
||||||
}
|
<li@if(active=="application"){ class="active"}>
|
||||||
</ul>
|
<a href="@path/@loginAccount.get.userName/_application">Applications</a>
|
||||||
|
</li>
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getAccountSettingMenus.map { menu =>
|
||||||
|
@menu(context).map { link =>
|
||||||
|
<li@if(active==link.id){ class="active"}>
|
||||||
|
<a href="@path/@link.path">@link.label</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
<div class="content-wrapper">
|
||||||
@body
|
<div class="content body">
|
||||||
|
@body
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,73 +3,75 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
|
|||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.main("Create a New Repository"){
|
@html.main("Create a New Repository"){
|
||||||
<div class="body main-center">
|
<div class="content-wrapper main-center">
|
||||||
<h2>Create a new repository</h2>
|
<div class="content body">
|
||||||
<p class="muted">
|
<h2>Create a new repository</h2>
|
||||||
A repository contains all the files for your project, including the revision history.
|
<p class="muted">
|
||||||
</p>
|
A repository contains all the files for your project, including the revision history.
|
||||||
<form id="form" method="post" action="@path/new" validate="true">
|
</p>
|
||||||
<fieldset class="margin form-group">
|
<form id="form" method="post" action="@path/new" validate="true">
|
||||||
<dl style="float: left;">
|
<fieldset class="border-top form-group">
|
||||||
<dt>Owner</dt>
|
<dl style="float: left;">
|
||||||
<dd style="margin-left: 0px;">
|
<dt>Owner</dt>
|
||||||
<div class="btn-group" id="owner-dropdown">
|
<dd style="margin-left: 0px;">
|
||||||
<button class="btn dropdown-toggle btn-default" data-toggle="dropdown">
|
<div class="btn-group" id="owner-dropdown">
|
||||||
<span class="strong">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span>
|
<button class="btn dropdown-toggle btn-default" data-toggle="dropdown">
|
||||||
<span class="caret"></span>
|
<span class="strong">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span>
|
||||||
</button>
|
<span class="caret"></span>
|
||||||
<ul class="dropdown-menu">
|
</button>
|
||||||
<li><a href="javascript:void(0);" data-name="@loginAccount.get.userName"><i class="octicon octicon-check"></i> <span>@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span></a></li>
|
<ul class="dropdown-menu">
|
||||||
@groupNames.map { groupName =>
|
<li><a href="javascript:void(0);" data-name="@loginAccount.get.userName"><i class="octicon octicon-check"></i> <span>@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span></a></li>
|
||||||
<li><a href="javascript:void(0);" data-name="@groupName"><i class="octicon"></i> <span>@avatar(groupName, 20) @groupName</span></a></li>
|
@groupNames.map { groupName =>
|
||||||
}
|
<li><a href="javascript:void(0);" data-name="@groupName"><i class="octicon"></i> <span>@avatar(groupName, 20) @groupName</span></a></li>
|
||||||
</ul>
|
}
|
||||||
<input type="hidden" name="owner" id="owner" value="@loginAccount.get.userName"/>
|
</ul>
|
||||||
|
<input type="hidden" name="owner" id="owner" value="@loginAccount.get.userName"/>
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<span class="slash" style="float: left; margin-left: 10px; margin-right: 10px; margin-top: 15px;">/</span>
|
||||||
|
<dl>
|
||||||
|
<dt>Repository name</dt>
|
||||||
|
<dd style="margin-left: 0px;">
|
||||||
|
<input type="text" name="name" id="name" class="form-control" style="width: 200px;" autofocus />
|
||||||
|
<span id="error-name" class="error"></span>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="form-group">
|
||||||
|
<label for="description" class="strong">Description (optional):</label>
|
||||||
|
<input type="text" name="description" id="description" class="form-control" style="width: 95%;"/>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="border-top">
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" name="isPrivate" value="false" @if(isCreateRepoOptionPublic){checked}>
|
||||||
|
<span class="strong"><i class="octicon octicon-repo"></i> </i> Public</span>
|
||||||
|
<div class="normal muted">
|
||||||
|
Anyone can see this repository. You choose who can commit.
|
||||||
</div>
|
</div>
|
||||||
</dd>
|
</label>
|
||||||
</dl>
|
<label class="radio">
|
||||||
<span class="slash" style="float: left; margin-left: 10px; margin-right: 10px; margin-top: 15px;">/</span>
|
<input type="radio" name="isPrivate" value="true" @if(!isCreateRepoOptionPublic){checked}>
|
||||||
<dl>
|
<span class="strong"><i class="octicon octicon-lock"></i> </i> Private</span>
|
||||||
<dt>Repository name</dt>
|
<div class="normal muted">
|
||||||
<dd style="margin-left: 0px;">
|
You choose who can see and commit to this repository.
|
||||||
<input type="text" name="name" id="name" class="form-control" style="width: 200px;" autofocus />
|
</div>
|
||||||
<span id="error-name" class="error"></span>
|
</label>
|
||||||
</dd>
|
</fieldset>
|
||||||
</dl>
|
<fieldset class="border-top">
|
||||||
</fieldset>
|
<label for="createReadme" class="checkbox">
|
||||||
<fieldset class="form-group">
|
<input type="checkbox" name="createReadme" id="createReadme"/>
|
||||||
<label for="description" class="strong">Description (optional):</label>
|
<span class="strong">Initialize this repository with a README</span>
|
||||||
<input type="text" name="description" id="description" class="form-control" style="width: 95%;"/>
|
<div class="normal muted">
|
||||||
</fieldset>
|
This will let you immediately clone the repository to your computer. Skip this step if you’re importing an existing repository.
|
||||||
<fieldset class="margin">
|
</div>
|
||||||
<label class="radio">
|
</label>
|
||||||
<input type="radio" name="isPrivate" value="false" @if(isCreateRepoOptionPublic){checked}>
|
</fieldset>
|
||||||
<span class="strong"><i class="octicon octicon-repo"></i> </i> Public</span>
|
<fieldset class="border-top form-actions">
|
||||||
<div class="normal muted">
|
<input type="submit" class="btn btn-success" value="Create repository"/>
|
||||||
Anyone can see this repository. You choose who can commit.
|
</fieldset>
|
||||||
</div>
|
</form>
|
||||||
</label>
|
</div>
|
||||||
<label class="radio">
|
|
||||||
<input type="radio" name="isPrivate" value="true" @if(!isCreateRepoOptionPublic){checked}>
|
|
||||||
<span class="strong"><i class="octicon octicon-lock"></i> </i> Private</span>
|
|
||||||
<div class="normal muted">
|
|
||||||
You choose who can see and commit to this repository.
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset class="margin">
|
|
||||||
<label for="createReadme" class="checkbox">
|
|
||||||
<input type="checkbox" name="createReadme" id="createReadme"/>
|
|
||||||
<span class="strong">Initialize this repository with a README</span>
|
|
||||||
<div class="normal muted">
|
|
||||||
This will let you immediately clone the repository to your computer. Skip this step if you’re importing an existing repository.
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset class="margin form-actions">
|
|
||||||
<input type="submit" class="btn btn-success" value="Create repository"/>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -2,49 +2,51 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.main("Create your account"){
|
@html.main("Create your account"){
|
||||||
<div class="container body">
|
<div class="content-wrapper main-center">
|
||||||
<h3>Create your account</h3>
|
<div class="content body">
|
||||||
<form action="@path/register" method="POST" validate="true">
|
<h2>Create your account</h2>
|
||||||
<div class="row">
|
<form action="@path/register" method="POST" validate="true">
|
||||||
<div class="col-md-6">
|
<div class="row">
|
||||||
<fieldset>
|
<div class="col-md-6">
|
||||||
<label for="userName" class="strong">Username:</label>
|
<fieldset>
|
||||||
<input type="text" name="userName" id="userName" value="" autofocus/>
|
<label for="userName" class="strong">Username:</label>
|
||||||
<span id="error-userName" class="error"></span>
|
<input type="text" name="userName" id="userName" value="" class="form-control" autofocus/>
|
||||||
</fieldset>
|
<span id="error-userName" class="error"></span>
|
||||||
<fieldset>
|
</fieldset>
|
||||||
<label for="password" class="strong">
|
<fieldset>
|
||||||
Password:
|
<label for="password" class="strong">
|
||||||
</label>
|
Password:
|
||||||
<input type="password" name="password" id="password" value=""/>
|
</label>
|
||||||
<span id="error-password" class="error"></span>
|
<input type="password" name="password" id="password" class="form-control" value=""/>
|
||||||
</fieldset>
|
<span id="error-password" class="error"></span>
|
||||||
<fieldset>
|
</fieldset>
|
||||||
<label for="fullName" class="strong">Full Name:</label>
|
<fieldset>
|
||||||
<input type="text" name="fullName" id="fullName" value=""/>
|
<label for="fullName" class="strong">Full Name:</label>
|
||||||
<span id="error-fullName" class="error"></span>
|
<input type="text" name="fullName" id="fullName" class="form-control" value=""/>
|
||||||
</fieldset>
|
<span id="error-fullName" class="error"></span>
|
||||||
<fieldset>
|
</fieldset>
|
||||||
<label for="mailAddress" class="strong">Mail Address:</label>
|
<fieldset>
|
||||||
<input type="text" name="mailAddress" id="mailAddress" value=""/>
|
<label for="mailAddress" class="strong">Mail Address:</label>
|
||||||
<span id="error-mailAddress" class="error"></span>
|
<input type="text" name="mailAddress" id="mailAddress" class="form-control" value=""/>
|
||||||
</fieldset>
|
<span id="error-mailAddress" class="error"></span>
|
||||||
<fieldset>
|
</fieldset>
|
||||||
<label for="url" class="strong">URL (optional):</label>
|
<fieldset>
|
||||||
<input type="text" name="url" id="url" style="width: 400px;" value=""/>
|
<label for="url" class="strong">URL (optional):</label>
|
||||||
<span id="error-url" class="error"></span>
|
<input type="text" name="url" id="url" class="form-control" value=""/>
|
||||||
</fieldset>
|
<span id="error-url" class="error"></span>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<fieldset>
|
||||||
|
<label for="avatar" class="strong">Image (optional):</label>
|
||||||
|
@helper.html.uploadavatar(None)
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<fieldset class="border-top">
|
||||||
<fieldset>
|
<input type="submit" class="btn btn-success" value="Create account"/>
|
||||||
<label for="avatar" class="strong">Image (optional):</label>
|
</fieldset>
|
||||||
@helper.html.uploadavatar(None)
|
</form>
|
||||||
</fieldset>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<fieldset class="margin">
|
|
||||||
<input type="submit" class="btn btn-success" value="Create account"/>
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
56
src/main/twirl/gitbucket/core/admin/data.scala.html
Normal file
56
src/main/twirl/gitbucket/core/admin/data.scala.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
@(tableNames: Seq[String])(implicit context: gitbucket.core.controller.Context)
|
||||||
|
@import context._
|
||||||
|
@import gitbucket.core.view.helpers._
|
||||||
|
@html.main("Data export / import"){
|
||||||
|
@admin.html.menu("data") {
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading strong">Export</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<form class="form form-horizontal" action="@path/admin/export" method="POST">
|
||||||
|
@tableNames.map { tableName =>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="tableNames" id="@tableName" value="@tableName" checked/> @tableName
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<input type="submit" class="btn btn-success pull-right" value="Export">
|
||||||
|
<div class="radio pull-right" style="margin-right: 10px;">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="type" value="sql">SQL
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio pull-right" style="margin-right: 10px;">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="type" value="xml" checked>XML
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading strong">Import (only XML)</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<form class="form form-horizontal" action="@path/upload/import" method="POST" enctype="multipart/form-data" id="import-form">
|
||||||
|
<input type="file" name="file" id="file">
|
||||||
|
<input type="submit" class="btn btn-success pull-right" value="Import" id="import">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#import-form').submit(function(){
|
||||||
|
if($('#file').val() == ''){
|
||||||
|
alert('Choose an import XML file.');
|
||||||
|
return false;
|
||||||
|
} else if(!$('#file').val().endsWith(".xml")){
|
||||||
|
alert('Import is available for only the XML file.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return confirm('All existing data is deleted before importing.\nAre you sure?');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@(active: String)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
@(active: String)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import context._
|
@import context._
|
||||||
<div class="container body">
|
<div class="main-sidebar">
|
||||||
<div class="main-sidebar">
|
<div class="sidebar">
|
||||||
<ul class="nav nav-pills nav-stacked" id="system-admin-menu-container">
|
<ul class="sidebar-menu" id="system-admin-menu-container">
|
||||||
<li@if(active=="users"){ class="active"}>
|
<li@if(active=="users"){ class="active"}>
|
||||||
<a href="@path/admin/users">User Management</a>
|
<a href="@path/admin/users">User Management</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -12,8 +12,11 @@
|
|||||||
<li@if(active=="plugins"){ class="active"}>
|
<li@if(active=="plugins"){ class="active"}>
|
||||||
<a href="@path/admin/plugins">Plugins</a>
|
<a href="@path/admin/plugins">Plugins</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li@if(active=="data"){ class="active"}>
|
||||||
|
<a href="@path/admin/data">Data export / import</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="@path/console/login.jsp">H2 Console</a>
|
<a href="@path/console/login.jsp" target="_blank">H2 Console</a>
|
||||||
</li>
|
</li>
|
||||||
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
|
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
|
||||||
@menu(context).map { link =>
|
@menu(context).map { link =>
|
||||||
@@ -24,7 +27,9 @@
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content body">
|
||||||
@body
|
@body
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,19 +18,19 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-md-2">Id</label>
|
<label class="col-md-2">Id</label>
|
||||||
<span class="col-md-8">@plugin.pluginId</span>
|
<span class="col-md-10">@plugin.pluginId</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-md-2">Version</label>
|
<label class="col-md-2">Version</label>
|
||||||
<span class="col-md-8">@plugin.version</span>
|
<span class="col-md-10">@plugin.version</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-md-2">Name</label>
|
<label class="col-md-2">Name</label>
|
||||||
<span class="col-md-8">@plugin.pluginName</span>
|
<span class="col-md-10">@plugin.pluginName</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-md-2">Description</label>
|
<label class="col-md-2">Description</label>
|
||||||
<span class="col-md-8 muted">@plugin.description</span>
|
<span class="col-md-10 muted">@plugin.description</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,11 +9,23 @@
|
|||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading strong">System Settings</div>
|
<div class="panel-heading strong">System Settings</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- GITBUCKET_HOME -->
|
<!-- System properties -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<label class="strong">GITBUCKET_HOME</label>
|
<table class="table table-bordered">
|
||||||
@GitBucketHome
|
<tr>
|
||||||
|
<th>Property</th>
|
||||||
|
<th>Value</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>GITBUCKET_HOME</td>
|
||||||
|
<td>@GitBucketHome</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>DATABASE_URL</td>
|
||||||
|
<td>@gitbucket.core.util.DatabaseConfig.url</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- Base URL -->
|
<!-- Base URL -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
@@ -85,10 +97,15 @@
|
|||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
<label><span class="strong">Limit of activity logs</span> (Unlimited if it's not specified or zero)</label>
|
<label><span class="strong">Limit of activity logs</span> (Unlimited if it's not specified or zero)</label>
|
||||||
<div class="controls">
|
<fieldset>
|
||||||
<input type="text" id="activityLogLimit" name="activityLogLimit" class="form-control input-mini" value="@settings.activityLogLimit"/>
|
<div class="form-group">
|
||||||
<span id="error-activityLogLimit" class="error"></span>
|
<label class="control-label col-md-3" for="activityLogLimit">Limit</label>
|
||||||
</div>
|
<div class="col-md-9">
|
||||||
|
<input type="text" id="activityLogLimit" name="activityLogLimit" class="form-control input-mini" value="@settings.activityLogLimit"/>
|
||||||
|
<span id="error-activityLogLimit" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
@@ -277,7 +294,7 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-md-3" for="smtpPassword">Enable SSL</label>
|
<label class="control-label col-md-3" for="smtpPassword">Enable SSL</label>
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<input type="checkbox" name="smtp.ssl"@if(settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/>
|
<input type="checkbox" id="smtpSsl" name="smtp.ssl"@if(settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -292,6 +309,12 @@
|
|||||||
<input type="text" id="fromName" name="smtp.fromName" class="form-control" value="@settings.smtp.map(_.fromName)"/>
|
<input type="text" id="fromName" name="smtp.fromName" class="form-control" value="@settings.smtp.map(_.fromName)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
Send test mail to:
|
||||||
|
<input type="text" id="testAddress" size="30"/>
|
||||||
|
<input type="button" id="sendTestMail" value="Send"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
Enable notification not only SMTP configuration if you want to send notification email.
|
Enable notification not only SMTP configuration if you want to send notification email.
|
||||||
</p>
|
</p>
|
||||||
@@ -306,6 +329,40 @@
|
|||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
|
$('#sendTestMail').click(function(){
|
||||||
|
var host = $('#smtpHost' ).val();
|
||||||
|
var port = $('#smtpPort' ).val();
|
||||||
|
var user = $('#smtpUser' ).val();
|
||||||
|
var password = $('#smtpPassword').val();
|
||||||
|
var ssl = $('#smtpSsl' ).prop('checked');
|
||||||
|
var fromAddress = $('#fromAddress' ).val();
|
||||||
|
var fromName = $('#fromName' ).val();
|
||||||
|
var testAddress = $('#testAddress' ).val();
|
||||||
|
|
||||||
|
if(host == ''){
|
||||||
|
alert('SMTP Host is required.');
|
||||||
|
$('#smtpHost').focus();
|
||||||
|
} else if(testAddress == ''){
|
||||||
|
alert('Destination is required.');
|
||||||
|
$('#testAddress').focus();
|
||||||
|
} else {
|
||||||
|
$.post('@path/admin/system/sendmail', {
|
||||||
|
'smtp.host': host,
|
||||||
|
'smtp.port': port,
|
||||||
|
'smtp.user': user,
|
||||||
|
'smtp.password': password,
|
||||||
|
'smtp.ssl': ssl,
|
||||||
|
'smtp.fromAddress': fromAddress,
|
||||||
|
'smtp.fromName': fromName,
|
||||||
|
'testAddress': testAddress
|
||||||
|
}, function(data, status){
|
||||||
|
if(data != ''){
|
||||||
|
alert(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$('#ssh').change(function(){
|
$('#ssh').change(function(){
|
||||||
$('.ssh input').prop('disabled', !$(this).prop('checked'));
|
$('.ssh input').prop('disabled', !$(this).prop('checked'));
|
||||||
}).change();
|
}).change();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
@(account: Option[gitbucket.core.model.Account])(implicit context: gitbucket.core.controller.Context)
|
@(account: Option[gitbucket.core.model.Account], error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@html.main(if(account.isEmpty) "New User" else "Update User"){
|
@html.main(if(account.isEmpty) "New User" else "Update User"){
|
||||||
@admin.html.menu("users"){
|
@admin.html.menu("users"){
|
||||||
|
@helper.html.error(error)
|
||||||
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newuser} else {@path/admin/users/@account.get.userName/_edituser}" validate="true">
|
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newuser} else {@path/admin/users/@account.get.userName/_edituser}" validate="true">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="margin">
|
<fieldset class="border-top">
|
||||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/>
|
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/>
|
||||||
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
|
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
@admin.html.menu("users"){
|
@admin.html.menu("users"){
|
||||||
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newgroup} else {@path/admin/users/@account.get.userName/_editgroup}" validate="true">
|
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newgroup} else {@path/admin/users/@account.get.userName/_editgroup}" validate="true">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5">
|
<div class="col-md-6">
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<label for="groupName" class="strong">Group name</label>
|
<label for="groupName" class="strong">Group name</label>
|
||||||
<div>
|
<div>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
@helper.html.uploadavatar(account)
|
@helper.html.uploadavatar(account)
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7">
|
<div class="col-md-6">
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<label class="strong">Members</label>
|
<label class="strong">Members</label>
|
||||||
<ul id="member-list" class="collaborator">
|
<ul id="member-list" class="collaborator">
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="margin">
|
<fieldset class="border-top">
|
||||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
||||||
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
|
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@@ -78,7 +78,8 @@ $(function(){
|
|||||||
|
|
||||||
// check existence
|
// check existence
|
||||||
$.post('@path/_user/existence', {
|
$.post('@path/_user/existence', {
|
||||||
'userName': userName
|
'userName': userName,
|
||||||
|
'userOnly': true
|
||||||
}, function(data, status){
|
}, function(data, status){
|
||||||
if(data == 'true'){
|
if(data == 'true'){
|
||||||
addMemberHTML(userName, false);
|
addMemberHTML(userName, false);
|
||||||
@@ -102,18 +103,18 @@ $(function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addMemberHTML(userName, isManager){
|
function addMemberHTML(userName, isManager){
|
||||||
var memberButton = $('<button type="button" class="btn btn-default btn-mini" value="false">Member</button>').data('name', userName);
|
var memberButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="false" name="' + userName + '">Member</label>');
|
||||||
if(!isManager){
|
if(!isManager){
|
||||||
memberButton.addClass('active');
|
memberButton.addClass('active');
|
||||||
}
|
}
|
||||||
var managerButton = $('<button type="button" class="btn btn-default btn-mini" value="true">Manager</button>').data('name', userName);
|
var managerButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="true" name="' + userName + '">Manager</label>');
|
||||||
if(isManager){
|
if(isManager){
|
||||||
managerButton.addClass('active');
|
managerButton.addClass('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#member-list').append($('<li>')
|
$('#member-list').append($('<li>')
|
||||||
.data('name', userName)
|
.data('name', userName)
|
||||||
.append($('<div class="btn-group is_manager" data-toggle="buttons-radio">')
|
.append($('<div class="btn-group is_manager" data-toggle="buttons">')
|
||||||
.append(memberButton)
|
.append(memberButton)
|
||||||
.append(managerButton))
|
.append(managerButton))
|
||||||
.append(' ')
|
.append(' ')
|
||||||
@@ -125,9 +126,7 @@ $(function(){
|
|||||||
function updateMembers(){
|
function updateMembers(){
|
||||||
var members = $('#member-list li').map(function(i, e){
|
var members = $('#member-list li').map(function(i, e){
|
||||||
var userName = $(e).data('name');
|
var userName = $(e).data('name');
|
||||||
return userName + ':' + $('button.active').filter(function(i, e){
|
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
|
||||||
return $(e).data('name') == userName;
|
|
||||||
}).attr('value');
|
|
||||||
}).get().join(',');
|
}).get().join(',');
|
||||||
$('#members').val(members);
|
$('#members').val(members);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,71 +2,62 @@
|
|||||||
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(body: Html)(implicit context: gitbucket.core.controller.Context)
|
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
<div class="container body">
|
<div class="main-sidebar">
|
||||||
<div class="dashboard-sidebar">
|
<div class="sidebar">
|
||||||
@if(loginAccount.isEmpty){
|
<ul class="nav sidebar-menu">
|
||||||
<div id="dashboard-signin-form">@html.signinform(settings)</div>
|
@if(loginAccount.isDefined){
|
||||||
|
<li class="header">Your repositories <span class="label label-primary pull-right">@userRepositories.size</span></li>
|
||||||
|
@if(userRepositories.isEmpty){
|
||||||
|
<li>No repositories</li>
|
||||||
} else {
|
} else {
|
||||||
<div class="panel panel-default">
|
@defining(10){ max =>
|
||||||
<div class="panel-heading strong">
|
@userRepositories.zipWithIndex.map { case (repository, i) =>
|
||||||
Your repositories <span class="badge">@userRepositories.size</span>
|
<li class="repo-link" style="@if(i > max - 1){display:none;}">
|
||||||
</div>
|
@if(repository.owner == loginAccount.get.userName){
|
||||||
<ul class="list-group list-group-flush">
|
<a href="@url(repository)">@helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
|
||||||
@if(userRepositories.isEmpty){
|
} else {
|
||||||
<li class="list-group-item">No repositories</li>
|
<a href="@url(repository)">@helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
|
||||||
} 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>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
</ul>
|
@if(userRepositories.size > max){
|
||||||
</div>
|
<li class="show-more">
|
||||||
}
|
<a href="javascript:void(0);" id="show-more-repos">Show @{userRepositories.size - max} more repositories...</a>
|
||||||
<div class="panel panel-default">
|
</li>
|
||||||
<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>
|
} else {
|
||||||
|
<li class="header">Recent updated repositories</li>
|
||||||
|
@if(recentRepositories.isEmpty){
|
||||||
|
<li>No repositories</li>
|
||||||
|
} else {
|
||||||
|
@defining(10){ max =>
|
||||||
|
@recentRepositories.zipWithIndex.map { case (repository, i) =>
|
||||||
|
<li class="repo-link" style="@if(i > max - 1){display:none;}">
|
||||||
|
<a href="@url(repository)">@helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
@if(recentRepositories.size > max){
|
||||||
|
<li class="show-more">
|
||||||
|
<a href="javascript:void(0);" id="show-more-recent-repos">Show @{recentRepositories.size - max} more repositories...</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="dashboard-content">
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content body">
|
||||||
@body
|
@body
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('#show-more-repos, #show-more-recent-repos').click(function(e){
|
$('#show-more-repos, #show-more-recent-repos').click(function(e){
|
||||||
$(e.target).parents('ul.list-group').find('li.repo-link').show();
|
$(e.target).parents('ul').find('li.repo-link').show();
|
||||||
$(e.target).parents('li.show-more').remove();
|
$(e.target).parents('li.show-more').remove();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,15 +10,13 @@
|
|||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||||
@if(showIndex){
|
@if(showIndex){
|
||||||
<div style="overflow: hidden;">
|
<div class="pull-right" style="margin-bottom: 10px;">
|
||||||
<div class="pull-right" style="margin-bottom: 10px;">
|
<div class="btn-group" data-toggle="buttons-radio">
|
||||||
<div class="btn-group" data-toggle="buttons-radio">
|
<input type="button" id="btn-unified" class="btn btn-default btn-small active" value="Unified">
|
||||||
<input type="button" id="btn-unified" class="btn btn-default btn-small active" value="Unified">
|
<input type="button" id="btn-split" class="btn btn-default btn-small" value="Split">
|
||||||
<input type="button" id="btn-split" class="btn btn-default btn-small" value="Split">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @plural(diffs.size, "file")</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @plural(diffs.size, "file")</a>
|
||||||
<ul id="commit-file-list" style="display: none;">
|
<ul id="commit-file-list" style="display: none;">
|
||||||
@diffs.zipWithIndex.map { case (diff, i) =>
|
@diffs.zipWithIndex.map { case (diff, i) =>
|
||||||
<li@if(i > 0){ class="border"}>
|
<li@if(i > 0){ class="border"}>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<div class="edit-title pull-right" style="display: none;">
|
<div class="edit-title pull-right" style="display: none;">
|
||||||
<a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
|
<a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
<h1>
|
<h1 class="body-title">
|
||||||
<span class="show-title">
|
<span class="show-title">
|
||||||
<span id="show-title">@issue.title</span>
|
<span id="show-title">@issue.title</span>
|
||||||
<span class="muted">#@issue.issueId</span>
|
<span class="muted">#@issue.issueId</span>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row-fluid" style="margin-top: 15px;">
|
<div style="margin-top: 15px;">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
@commentlist(Some(issue), comments, hasWritePermission, repository)
|
@commentlist(Some(issue), comments, hasWritePermission, repository)
|
||||||
@commentform(issue, true, hasWritePermission, repository)
|
@commentform(issue, true, hasWritePermission, repository)
|
||||||
|
|||||||
@@ -51,23 +51,28 @@
|
|||||||
}
|
}
|
||||||
@helper.html.dropdown("Milestone") {
|
@helper.html.dropdown("Milestone") {
|
||||||
<li>
|
<li>
|
||||||
<a href="@condition.copy(milestone = Some(None)).toURL">
|
<a href="@condition.copy(milestone = (if(condition.milestone == Some(None)) None else Some(None))).toURL">
|
||||||
@helper.html.checkicon(condition.milestone == Some(None)) Issues with no milestone
|
@helper.html.checkicon(condition.milestone == Some(None)) Issues with no milestone
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||||
<li>
|
<li>
|
||||||
<a href="@condition.copy(milestone = Some(Some(milestone.title))).toURL">
|
<a href="@condition.copy(milestone = (if(condition.milestone == Some(Some(milestone.title))) None else Some(Some(milestone.title)))).toURL">
|
||||||
@helper.html.checkicon(condition.milestone == Some(Some(milestone.title))) @milestone.title
|
@helper.html.checkicon(condition.milestone == Some(Some(milestone.title))) @milestone.title
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@helper.html.dropdown("Assignee") {
|
@helper.html.dropdown("Assignee") {
|
||||||
|
<li>
|
||||||
|
<a href="@condition.copy(assigned = (if(condition.assigned == Some(None)) None else Some(None))).toURL">
|
||||||
|
@helper.html.checkicon(condition.assigned == Some(None)) Assigned to nobody
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
@collaborators.map { collaborator =>
|
@collaborators.map { collaborator =>
|
||||||
<li>
|
<li>
|
||||||
<a href="@condition.copy(assigned = Some(collaborator)).toURL">
|
<a href="@condition.copy(assigned = (if(condition.assigned == Some(Some(collaborator))) None else Some(Some(collaborator)))).toURL">
|
||||||
@helper.html.checkicon(condition.assigned == Some(collaborator))
|
@helper.html.checkicon(condition.assigned == Some(Some(collaborator)))
|
||||||
@avatar(collaborator, 20) @collaborator
|
@avatar(collaborator, 20) @collaborator
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@(title: String, repository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo] = None)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
@(title: String, repository: Option[gitbucket.core.service.RepositoryService.RepositoryInfo] = None)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import gitbucket.core.plugin.PluginRegistry
|
@import gitbucket.core.plugin.PluginRegistry
|
||||||
@import gitbucket.core.servlet.AutoUpdate
|
|
||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -12,11 +11,13 @@
|
|||||||
<link rel="icon" href="@assets/common/images/gitbucket.png" type="image/vnd.microsoft.icon" />
|
<link rel="icon" href="@assets/common/images/gitbucket.png" type="image/vnd.microsoft.icon" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link href="@assets/vendors/bootstrap-3.3.6/css/bootstrap.min.css" rel="stylesheet">
|
<link href="@assets/vendors/bootstrap-3.3.6/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="@assets/vendors/octicons/octicons.css" rel="stylesheet">
|
<link href="@assets/vendors/octicons-4.2.0/octicons.css" rel="stylesheet">
|
||||||
<link href="@assets/vendors/datepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
|
<link href="@assets/vendors/datepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
|
||||||
<link href="@assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
|
<link href="@assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
|
||||||
<link href="@assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
|
<link href="@assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
|
||||||
<link href="@assets/vendors/facebox/facebox.css" rel="stylesheet"/>
|
<link href="@assets/vendors/facebox/facebox.css" rel="stylesheet"/>
|
||||||
|
<link href="@assets/vendors/AdminLTE-2.2.3/css/AdminLTE.min.css" rel="stylesheet">
|
||||||
|
<link href="@assets/vendors/AdminLTE-2.2.3/css/skins/skin-blue.min.css" rel="stylesheet">
|
||||||
<link href="@assets/common/css/gitbucket.css" rel="stylesheet">
|
<link href="@assets/common/css/gitbucket.css" rel="stylesheet">
|
||||||
<script src="@assets/vendors/jquery/jquery-1.11.1.js"></script>
|
<script src="@assets/vendors/jquery/jquery-1.11.1.js"></script>
|
||||||
<script src="@assets/vendors/dropzone/dropzone.js"></script>
|
<script src="@assets/vendors/dropzone/dropzone.js"></script>
|
||||||
@@ -37,77 +38,73 @@
|
|||||||
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
|
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
<script src="@assets/vendors/AdminLTE-2.2.3/js/app.js" type="text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="skin-blue">
|
||||||
<form id="search" action="@path/search" method="POST" class="form-inline">
|
<div class="wrapper">
|
||||||
<nav class="navbar navbar-default">
|
<header class="main-header">
|
||||||
<div class="container">
|
<a href="@path/" class="logo">
|
||||||
@* TODO: for plugi-ins?
|
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px; display: inline;"/>
|
||||||
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
GitBucket
|
||||||
<span class="icon-bar"></span>
|
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
|
||||||
<span class="icon-bar"></span>
|
</a>
|
||||||
<span class="icon-bar"></span>
|
<nav class="navbar navbar-static-top" role="navigation">
|
||||||
</button>
|
@repository.map { repository =>
|
||||||
*@
|
<form id="search" action="@path/search" method="POST" class="navbar-form navbar-left" role="search">
|
||||||
<a class="navbar-brand" href="@path/">
|
<div class="form-group">
|
||||||
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px; display: inline;"/>GitBucket
|
<input type="text" name="query" id="navbar-search-input" class="form-control" placeholder="Search this repository"/>
|
||||||
@defining(AutoUpdate.getCurrentVersion){ version =>
|
<input type="hidden" name="owner" value="@repository.owner"/>
|
||||||
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
<input type="hidden" name="repository" value="@repository.name"/>
|
||||||
}
|
|
||||||
</a>
|
|
||||||
@if(loginAccount.isDefined){
|
|
||||||
@repository.map { repository =>
|
|
||||||
<input type="text" name="query" class="form-control" style="width: 400px; margin-top: 3px; margin-bottom: 3px;" placeholder="Search this repository"/>
|
|
||||||
<input type="hidden" name="owner" value="@repository.owner"/>
|
|
||||||
<input type="hidden" name="repository" value="@repository.name"/>
|
|
||||||
}
|
|
||||||
<a href="@path/dashboard/pulls" class="global-header-menu">Pull requests</a>
|
|
||||||
<a href="@path/dashboard/issues" class="global-header-menu">Issues</a>
|
|
||||||
} else {
|
|
||||||
@* TODO: merge with below *@
|
|
||||||
@repository.map { repository =>
|
|
||||||
<input type="text" name="query" class="form-control" style="width: 400px; margin-top: 3px; margin-bottom: 3px;" placeholder="Search this repository"/>
|
|
||||||
<input type="hidden" name="owner" value="@repository.owner"/>
|
|
||||||
<input type="hidden" name="repository" value="@repository.name"/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@gitbucket.core.plugin.PluginRegistry().getGlobalMenus.map { menu =>
|
|
||||||
@menu(context).map { link =>
|
|
||||||
<a href="@path/@link.path" class="global-header-menu">@link.label</a>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@if(loginAccount.isDefined){
|
|
||||||
<div class="pull-right" style="margin-top: 6px;">
|
|
||||||
<div class="btn-group" style="margin-right: 8px;">
|
|
||||||
<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><span class="caret" style="color: black; vertical-align: middle;"></span>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu pull-right">
|
|
||||||
<li><a href="@path/new">New repository</a></li>
|
|
||||||
<li><a href="@path/groups/new">New group</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
</form>
|
||||||
<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)<span class="caret" style="color: black; vertical-align: middle;"></span>
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu pull-right">
|
|
||||||
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
|
|
||||||
<li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li>
|
|
||||||
@if(loginAccount.get.isAdmin){
|
|
||||||
<li><a href="@path/admin/users">System administration</a></li>
|
|
||||||
}
|
|
||||||
<li><a href="@path/signout">Sign out</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
} else {
|
|
||||||
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn btn-default pull-right" style="margin-top: 3px; margin-bottom: 3px;" id="signin">Sign in</a>
|
|
||||||
}
|
}
|
||||||
</div>
|
<ul class="nav navbar-nav">
|
||||||
</nav>
|
@if(loginAccount.isDefined){
|
||||||
</form>
|
<li><a href="@path/dashboard/pulls">Pull requests</a></li>
|
||||||
@body
|
<li><a href="@path/dashboard/issues">Issues</a></li>
|
||||||
|
}
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getGlobalMenus.map { menu =>
|
||||||
|
@menu(context).map { link =>
|
||||||
|
<li><a href="@path/@link.path">@link.label</a></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
<div class="navbar-custom-menu">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
@if(loginAccount.isDefined){
|
||||||
|
<li class="dropdown">
|
||||||
|
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#">
|
||||||
|
<i class="octicon octicon-plus" style="color: black;"></i><span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu pull-right">
|
||||||
|
<li><a href="@path/new">New repository</a></li>
|
||||||
|
<li><a href="@path/groups/new">New group</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<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)<span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu pull-right">
|
||||||
|
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
|
||||||
|
<li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li>
|
||||||
|
@if(loginAccount.get.isAdmin){
|
||||||
|
<li><a href="@path/admin/users">System administration</a></li>
|
||||||
|
}
|
||||||
|
<li><a href="@path/signout">Sign out</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
} else {
|
||||||
|
<li>
|
||||||
|
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="pull-right" id="signin">Sign in</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
@body
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('#search').submit(function(){
|
$('#search').submit(function(){
|
||||||
|
|||||||
@@ -8,65 +8,80 @@
|
|||||||
|
|
||||||
@menuitem(path: String, name: String, label: String, icon: String, count: Int = 0) = {
|
@menuitem(path: String, name: String, label: String, icon: String, count: Int = 0) = {
|
||||||
<li @if(active == name){class="active"}>
|
<li @if(active == name){class="active"}>
|
||||||
<a href="@url(repository)@path">
|
@if(path.startsWith("http")){
|
||||||
<i class="menu-icon octicon octicon-@icon"></i>
|
<a href="@path" target="_blank">
|
||||||
<span class="pc">
|
<i class="menu-icon octicon octicon-@icon"></i> <span class="pc">@label @if(count > 0) { <span class="label label-primary pull-right">@count</span> }</span>
|
||||||
@label
|
</a>
|
||||||
@if(count > 0){
|
} else {
|
||||||
<span class="badge">@count</span>
|
<a href="@url(repository)@path">
|
||||||
}
|
<i class="menu-icon octicon octicon-@icon"></i> <span class="pc">@label @if(count > 0) { <span class="label label-primary pull-right">@count</span> }</span>
|
||||||
</span>
|
</a>
|
||||||
</a>
|
}
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="headbar">
|
<div class="main-sidebar">
|
||||||
<div class="container">
|
<div class="sidebar">
|
||||||
@helper.html.information(info)
|
<ul class="sidebar-menu">
|
||||||
@helper.html.error(error)
|
@menuitem("", "files", "Files", "code")
|
||||||
<div class="head">
|
@if(repository.commitCount != 0) {
|
||||||
@helper.html.repositoryicon(repository, true)
|
@menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length)
|
||||||
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a>
|
@menuitem("/tags", "tags", "Tags", "tag", repository.tags.length)
|
||||||
|
}
|
||||||
|
@if(repository.repository.enableIssues) {
|
||||||
|
@menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount)
|
||||||
|
@menuitem("/pulls", "pulls", "Pull Requests", "git-pull-request", repository.pullCount)
|
||||||
|
@menuitem("/issues/labels", "labels", "Labels", "tag")
|
||||||
|
@menuitem("/issues/milestones", "milestones", "Milestones", "milestone")
|
||||||
|
} else {
|
||||||
|
@repository.repository.externalIssuesUrl.map { externalIssuesUrl =>
|
||||||
|
@menuitem(externalIssuesUrl, "issues", "Issues", "issue-opened")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if(repository.repository.enableWiki) {
|
||||||
|
@menuitem("/wiki", "wiki", "Wiki", "book")
|
||||||
|
} else {
|
||||||
|
@repository.repository.externalWikiUrl.map { externalWikiUrl =>
|
||||||
|
@menuitem(externalWikiUrl, "wiki", "Wiki", "book")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
|
||||||
|
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
|
||||||
|
@menuitem("/settings", "settings", "Settings", "tools")
|
||||||
|
}
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getRepositoryMenus.map { menu =>
|
||||||
|
@menu(repository, context).map { link =>
|
||||||
|
@menuitem(link.path, link.id, link.label, link.icon.getOrElse("ruby"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="content body">
|
||||||
|
<div class="headbar">
|
||||||
|
<div class="container">
|
||||||
|
@helper.html.information(info)
|
||||||
|
@helper.html.error(error)
|
||||||
|
<div class="head">
|
||||||
|
@helper.html.repositoryicon(repository, true)
|
||||||
|
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a>
|
||||||
|
|
||||||
@defining(repository.repository){ x =>
|
@defining(repository.repository){ x =>
|
||||||
@if(repository.repository.originRepositoryName.isDefined){
|
@if(repository.repository.originRepositoryName.isDefined){
|
||||||
<div class="forked">
|
<div class="forked">
|
||||||
forked from <a href="@path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
|
forked from <a href="@path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@x.description.map { description =>
|
||||||
|
<div class="normal muted" style="margin-left: 36px; font-size: 80%;">@detectAndRenderLinks(description)</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
@x.description.map { description =>
|
</div>
|
||||||
<div class="normal muted" style="margin-left: 36px; font-size: 80%;">@detectAndRenderLinks(description)</div>
|
@body
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container body">
|
|
||||||
<div class="main-sidebar">
|
|
||||||
<ul class="nav nav-pills nav-stacked">
|
|
||||||
@menuitem("" ,"files" ,"Files", "code")
|
|
||||||
@if(repository.commitCount != 0) {
|
|
||||||
@menuitem("/branches" ,"branches" ,"Branches", "git-branch", repository.branchList.length)
|
|
||||||
@menuitem("/tags" ,"tags" ,"Tags", "tag", repository.tags.length)
|
|
||||||
}
|
|
||||||
@menuitem("/issues" ,"issues" ,"Issues", "issue-opened", repository.issueCount)
|
|
||||||
@menuitem("/pulls" ,"pulls" ,"Pull Requests", "git-pull-request", repository.pullCount)
|
|
||||||
@menuitem("/issues/labels" ,"labels" ,"Labels", "tag")
|
|
||||||
@menuitem("/issues/milestones" ,"milestones" ,"Milestones", "milestone")
|
|
||||||
@menuitem("/wiki" ,"wiki" ,"Wiki", "book")
|
|
||||||
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
|
|
||||||
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
|
|
||||||
@menuitem("/settings" , "settings" , "Settings", "tools")
|
|
||||||
}
|
|
||||||
@gitbucket.core.plugin.PluginRegistry().getRepositoryMenus.map { menu =>
|
|
||||||
@menu(repository, context).map { link =>
|
|
||||||
@menuitem(link.path, link.id, link.label, link.icon.getOrElse("ruby"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="main-content">
|
|
||||||
@body
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,13 +27,13 @@
|
|||||||
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
|
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
|
||||||
}
|
}
|
||||||
<div>
|
<div>
|
||||||
@user(commit.authorName, commit.authorEmailAddress, "username")
|
|
||||||
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
|
|
||||||
@if(commit.isDifferentFromAuthor) {
|
@if(commit.isDifferentFromAuthor) {
|
||||||
|
@user(commit.authorName, commit.authorEmailAddress, "username")
|
||||||
|
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
|
||||||
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
|
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
|
||||||
@user(commit.committerName, commit.committerEmailAddress, "username")
|
|
||||||
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
|
|
||||||
}
|
}
|
||||||
|
@user(commit.committerName, commit.committerEmailAddress, "username")
|
||||||
|
<span class="muted">committed @helper.html.datetimeago(commit.commitTime)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@(commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
@(title: String,
|
||||||
|
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||||
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
|
||||||
members: List[(String, String)],
|
members: List[(String, String)],
|
||||||
comments: List[gitbucket.core.model.Comment],
|
comments: List[gitbucket.core.model.Comment],
|
||||||
@@ -56,7 +57,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<span class="error" id="error-title"></span>
|
<span class="error" id="error-title"></span>
|
||||||
<input type="text" name="title" class="form-control" style="margin-bottom: 6px;" placeholder="Title"/>
|
<input type="text" name="title" value="@title" class="form-control" style="margin-bottom: 6px;" placeholder="Title"/>
|
||||||
@helper.html.preview(
|
@helper.html.preview(
|
||||||
repository = repository,
|
repository = repository,
|
||||||
content = "",
|
content = "",
|
||||||
|
|||||||
@@ -10,43 +10,40 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@import gitbucket.core.model._
|
@import gitbucket.core.model._
|
||||||
|
<div class="col-md-9">
|
||||||
<div class="row">
|
<div id="comment-list">
|
||||||
<div class="col-md-9">
|
@issues.html.commentlist(Some(issue), comments, hasWritePermission, repository, Some(pullreq))
|
||||||
<div id="comment-list">
|
</div>
|
||||||
@issues.html.commentlist(Some(issue), comments, hasWritePermission, repository, Some(pullreq))
|
@defining(comments.flatMap {
|
||||||
</div>
|
case comment: gitbucket.core.model.IssueComment => Some(comment)
|
||||||
@defining(comments.flatMap {
|
case other => None
|
||||||
case comment: gitbucket.core.model.IssueComment => Some(comment)
|
}.exists(_.action == "merge")){ merged =>
|
||||||
case other => None
|
@if(!issue.closed){
|
||||||
}.exists(_.action == "merge")){ merged =>
|
<div class="check-conflict" style="display: none;">
|
||||||
@if(!issue.closed){
|
<div class="box issue-comment-box" style="background-color: #fbeed5">
|
||||||
<div class="check-conflict" style="display: none;">
|
<div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;">
|
||||||
<div class="box issue-comment-box" style="background-color: #fbeed5">
|
<img src="@assets/common/images/indicator.gif"/> Checking...
|
||||||
<div class="box-content"class="issue-content" style="border: 1px solid #c09853; padding: 10px;">
|
|
||||||
<img src="@assets/common/images/indicator.gif"/> Checking...
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
@if(hasWritePermission && issue.closed && pullreq.userName == pullreq.requestUserName && merged &&
|
|
||||||
pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){
|
|
||||||
<div class="box issue-comment-box" style="background-color: #d0eeff;">
|
|
||||||
<div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;">
|
|
||||||
<a href="@url(repository)/pull/@issue.issueId/delete/@encodeRefName(pullreq.requestBranch)" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a>
|
|
||||||
<div>
|
|
||||||
<span class="strong">Pull request successfully merged and closed</span>
|
|
||||||
</div>
|
|
||||||
<span class="small muted">You're all set-the <span class="label label-info monospace">@pullreq.requestBranch</span> branch can be safely deleted.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
|
|
||||||
}
|
}
|
||||||
</div>
|
@if(hasWritePermission && issue.closed && pullreq.userName == pullreq.requestUserName && merged &&
|
||||||
<div class="col-md-3">
|
pullreq.repositoryName == pullreq.requestRepositoryName && repository.branchList.contains(pullreq.requestBranch)){
|
||||||
@issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
<div class="box issue-comment-box" style="background-color: #d0eeff;">
|
||||||
</div>
|
<div class="box-content"class="issue-content" style="border: 1px solid #87a8c9; padding: 10px;">
|
||||||
|
<a href="@url(repository)/pull/@issue.issueId/delete/@encodeRefName(pullreq.requestBranch)" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a>
|
||||||
|
<div>
|
||||||
|
<span class="strong">Pull request successfully merged and closed</span>
|
||||||
|
</div>
|
||||||
|
<span class="small muted">You're all set-the <span class="label label-info monospace">@pullreq.requestBranch</span> branch can be safely deleted.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@issues.html.commentform(issue, !merged, hasWritePermission, repository)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
@issues.html.issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
|
|||||||
@@ -13,26 +13,24 @@
|
|||||||
@if(!status.statuses.isEmpty){
|
@if(!status.statuses.isEmpty){
|
||||||
<div class="build-statuses">
|
<div class="build-statuses">
|
||||||
@defining(status.commitStateSummary){ case (summaryState, summary) =>
|
@defining(status.commitStateSummary){ case (summaryState, summary) =>
|
||||||
<div class="build-status-item-header">
|
<a class="pull-right" id="toggle-all-checks"></a>
|
||||||
<a class="pull-right" id="toggle-all-checks"></a>
|
<span class="build-status-icon text-@{summaryState.name}">@commitStateIcon(summaryState)</span>
|
||||||
<span class="build-status-icon text-@{summaryState.name}">@commitStateIcon(summaryState)</span>
|
<strong class="text-@{summaryState.name}">@commitStateText(summaryState, pullreq.commitIdTo)</strong>
|
||||||
<strong class="text-@{summaryState.name}">@commitStateText(summaryState, pullreq.commitIdTo)</strong>
|
<span class="text-@{summaryState.name}">— @summary checks</span>
|
||||||
<span class="text-@{summaryState.name}">— @summary checks</span>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<div class="build-statuses-list" style="@if(status.isAllSuccess){ display:none; }else{ }">
|
</div>
|
||||||
@status.statusesAndRequired.map{ case (status, required) =>
|
<div class="build-statuses-list" style="@if(status.isAllSuccess){ display:none; }">
|
||||||
|
@status.statusesAndRequired.map { case (status, required) =>
|
||||||
<div class="build-status-item">
|
<div class="build-status-item">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@if(required){ <span class="label">Required</span> }
|
@if(required){ <span class="label">Required</span> }
|
||||||
@status.targetUrl.map{ url => <a href="@url">Details</a> }
|
@status.targetUrl.map { url => <a href="@url">Details</a> }
|
||||||
</div>
|
</div>
|
||||||
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
|
<span class="build-status-icon text-@{status.state.name}">@commitStateIcon(status.state)</span>
|
||||||
<strong>@status.context</strong>
|
<strong>@status.context</strong>
|
||||||
@status.description.map{ desc => <span class="muted">— @desc</span> }
|
@status.description.map { desc => <span class="muted">— @desc</span> }
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div style="padding:15px">
|
<div style="padding:15px">
|
||||||
@@ -46,43 +44,47 @@
|
|||||||
Only those with write access to this repository can merge pull requests.
|
Only those with write access to this repository can merge pull requests.
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} else { @if(status.branchIsOutOfDate){
|
} else {
|
||||||
@if(status.hasUpdatePermission){
|
@if(status.branchIsOutOfDate){
|
||||||
<div class="pull-right">
|
@if(status.hasUpdatePermission){
|
||||||
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/update_branch">
|
<div class="pull-right">
|
||||||
<input type="hidden" name="expected_head_oid" value="@pullreq.commitIdFrom">
|
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/update_branch">
|
||||||
<button class="btn btn-default"@if(!status.canUpdate){ disabled="true"} id="update-branch-button">Update branch</button>
|
<input type="hidden" name="expected_head_oid" value="@pullreq.commitIdFrom">
|
||||||
</form>
|
<button class="btn btn-default"@if(!status.canUpdate){ disabled="true"} id="update-branch-button">Update branch</button>
|
||||||
</div>
|
</form>
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
<div class="merge-indicator merge-indicator-alert"><span class="octicon octicon-alert"></span></div>
|
<div class="merge-indicator merge-indicator-alert"><span class="octicon octicon-alert"></span></div>
|
||||||
<span class="strong">This branch is out-of-date with the base branch</span>
|
<span class="strong">This branch is out-of-date with the base branch</span>
|
||||||
<div class="small">
|
<div class="small">
|
||||||
Merge the latest changes from <code>@pullreq.branch</code> into this branch.
|
Merge the latest changes from <code>@pullreq.branch</code> into this branch.
|
||||||
</div>
|
</div>
|
||||||
} else { @if(status.hasRequiredStatusProblem) {
|
|
||||||
<div class="merge-indicator merge-indicator-warning"><span class="octicon octicon-primitive-dot"></span></div>
|
|
||||||
<span class="strong">Required statuses must pass before merging.</span>
|
|
||||||
<div class="small">
|
|
||||||
All required status checks on this pull request must run successfully to enable automatic merging.
|
|
||||||
</div>
|
|
||||||
} else {
|
} else {
|
||||||
<div class="merge-indicator merge-indicator-success"><span class="octicon octicon-check"></span></div>
|
@if(status.hasRequiredStatusProblem) {
|
||||||
@if(status.hasMergePermission){
|
<div class="merge-indicator merge-indicator-warning"><span class="octicon octicon-primitive-dot"></span></div>
|
||||||
<span class="strong">Merging can be performed automatically.</span>
|
<span class="strong">Required statuses must pass before merging.</span>
|
||||||
<div class="small">
|
<div class="small">
|
||||||
Merging can be performed automatically.
|
All required status checks on this pull request must run successfully to enable automatic merging.
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
<span class="strong">This branch has no conflicts with the base branch.</span>
|
<div class="merge-indicator merge-indicator-success"><span class="octicon octicon-check"></span></div>
|
||||||
<div class="small">
|
@if(status.hasMergePermission){
|
||||||
Only those with write access to this repository can merge pull requests.
|
<span class="strong">Merging can be performed automatically.</span>
|
||||||
</div>
|
<div class="small">
|
||||||
|
Merging can be performed automatically.
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<span class="strong">This branch has no conflicts with the base branch.</span>
|
||||||
|
<div class="small">
|
||||||
|
Only those with write access to this repository can merge pull requests.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} } }
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
@if(status.hasMergePermission){
|
@if(status.hasMergePermission){
|
||||||
<div style="padding:15px;border-top:solid 1px #e5e5e5;background:#fafafa">
|
<div style="padding:15px; border-top:solid 1px #e5e5e5; background:#fafafa">
|
||||||
<input type="button" class="btn @if(!status.hasProblem){btn-success} else {btn-default}" id="merge-pull-request-button" value="Merge pull request"@if(!status.canMerge){ disabled="true"}/>
|
<input type="button" class="btn @if(!status.hasProblem){btn-success} else {btn-default}" 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>.
|
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;">
|
<div id="command-line" style="display: none;margin-top: 15px;">
|
||||||
@@ -133,15 +135,15 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div id="confirm-merge-form" style="display: none;">
|
<div id="confirm-merge-form" style="display: none; padding: 12px;">
|
||||||
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge">
|
<form method="POST" action="@url(originRepository)/pull/@pullreq.issueId/merge">
|
||||||
<div class="strong">
|
<div class="strong">
|
||||||
Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}
|
Merge pull request #@issue.issueId from @{pullreq.requestUserName}/@{pullreq.requestBranch}
|
||||||
</div>
|
</div>
|
||||||
<span id="error-message" class="error"></span>
|
<span id="error-message" class="error"></span>
|
||||||
<textarea name="message" style="height: 80px;" class="form-control">@issue.title</textarea>
|
<textarea name="message" style="height: 80px; margin-top: 8px; margin-bottom: 8px;" class="form-control">@issue.title</textarea>
|
||||||
<div>
|
<div>
|
||||||
<input type="button" class="btn" value="Cancel" id="cancel-merge-pull-request"/>
|
<input type="button" class="btn btn-default" value="Cancel" id="cancel-merge-pull-request"/>
|
||||||
<input type="submit" class="btn btn-success" value="Confirm merge"/>
|
<input type="submit" class="btn btn-success" value="Confirm merge"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -168,6 +170,10 @@ $(function(){
|
|||||||
$('#merge-pull-request').hide();
|
$('#merge-pull-request').hide();
|
||||||
$('#confirm-merge-form').show();
|
$('#confirm-merge-form').show();
|
||||||
});
|
});
|
||||||
|
$('#cancel-merge-pull-request').click(function(){
|
||||||
|
$('#merge-pull-request').show();
|
||||||
|
$('#confirm-merge-form').hide();
|
||||||
|
});
|
||||||
|
|
||||||
@forkedRepository.sshUrl.map { sshUrl =>
|
@forkedRepository.sshUrl.map { sshUrl =>
|
||||||
$('#repository-url-http').click(function(e){
|
$('#repository-url-http').click(function(e){
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<div class="edit-title pull-right" style="display: none;">
|
<div class="edit-title pull-right" style="display: none;">
|
||||||
<a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
|
<a class="btn btn-success" href="#" id="update">Save</a> <a class="btn btn-default" href="#" id="cancel">Cancel</a>
|
||||||
</div>
|
</div>
|
||||||
<h1>
|
<h1 class="body-title">
|
||||||
<span class="show-title">
|
<span class="show-title">
|
||||||
<span id="show-title">@issue.title</span>
|
<span id="show-title">@issue.title</span>
|
||||||
<span class="muted">#@issue.issueId</span>
|
<span class="muted">#@issue.issueId</span>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user