diff --git a/build.sbt b/build.sbt
index 4b43aef..dcbe896 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,6 +1,10 @@
val Organization = "gitbucket"
val Name = "gitbucket"
+<<<<<<< HEAD
+val GitBucketVersion = "4.0.0-SNAPSHOT"
+=======
val GitBucketVersion = "3.14.0"
+>>>>>>> master
val ScalatraVersion = "2.4.0"
val JettyVersion = "9.3.6.v20151106"
@@ -15,7 +19,9 @@
// dependency settings
resolvers ++= Seq(
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(
"org.scala-lang.modules" %% "scala-java8-compat" % "0.7.0",
@@ -26,6 +32,7 @@
"org.json4s" %% "json4s-jackson" % "3.3.0",
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
"commons-io" % "commons-io" % "2.4",
+ "io.github.gitbucket" % "solidbase" % "1.0.0",
"io.github.gitbucket" % "markedj" % "1.0.8",
"org.apache.commons" % "commons-compress" % "1.10",
"org.apache.commons" % "commons-email" % "1.4",
@@ -35,8 +42,10 @@
"com.typesafe.slick" %% "slick" % "2.1.0",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.190",
+ "mysql" % "mysql-connector-java" % "5.1.38",
+ "org.postgresql" % "postgresql" % "9.4.1208",
"ch.qos.logback" % "logback-classic" % "1.1.1",
- "com.mchange" % "c3p0" % "0.9.5.2",
+ "com.zaxxer" % "HikariCP" % "2.4.5",
"com.typesafe" % "config" % "1.3.0",
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
diff --git a/doc/release.md b/doc/release.md
index 98ba383..d677a26 100644
--- a/doc/release.md
+++ b/doc/release.md
@@ -11,22 +11,24 @@
```scala
val Organization = "gitbucket"
val Name = "gitbucket"
-val GitBucketVersion = "3.12.0" // <---- update version!!
+val GitBucketVersion = "4.0.0" // <---- update version!!
val ScalatraVersion = "2.4.0"
val JettyVersion = "9.3.6.v20151106"
```
-### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
+### src/main/scala/gitbucket/core/GitBucketCoreModule.scala
```scala
-object AutoUpdate {
-
- /**
- * The history of versions. A head of this sequence is the current GitBucket version.
- */
- val versions = Seq(
- new Version(3, 12), // <---- add this line!!
- new Version(3, 11),
+object GitBucketCoreModule extends Module("gitbucket-core",
+ // add new version definition
+ new Version("4.1.0",
+ new LiquibaseMigration("update/gitbucket-core_4.1.xml")
+ ),
+ new Version("4.0.0",
+ new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
+ new SqlMigration("update/gitbucket-core_4.0.sql")
+ )
+)
```
Generate release files
diff --git a/src/main/java/org/postgresql/Driver2.java b/src/main/java/org/postgresql/Driver2.java
new file mode 100644
index 0000000..5763656
--- /dev/null
+++ b/src/main/java/org/postgresql/Driver2.java
@@ -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);
+ }
+ }
+
+}
+
diff --git a/src/main/resources/database.conf b/src/main/resources/database.conf
deleted file mode 100644
index 0eca733..0000000
--- a/src/main/resources/database.conf
+++ /dev/null
@@ -1,6 +0,0 @@
-db {
- driver = "org.h2.Driver"
- url = "jdbc:h2:${DatabaseHome};MVCC=true"
- user = "sa"
- password = "sa"
-}
diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf
deleted file mode 100644
index 2f8087c..0000000
--- a/src/main/resources/reference.conf
+++ /dev/null
@@ -1,5 +0,0 @@
-c3p0 {
- privilegeSpawnedThreads=true
- contextClassLoaderSource=library
-}
-
diff --git a/src/main/resources/update/1_0.sql b/src/main/resources/update/1_0.sql
deleted file mode 100644
index 7d64af6..0000000
--- a/src/main/resources/update/1_0.sql
+++ /dev/null
@@ -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
-);
diff --git a/src/main/resources/update/1_1.sql b/src/main/resources/update/1_1.sql
deleted file mode 100644
index 9cfd50a..0000000
--- a/src/main/resources/update/1_1.sql
+++ /dev/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);
diff --git a/src/main/resources/update/1_12.sql b/src/main/resources/update/1_12.sql
deleted file mode 100644
index f8658a2..0000000
--- a/src/main/resources/update/1_12.sql
+++ /dev/null
@@ -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);
diff --git a/src/main/resources/update/1_13.sql b/src/main/resources/update/1_13.sql
deleted file mode 100644
index ed26f65..0000000
--- a/src/main/resources/update/1_13.sql
+++ /dev/null
@@ -1 +0,0 @@
-DROP TABLE COMMIT_LOG;
\ No newline at end of file
diff --git a/src/main/resources/update/1_2.sql b/src/main/resources/update/1_2.sql
deleted file mode 100644
index d2765bc..0000000
--- a/src/main/resources/update/1_2.sql
+++ /dev/null
@@ -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);
-
diff --git a/src/main/resources/update/1_3.sql b/src/main/resources/update/1_3.sql
deleted file mode 100644
index 59ab009..0000000
--- a/src/main/resources/update/1_3.sql
+++ /dev/null
@@ -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';
diff --git a/src/main/resources/update/1_4.sql b/src/main/resources/update/1_4.sql
deleted file mode 100644
index 2d3c492..0000000
--- a/src/main/resources/update/1_4.sql
+++ /dev/null
@@ -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);
diff --git a/src/main/resources/update/1_5.sql b/src/main/resources/update/1_5.sql
deleted file mode 100644
index 03fc1bf..0000000
--- a/src/main/resources/update/1_5.sql
+++ /dev/null
@@ -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;
diff --git a/src/main/resources/update/1_6.sql b/src/main/resources/update/1_6.sql
deleted file mode 100644
index 43eb92d..0000000
--- a/src/main/resources/update/1_6.sql
+++ /dev/null
@@ -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);
diff --git a/src/main/resources/update/1_7.sql b/src/main/resources/update/1_7.sql
deleted file mode 100644
index 9005ff9..0000000
--- a/src/main/resources/update/1_7.sql
+++ /dev/null
@@ -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;
diff --git a/src/main/resources/update/1_8.sql b/src/main/resources/update/1_8.sql
deleted file mode 100644
index 8b50ddf..0000000
--- a/src/main/resources/update/1_8.sql
+++ /dev/null
@@ -1 +0,0 @@
-ALTER TABLE ACCOUNT ADD COLUMN REMOVED BOOLEAN DEFAULT FALSE;
\ No newline at end of file
diff --git a/src/main/resources/update/2_3.sql b/src/main/resources/update/2_3.sql
deleted file mode 100644
index 7620092..0000000
--- a/src/main/resources/update/2_3.sql
+++ /dev/null
@@ -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);
diff --git a/src/main/resources/update/2_7.sql b/src/main/resources/update/2_7.sql
deleted file mode 100644
index 6fa0684..0000000
--- a/src/main/resources/update/2_7.sql
+++ /dev/null
@@ -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);
diff --git a/src/main/resources/update/2_8.sql b/src/main/resources/update/2_8.sql
deleted file mode 100644
index 38c95d3..0000000
--- a/src/main/resources/update/2_8.sql
+++ /dev/null
@@ -1 +0,0 @@
-ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);
diff --git a/src/main/resources/update/3_1.sql b/src/main/resources/update/3_1.sql
deleted file mode 100644
index 3ddc48a..0000000
--- a/src/main/resources/update/3_1.sql
+++ /dev/null
@@ -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;
diff --git a/src/main/resources/update/3_11.sql b/src/main/resources/update/3_11.sql
deleted file mode 100644
index 721690a..0000000
--- a/src/main/resources/update/3_11.sql
+++ /dev/null
@@ -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;
diff --git a/src/main/resources/update/3_13.sql b/src/main/resources/update/3_13.sql
deleted file mode 100644
index 7dacf6b..0000000
--- a/src/main/resources/update/3_13.sql
+++ /dev/null
@@ -1 +0,0 @@
-ALTER TABLE WEB_HOOK ADD COLUMN TOKEN VARCHAR(100);
diff --git a/src/main/resources/update/3_14.sql b/src/main/resources/update/3_14.sql
deleted file mode 100644
index 8738dd3..0000000
--- a/src/main/resources/update/3_14.sql
+++ /dev/null
@@ -1,3 +0,0 @@
-ALTER TABLE WEB_HOOK ADD COLUMN CTYPE VARCHAR(10);
-
-UPDATE WEB_HOOK SET CTYPE = 'form';
diff --git a/src/main/resources/update/3_9.sql b/src/main/resources/update/3_9.sql
deleted file mode 100644
index b2f5c56..0000000
--- a/src/main/resources/update/3_9.sql
+++ /dev/null
@@ -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;
\ No newline at end of file
diff --git a/src/main/resources/update/gitbucket-core_4.0.sql b/src/main/resources/update/gitbucket-core_4.0.sql
new file mode 100644
index 0000000..f97bdbc
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.0.sql
@@ -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);
diff --git a/src/main/resources/update/gitbucket-core_4.0.xml b/src/main/resources/update/gitbucket-core_4.0.xml
new file mode 100644
index 0000000..04f33fe
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.0.xml
@@ -0,0 +1,351 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
new file mode 100644
index 0000000..3b85c72
--- /dev/null
+++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
@@ -0,0 +1,11 @@
+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")
+ )
+)
diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala
index e74f9d4..f52491a 100644
--- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala
+++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala
@@ -6,9 +6,11 @@
import gitbucket.core.util._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
+import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
+import org.scalatra
import org.scalatra._
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
import org.apache.commons.io.{IOUtils, FileUtils}
@@ -76,6 +78,21 @@
} 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 = {
implicit val session = Database.getSession(request)
loginAccount match {
diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
index 157a584..ed578d0 100644
--- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
@@ -1,5 +1,7 @@
package gitbucket.core.controller
+import java.io.FileInputStream
+
import gitbucket.core.admin.html
import gitbucket.core.service.{AccountService, SystemSettingsService, RepositoryService}
import gitbucket.core.util.AdminAuthenticator
@@ -11,7 +13,7 @@
import gitbucket.core.util.Directory._
import gitbucket.core.util.StringUtil._
import io.github.gitbucket.scalatra.forms._
-import org.apache.commons.io.FileUtils
+import org.apache.commons.io.{IOUtils, FileUtils}
import org.scalatra.i18n.Messages
class SystemSettingsController extends SystemSettingsControllerBase
@@ -74,6 +76,7 @@
case class PluginForm(pluginIds: List[String])
+ case class DataExportForm(tableNames: List[String])
case class NewUserForm(userName: String, password: String, fullName: String,
mailAddress: String, isAdmin: Boolean,
@@ -268,6 +271,32 @@
}
})
+ 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(){
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if(value.split(",").exists {
diff --git a/src/main/scala/gitbucket/core/model/Plugin.scala b/src/main/scala/gitbucket/core/model/Plugin.scala
deleted file mode 100644
index 1e8aac5..0000000
--- a/src/main/scala/gitbucket/core/model/Plugin.scala
+++ /dev/null
@@ -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
-)
diff --git a/src/main/scala/gitbucket/core/model/Profile.scala b/src/main/scala/gitbucket/core/model/Profile.scala
index 0badf86..26bb225 100644
--- a/src/main/scala/gitbucket/core/model/Profile.scala
+++ b/src/main/scala/gitbucket/core/model/Profile.scala
@@ -1,5 +1,6 @@
package gitbucket.core.model
+import gitbucket.core.util.DatabaseConfig
trait Profile {
val profile: slick.driver.JdbcProfile
@@ -28,7 +29,9 @@
}
trait ProfileProvider { self: Profile =>
- val profile = slick.driver.H2Driver
+
+ lazy val profile = DatabaseConfig.slickDriver
+
}
trait CoreProfile extends ProfileProvider with Profile
@@ -49,7 +52,6 @@
with SshKeyComponent
with WebHookComponent
with WebHookEventComponent
- with PluginComponent
with ProtectedBranchComponent
object Profile extends CoreProfile
diff --git a/src/main/scala/gitbucket/core/plugin/Plugin.scala b/src/main/scala/gitbucket/core/plugin/Plugin.scala
index 82c5867..caaf726 100644
--- a/src/main/scala/gitbucket/core/plugin/Plugin.scala
+++ b/src/main/scala/gitbucket/core/plugin/Plugin.scala
@@ -6,7 +6,7 @@
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.ControlUtil._
-import gitbucket.core.util.Version
+import io.github.gitbucket.solidbase.model.Version
/**
* Trait for define plugin interface.
diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala
index 12a4656..d784d87 100644
--- a/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala
+++ b/src/main/scala/gitbucket/core/plugin/PluginRegistory.scala
@@ -10,9 +10,11 @@
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.ControlUtil._
+import gitbucket.core.util.DatabaseConfig
import gitbucket.core.util.Directory._
-import gitbucket.core.util.JDBCUtil._
-import gitbucket.core.util.{Version, Versions}
+import io.github.gitbucket.solidbase.Solidbase
+import io.github.gitbucket.solidbase.model.Module
+import liquibase.database.core.H2Database
import org.apache.commons.codec.binary.{Base64, StringUtils}
import org.slf4j.LoggerFactory
@@ -187,30 +189,15 @@
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin]
// Migration
- val headVersion = plugin.versions.head
- val currentVersion = conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match {
- 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)
- }
- }
+ val solidbase = new Solidbase()
+ solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
// Initialize
plugin.initialize(instance, context, settings)
instance.addPlugin(PluginInfo(
pluginId = plugin.pluginId,
pluginName = plugin.pluginName,
- version = plugin.versions.head.versionString,
+ version = plugin.versions.head.getVersion,
description = plugin.description,
pluginClass = plugin
))
diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala
index 86430a9..529492d 100644
--- a/src/main/scala/gitbucket/core/service/IssuesService.scala
+++ b/src/main/scala/gitbucket/core/service/IssuesService.scala
@@ -108,25 +108,41 @@
}
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"""
- SELECT SUMM.USER_NAME, SUMM.REPOSITORY_NAME, SUMM.ISSUE_ID, CS_ALL, CS_SUCCESS
- , CSD.CONTEXT, CSD.STATE, CSD.TARGET_URL, CSD.DESCRIPTION
- FROM (SELECT
- PR.USER_NAME
- , PR.REPOSITORY_NAME
- , PR.ISSUE_ID
- , COUNT(CS.STATE) AS CS_ALL
- , SUM(CS.STATE='success') AS CS_SUCCESS
- , PR.COMMIT_ID_TO AS COMMIT_ID
+ SELECT
+ SUMM.USER_NAME,
+ SUMM.REPOSITORY_NAME,
+ SUMM.ISSUE_ID,
+ CS_ALL,
+ CS_SUCCESS,
+ CSD.CONTEXT,
+ CSD.STATE,
+ 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
JOIN COMMIT_STATUS CS
- ON PR.USER_NAME=CS.USER_NAME
- AND PR.REPOSITORY_NAME=CS.REPOSITORY_NAME
- AND PR.COMMIT_ID_TO=CS.COMMIT_ID
- WHERE $issueIdQuery
- GROUP BY PR.USER_NAME, PR.REPOSITORY_NAME, PR.ISSUE_ID) as SUMM
+ ON PR.USER_NAME = CS.USER_NAME AND PR.REPOSITORY_NAME = CS.REPOSITORY_NAME AND PR.COMMIT_ID_TO = CS.COMMIT_ID
+ JOIN (
+ SELECT
+ COUNT(*) AS CS_SUCCESS,
+ 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
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) =>
(userName, repositoryName, issueId) -> CommitStatusInfo(count, successCount, context, state, targetUrl, description)
}.toMap
diff --git a/src/main/scala/gitbucket/core/service/PluginService.scala b/src/main/scala/gitbucket/core/service/PluginService.scala
deleted file mode 100644
index 99a20d8..0000000
--- a/src/main/scala/gitbucket/core/service/PluginService.scala
+++ /dev/null
@@ -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
-
-}
diff --git a/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala b/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
deleted file mode 100644
index 22b05dd..0000000
--- a/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
+++ /dev/null
@@ -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)
- }
-
-}
diff --git a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala
index a375b37..f494a4a 100644
--- a/src/main/scala/gitbucket/core/servlet/InitializeListener.scala
+++ b/src/main/scala/gitbucket/core/servlet/InitializeListener.scala
@@ -1,16 +1,22 @@
package gitbucket.core.servlet
+import java.io.File
+
import akka.event.Logging
import com.typesafe.config.ConfigFactory
+import gitbucket.core.GitBucketCoreModule
import gitbucket.core.plugin.PluginRegistry
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 org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
-import gitbucket.core.util.Versions
import akka.actor.{Actor, Props, ActorSystem}
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
-import AutoUpdate._
/**
* Initialize GitBucket system.
@@ -30,14 +36,49 @@
Database() withTransaction { session =>
val conn = session.conn
- // Migration
- logger.debug("Start schema update")
- Versions.update(conn, headVersion, getCurrentVersion(), versions, Thread.currentThread.getContextClassLoader){ conn =>
- FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8")
+ // Check version
+ val versionFile = new File(GitBucketHome, "version")
+
+ 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
- logger.debug("Initialize plugins")
+ logger.info("Initialize plugins")
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
}
diff --git a/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala b/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala
index fc2e457..bad06e1 100644
--- a/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala
+++ b/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala
@@ -2,7 +2,7 @@
import javax.servlet._
import javax.servlet.http.HttpServletRequest
-import com.mchange.v2.c3p0.ComboPooledDataSource
+import com.zaxxer.hikari._
import gitbucket.core.util.DatabaseConfig
import org.scalatra.ScalatraBase
import org.slf4j.LoggerFactory
@@ -46,14 +46,14 @@
private val logger = LoggerFactory.getLogger(Database.getClass)
- private val dataSource: ComboPooledDataSource = {
- val ds = new ComboPooledDataSource
- ds.setDriverClass(DatabaseConfig.driver)
- ds.setJdbcUrl(DatabaseConfig.url)
- ds.setUser(DatabaseConfig.user)
- ds.setPassword(DatabaseConfig.password)
+ private val dataSource: HikariDataSource = {
+ val config = new HikariConfig()
+ config.setDriverClassName(DatabaseConfig.jdbcDriver)
+ config.setJdbcUrl(DatabaseConfig.url)
+ config.setUsername(DatabaseConfig.user)
+ config.setPassword(DatabaseConfig.password)
logger.debug("load database connection pool")
- ds
+ new HikariDataSource(config)
}
private val db: SlickDatabase = {
diff --git a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala
index ffe7cee..ec68d0b 100644
--- a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala
+++ b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala
@@ -1,19 +1,83 @@
package gitbucket.core.util
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 {
- private val config = ConfigFactory.load("database")
- private val dbUrl = config.getString("db.url")
+ private lazy val config = {
+ 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 =
dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome))
- val url: String = url(None)
- val user: String = config.getString("db.user")
- val password: String = config.getString("db.password")
- val driver: String = config.getString("db.driver")
+ lazy val url: String = url(None)
+ lazy val user: String = config.getString("db.user")
+ lazy val password: String = config.getString("db.password")
+ 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()
+ }
+}
diff --git a/src/main/scala/gitbucket/core/util/JDBCUtil.scala b/src/main/scala/gitbucket/core/util/JDBCUtil.scala
index 41a2b1a..60116a5 100644
--- a/src/main/scala/gitbucket/core/util/JDBCUtil.scala
+++ b/src/main/scala/gitbucket/core/util/JDBCUtil.scala
@@ -1,7 +1,13 @@
package gitbucket.core.util
+import java.io._
import java.sql._
+import java.text.SimpleDateFormat
+import javax.xml.stream.{XMLStreamConstants, XMLInputFactory, XMLOutputFactory}
import ControlUtil._
+import scala.StringBuilder
+import scala.annotation.tailrec
+import scala.collection.mutable
import scala.collection.mutable.ListBuffer
/**
@@ -58,6 +64,265 @@
}
}
+ 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())
+ }
}
}
diff --git a/src/main/scala/gitbucket/core/util/Version.scala b/src/main/scala/gitbucket/core/util/Version.scala
deleted file mode 100644
index 3c7ffe6..0000000
--- a/src/main/scala/gitbucket/core/util/Version.scala
+++ /dev/null
@@ -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")
- }
-
-}
-
diff --git a/src/main/twirl/gitbucket/core/admin/data.scala.html b/src/main/twirl/gitbucket/core/admin/data.scala.html
new file mode 100644
index 0000000..669d75d
--- /dev/null
+++ b/src/main/twirl/gitbucket/core/admin/data.scala.html
@@ -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") {
+
+
+
+
Import (only XML)
+
+
+
+
+ }
+}
+
\ No newline at end of file
diff --git a/src/main/twirl/gitbucket/core/admin/menu.scala.html b/src/main/twirl/gitbucket/core/admin/menu.scala.html
index edc7d7b..fedc73c 100644
--- a/src/main/twirl/gitbucket/core/admin/menu.scala.html
+++ b/src/main/twirl/gitbucket/core/admin/menu.scala.html
@@ -12,6 +12,9 @@
Plugins
+
+ Data export / import
+
H2 Console
diff --git a/src/main/twirl/gitbucket/core/admin/system.scala.html b/src/main/twirl/gitbucket/core/admin/system.scala.html
index 5139e70..7001150 100644
--- a/src/main/twirl/gitbucket/core/admin/system.scala.html
+++ b/src/main/twirl/gitbucket/core/admin/system.scala.html
@@ -9,11 +9,23 @@
System Settings
-
-
-
-
- @GitBucketHome
+
+
+
+
+
+ Property |
+ Value |
+
+
+ GITBUCKET_HOME |
+ @GitBucketHome |
+
+
+ DATBASE_URL |
+ @gitbucket.core.util.DatabaseConfig.url |
+
+
diff --git a/src/main/twirl/gitbucket/core/main.scala.html b/src/main/twirl/gitbucket/core/main.scala.html
index fa30715..c595945 100644
--- a/src/main/twirl/gitbucket/core/main.scala.html
+++ b/src/main/twirl/gitbucket/core/main.scala.html
@@ -1,6 +1,5 @@
@(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.servlet.AutoUpdate
@import context._
@import gitbucket.core.view.helpers._
@@ -51,9 +50,7 @@
*@
GitBucket
- @defining(AutoUpdate.getCurrentVersion){ version =>
-
- }
+
@if(loginAccount.isDefined){
@repository.map { repository =>
diff --git a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala
index 06aaaf7..87eb29a 100644
--- a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala
+++ b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala
@@ -1,11 +1,16 @@
package gitbucket.core.service
-import gitbucket.core.servlet.AutoUpdate
+import gitbucket.core.GitBucketCoreModule
import gitbucket.core.util.{ControlUtil, DatabaseConfig, FileUtil}
import gitbucket.core.util.ControlUtil._
import gitbucket.core.model._
import gitbucket.core.model.Profile._
+import io.github.gitbucket.solidbase.Solidbase
+import liquibase.database.core.H2Database
+import liquibase.database.jvm.JdbcConnection
import profile.simple._
+import scalaz._
+import Scalaz._
import org.apache.commons.io.FileUtils
@@ -22,7 +27,9 @@
val (url, user, pass) = (DatabaseConfig.url(Some(dir.toString)), DatabaseConfig.user, DatabaseConfig.password)
org.h2.Driver.load()
using(DriverManager.getConnection(url, user, pass)){ conn =>
- AutoUpdate.versions.reverse.foreach(_.update(conn, Thread.currentThread.getContextClassLoader))
+ val solidbase = new Solidbase()
+ val db = new H2Database() <| { _.setConnection(new JdbcConnection(conn)) } // TODO Remove setConnection in the future
+ solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, db, GitBucketCoreModule)
}
Database.forURL(url, user, pass).withSession { session =>
action(session)