mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 12:15:35 +02:00
Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
247b664654 | ||
|
|
b3db0a6a7b | ||
|
|
0a03e41f1c | ||
|
|
a79f105eea | ||
|
|
74f3f6bf2e | ||
|
|
a4bf73724d | ||
|
|
8d91253ede | ||
|
|
b7355af49a | ||
|
|
7ec85cbf99 | ||
|
|
a6790b049d | ||
|
|
dce747b1e8 | ||
|
|
c22ee8acfd | ||
|
|
30b8b738b6 | ||
|
|
b916595da3 | ||
|
|
1accafa8b1 | ||
|
|
11700f4cb4 | ||
|
|
c9de8dd323 | ||
|
|
fd694e38aa | ||
|
|
8822c36b5f | ||
|
|
e614e31162 | ||
|
|
ad47ad4269 | ||
|
|
90b63090cc | ||
|
|
345685ed7c | ||
|
|
1d9fe5770e | ||
|
|
0c50545cbd | ||
|
|
53cbc36a01 | ||
|
|
85b2053004 | ||
|
|
eba240de65 | ||
|
|
1e5114cd54 | ||
|
|
90cb5de5f0 | ||
|
|
11d33e9389 | ||
|
|
c71e9331ae | ||
|
|
ec307b84d3 | ||
|
|
f37b5fa682 | ||
|
|
8cb1ac734d | ||
|
|
05ff2a854c | ||
|
|
d956ade5e3 | ||
|
|
73228506a5 | ||
|
|
2525bbafa8 | ||
|
|
338946dd3a | ||
|
|
2d225641ee | ||
|
|
3c727fe678 | ||
|
|
523ea0d437 | ||
|
|
9eff8f248b | ||
|
|
d50c858a26 | ||
|
|
6f4e94ba9a | ||
|
|
f2750c20a2 | ||
|
|
2da2b426a1 | ||
|
|
5a0bc127b7 | ||
|
|
23a482bbba | ||
|
|
6c2fce1b16 | ||
|
|
5d7346db91 | ||
|
|
443498433d | ||
|
|
a58ce07736 | ||
|
|
1903c3990c | ||
|
|
90441c8eec | ||
|
|
601919bcc6 | ||
|
|
6903b096f5 | ||
|
|
e44fed09fa | ||
|
|
8ed0c8a170 | ||
|
|
31118ac285 | ||
|
|
c851b7582f | ||
|
|
102a02d527 | ||
|
|
1968bf871a | ||
|
|
5cd4e48173 | ||
|
|
111d212cb5 | ||
|
|
d648d34393 | ||
|
|
bbaa5b38e7 | ||
|
|
3c50a78be2 | ||
|
|
82cc1fa530 | ||
|
|
8c0581973e | ||
|
|
bfa15f5d75 | ||
|
|
57e87b581d | ||
|
|
6431d25409 | ||
|
|
c8686f4b34 | ||
|
|
43097b4c1c | ||
|
|
539751a1d9 | ||
|
|
ff3205b6c7 | ||
|
|
40e36e3f8b | ||
|
|
3d1c9bc9de | ||
|
|
5a5bf34fe0 | ||
|
|
af7043f4bf | ||
|
|
249b27593e | ||
|
|
1201271949 | ||
|
|
19e74a4fe1 | ||
|
|
130aa1e515 | ||
|
|
e92d1eae5a | ||
|
|
8b44a00299 | ||
|
|
f5c1c0703d | ||
|
|
5036b1a1aa |
12
README.md
12
README.md
@@ -63,6 +63,18 @@ Support
|
|||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
--------
|
||||||
|
### 4.0 - 30 Apr 2016
|
||||||
|
|
||||||
|
- MySQL and PostgreSQL support
|
||||||
|
- Data export and import
|
||||||
|
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
|
||||||
|
|
||||||
|
### 3.14 - 30 Apr 2016
|
||||||
|
|
||||||
|
- File attachment and search for wiki pages
|
||||||
|
- New extension points to add menus
|
||||||
|
- Content-Type of webhooks has been choosable
|
||||||
|
|
||||||
### 3.13 - 1 Apr 2016
|
### 3.13 - 1 Apr 2016
|
||||||
- Refresh user interface for wide screen
|
- Refresh user interface for wide screen
|
||||||
- Add `pull_request` key in list issues API for pull requests
|
- Add `pull_request` key in list issues API for pull requests
|
||||||
|
|||||||
13
build.sbt
13
build.sbt
@@ -1,6 +1,6 @@
|
|||||||
val Organization = "gitbucket"
|
val Organization = "gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "3.13.0"
|
val GitBucketVersion = "4.0.0"
|
||||||
val ScalatraVersion = "2.4.0"
|
val ScalatraVersion = "2.4.0"
|
||||||
val JettyVersion = "9.3.6.v20151106"
|
val JettyVersion = "9.3.6.v20151106"
|
||||||
|
|
||||||
@@ -15,7 +15,9 @@ 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",
|
||||||
@@ -26,7 +28,8 @@ libraryDependencies ++= Seq(
|
|||||||
"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.7",
|
"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-compress" % "1.10",
|
||||||
"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",
|
||||||
@@ -35,8 +38,10 @@ libraryDependencies ++= Seq(
|
|||||||
"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.190",
|
||||||
|
"mysql" % "mysql-connector-java" % "5.1.38",
|
||||||
|
"org.postgresql" % "postgresql" % "9.4.1208",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
"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" % "config" % "1.3.0",
|
||||||
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
"com.typesafe.akka" %% "akka-actor" % "2.3.14",
|
||||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
// add new version definition
|
||||||
/**
|
new Version("4.1.0",
|
||||||
* The history of versions. A head of this sequence is the current GitBucket version.
|
new LiquibaseMigration("update/gitbucket-core_4.1.xml")
|
||||||
*/
|
),
|
||||||
val versions = Seq(
|
new Version("4.0.0",
|
||||||
new Version(3, 12), // <---- add this line!!
|
new LiquibaseMigration("update/gitbucket-core_4.0.xml"),
|
||||||
new Version(3, 11),
|
new SqlMigration("update/gitbucket-core_4.0.sql")
|
||||||
|
)
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Generate release files
|
Generate release files
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<extension>
|
<extension>
|
||||||
<groupId>org.apache.maven.wagon</groupId>
|
<groupId>org.apache.maven.wagon</groupId>
|
||||||
<artifactId>wagon-ssh</artifactId>
|
<artifactId>wagon-ssh</artifactId>
|
||||||
<version>1.0-beta-6</version>
|
<version>2.10</version>
|
||||||
</extension>
|
</extension>
|
||||||
</extensions>
|
</extensions>
|
||||||
</build>
|
</build>
|
||||||
|
|||||||
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"
|
|
||||||
}
|
|
||||||
@@ -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,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>
|
||||||
11
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
11
src/main/scala/gitbucket/core/GitBucketCoreModule.scala
Normal file
@@ -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")
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -1,18 +1,26 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.util.{Keys, FileUtil}
|
import gitbucket.core.model.Account
|
||||||
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
|
import gitbucket.core.servlet.Database
|
||||||
|
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.dircache.DirCache
|
||||||
|
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.FileUtils
|
import org.apache.commons.io.{IOUtils, FileUtils}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Ajax based file upload functionality.
|
* Provides Ajax based file upload functionality.
|
||||||
*
|
*
|
||||||
* This servlet saves uploaded file.
|
* This servlet saves uploaded file.
|
||||||
*/
|
*/
|
||||||
class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
class FileUploadController extends ScalatraServlet with FileUploadSupport with RepositoryService with AccountService {
|
||||||
|
|
||||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||||
|
|
||||||
@@ -31,6 +39,69 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
|||||||
}, FileUtil.isUploadableType)
|
}, FileUtil.isUploadableType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post("/wiki/:owner/:repository"){
|
||||||
|
// Don't accept not logged-in users
|
||||||
|
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account =>
|
||||||
|
val owner = params("owner")
|
||||||
|
val repository = params("repository")
|
||||||
|
|
||||||
|
// Check whether logged-in user is collaborator
|
||||||
|
collaboratorsOnly(owner, repository, loginAccount){
|
||||||
|
execute({ (file, fileId) =>
|
||||||
|
val fileName = file.getName
|
||||||
|
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||||
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||||
|
val builder = DirCache.newInCore.builder()
|
||||||
|
val inserter = git.getRepository.newObjectInserter()
|
||||||
|
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||||
|
|
||||||
|
if(headId != null){
|
||||||
|
JGitUtil.processTree(git, headId){ (path, tree) =>
|
||||||
|
if(path != fileName){
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bytes = IOUtils.toByteArray(file.getInputStream)
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(fileName, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes)))
|
||||||
|
builder.finish()
|
||||||
|
|
||||||
|
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||||
|
Constants.HEAD, loginAccount.userName, loginAccount.mailAddress, s"Uploaded ${fileName}")
|
||||||
|
|
||||||
|
fileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, FileUtil.isImage)
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
|
case x if(x.isAdmin) => action
|
||||||
|
case x if(getCollaborators(owner, repository).contains(x.userName)) => action
|
||||||
|
case _ => BadRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
||||||
case Some(file) if(mimeTypeChcker(file.name)) =>
|
case Some(file) if(mimeTypeChcker(file.name)) =>
|
||||||
defining(FileUtil.generateFileId){ fileId =>
|
defining(FileUtil.generateFileId){ fileId =>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.api._
|
|
||||||
import gitbucket.core.helper.xml
|
import gitbucket.core.helper.xml
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService, RepositorySearchService, IssuesService}
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
|
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator, ReferrerAuthenticator, StringUtil}
|
||||||
@@ -138,13 +137,21 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issue" => gitbucket.core.search.html.issues(
|
case "issue" => gitbucket.core.search.html.issues(
|
||||||
searchIssues(repository.owner, repository.name, query),
|
|
||||||
countFiles(repository.owner, repository.name, query),
|
countFiles(repository.owner, repository.name, query),
|
||||||
|
searchIssues(repository.owner, repository.name, query),
|
||||||
|
countWikiPages(repository.owner, repository.name, query),
|
||||||
|
query, page, repository)
|
||||||
|
|
||||||
|
case "wiki" => gitbucket.core.search.html.wiki(
|
||||||
|
countFiles(repository.owner, repository.name, query),
|
||||||
|
countIssues(repository.owner, repository.name, query),
|
||||||
|
searchWikiPages(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
|
|
||||||
case _ => gitbucket.core.search.html.code(
|
case _ => gitbucket.core.search.html.code(
|
||||||
searchFiles(repository.owner, repository.name, query),
|
searchFiles(repository.owner, repository.name, query),
|
||||||
countIssues(repository.owner, repository.name, query),
|
countIssues(repository.owner, repository.name, query),
|
||||||
|
countWikiPages(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.scalatra.i18n.Messages
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
import org.eclipse.jgit.lib.ObjectId
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
|
import gitbucket.core.model.WebHookContentType
|
||||||
|
|
||||||
|
|
||||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||||
@@ -49,13 +50,16 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
)(CollaboratorForm.apply)
|
)(CollaboratorForm.apply)
|
||||||
|
|
||||||
// for web hook url addition
|
// for web hook url addition
|
||||||
case class WebHookForm(url: String, events: Set[WebHook.Event], token: Option[String])
|
case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
|
||||||
|
|
||||||
def webHookForm(update:Boolean) = mapping(
|
def webHookForm(update:Boolean) = mapping(
|
||||||
"url" -> trim(label("url", text(required, webHook(update)))),
|
"url" -> trim(label("url", text(required, webHook(update)))),
|
||||||
"events" -> webhookEvents,
|
"events" -> webhookEvents,
|
||||||
|
"ctype" -> label("ctype", text()),
|
||||||
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||||
)(WebHookForm.apply)
|
)(
|
||||||
|
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
|
||||||
|
)
|
||||||
|
|
||||||
// for transfer ownership
|
// for transfer ownership
|
||||||
case class TransferOwnerShipForm(newOwner: String)
|
case class TransferOwnerShipForm(newOwner: String)
|
||||||
@@ -183,7 +187,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the web hook edit page.
|
* Display the web hook edit page.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
|
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
|
||||||
val webhook = WebHook(repository.owner, repository.name, "", None)
|
val webhook = WebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None)
|
||||||
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
|
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -191,7 +195,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Add the web hook URL.
|
* Add the web hook URL.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
|
||||||
addWebHook(repository.owner, repository.name, form.url, form.events, form.token)
|
addWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
|
||||||
flash += "info" -> s"Webhook ${form.url} created"
|
flash += "info" -> s"Webhook ${form.url} created"
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
})
|
})
|
||||||
@@ -221,7 +225,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
val url = params("url")
|
val url = params("url")
|
||||||
val token = Some(params("token"))
|
val token = Some(params("token"))
|
||||||
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, token)
|
val ctype = WebHookContentType.valueOf(params("ctype"))
|
||||||
|
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url, ctype, token)
|
||||||
val dummyPayload = {
|
val dummyPayload = {
|
||||||
val ownerAccount = getAccountByUserName(repository.owner).get
|
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||||
val commits = if(repository.commitCount == 0) List.empty else git.log
|
val commits = if(repository.commitCount == 0) List.empty else git.log
|
||||||
@@ -280,7 +285,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Update web hook settings.
|
* Update web hook settings.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
|
||||||
updateWebHook(repository.owner, repository.name, form.url, form.events, form.token)
|
updateWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
|
||||||
flash += "info" -> s"webhook ${form.url} updated"
|
flash += "info" -> s"webhook ${form.url} updated"
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
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, SystemSettingsService, RepositoryService}
|
||||||
import gitbucket.core.util.AdminAuthenticator
|
import gitbucket.core.util.AdminAuthenticator
|
||||||
@@ -11,7 +13,7 @@ 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.{IOUtils, FileUtils}
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
@@ -74,6 +76,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
case class PluginForm(pluginIds: List[String])
|
case class PluginForm(pluginIds: List[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,
|
||||||
@@ -268,6 +271,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 {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import org.eclipse.jgit.api.Git
|
|||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
|
||||||
class WikiController extends WikiControllerBase
|
class WikiController extends WikiControllerBase
|
||||||
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator
|
with WikiService with RepositoryService with AccountService with ActivityService
|
||||||
|
with CollaboratorsAuthenticator with ReferrerAuthenticator
|
||||||
|
|
||||||
trait WikiControllerBase extends ControllerBase {
|
trait WikiControllerBase extends ControllerBase {
|
||||||
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -3,21 +3,42 @@ package gitbucket.core.model
|
|||||||
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
|
|
||||||
|
implicit val whContentTypeColumnType = MappedColumnType.base[WebHookContentType, String](whct => whct.code , code => WebHookContentType.valueOf(code))
|
||||||
|
|
||||||
lazy val WebHooks = TableQuery[WebHooks]
|
lazy val WebHooks = TableQuery[WebHooks]
|
||||||
|
|
||||||
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
||||||
val url = column[String]("URL")
|
val url = column[String]("URL")
|
||||||
val token = column[Option[String]]("TOKEN", O.Nullable)
|
val token = column[Option[String]]("TOKEN", O.Nullable)
|
||||||
def * = (userName, repositoryName, url, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
|
val ctype = column[WebHookContentType]("CTYPE", O.NotNull)
|
||||||
|
def * = (userName, repositoryName, url, ctype, token) <> ((WebHook.apply _).tupled, WebHook.unapply)
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class WebHookContentType(val code: String, val ctype: String)
|
||||||
|
|
||||||
|
object WebHookContentType {
|
||||||
|
object JSON extends WebHookContentType("json", "application/json")
|
||||||
|
|
||||||
|
object FORM extends WebHookContentType("form", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
val values: Vector[WebHookContentType] = Vector(JSON, FORM)
|
||||||
|
|
||||||
|
private val map: Map[String, WebHookContentType] = values.map(enum => enum.code -> enum).toMap
|
||||||
|
|
||||||
|
def apply(code: String): WebHookContentType = map(code)
|
||||||
|
|
||||||
|
def valueOf(code: String): WebHookContentType = map(code)
|
||||||
|
def valueOpt(code: String): Option[WebHookContentType] = map.get(code)
|
||||||
|
}
|
||||||
|
|
||||||
case class WebHook(
|
case class WebHook(
|
||||||
userName: String,
|
userName: String,
|
||||||
repositoryName: String,
|
repositoryName: String,
|
||||||
url: String,
|
url: String,
|
||||||
|
ctype: WebHookContentType,
|
||||||
token: Option[String]
|
token: Option[String]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package gitbucket.core.plugin
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
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.
|
||||||
* To provide plugin, put Plugin class which mixed in this trait into the package root.
|
* To provide a plugin, put a Plugin class which extends this class into the package root.
|
||||||
*/
|
*/
|
||||||
trait Plugin {
|
abstract class Plugin {
|
||||||
|
|
||||||
val pluginId: String
|
val pluginId: String
|
||||||
val pluginName: String
|
val pluginName: String
|
||||||
@@ -77,6 +79,76 @@ trait Plugin {
|
|||||||
*/
|
*/
|
||||||
def receiveHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[ReceiveHook] = Nil
|
def receiveHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[ReceiveHook] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add global menus.
|
||||||
|
*/
|
||||||
|
val globalMenus: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add global menus.
|
||||||
|
*/
|
||||||
|
def globalMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository menus.
|
||||||
|
*/
|
||||||
|
val repositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository menus.
|
||||||
|
*/
|
||||||
|
def repositoryMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository setting tabs.
|
||||||
|
*/
|
||||||
|
val repositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository setting tabs.
|
||||||
|
*/
|
||||||
|
def repositorySettingTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add profile tabs.
|
||||||
|
*/
|
||||||
|
val profileTabs: Seq[(Account, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add profile tabs.
|
||||||
|
*/
|
||||||
|
def profileTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Account, Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add system setting menus.
|
||||||
|
*/
|
||||||
|
val systemSettingMenus: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add system setting menus.
|
||||||
|
*/
|
||||||
|
def systemSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add account setting menus.
|
||||||
|
*/
|
||||||
|
val accountSettingMenus: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add account setting menus.
|
||||||
|
*/
|
||||||
|
def accountSettingMenus(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add dashboard tabs.
|
||||||
|
*/
|
||||||
|
val dashboardTabs: Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add dashboard tabs.
|
||||||
|
*/
|
||||||
|
def dashboardTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is invoked in initialization of plugin system.
|
* This method is invoked in initialization of plugin system.
|
||||||
* Register plugin functionality to PluginRegistry.
|
* Register plugin functionality to PluginRegistry.
|
||||||
@@ -100,6 +172,27 @@ trait Plugin {
|
|||||||
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook =>
|
(receiveHooks ++ receiveHooks(registry, context, settings)).foreach { receiveHook =>
|
||||||
registry.addReceiveHook(receiveHook)
|
registry.addReceiveHook(receiveHook)
|
||||||
}
|
}
|
||||||
|
(globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu =>
|
||||||
|
registry.addGlobalMenu(globalMenu)
|
||||||
|
}
|
||||||
|
(repositoryMenus ++ repositoryMenus(registry, context, settings)).foreach { repositoryMenu =>
|
||||||
|
registry.addRepositoryMenu(repositoryMenu)
|
||||||
|
}
|
||||||
|
(repositorySettingTabs ++ repositorySettingTabs(registry, context, settings)).foreach { repositorySettingTab =>
|
||||||
|
registry.addRepositorySettingTab(repositorySettingTab)
|
||||||
|
}
|
||||||
|
(profileTabs ++ profileTabs(registry, context, settings)).foreach { profileTab =>
|
||||||
|
registry.addProfileTab(profileTab)
|
||||||
|
}
|
||||||
|
(systemSettingMenus ++ systemSettingMenus(registry, context, settings)).foreach { systemSettingMenu =>
|
||||||
|
registry.addSystemSettingMenu(systemSettingMenu)
|
||||||
|
}
|
||||||
|
(accountSettingMenus ++ accountSettingMenus(registry, context, settings)).foreach { accountSettingMenu =>
|
||||||
|
registry.addAccountSettingMenu(accountSettingMenu)
|
||||||
|
}
|
||||||
|
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
|
||||||
|
registry.addDashboardTab(dashboardTab)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,16 +3,18 @@ package gitbucket.core.plugin
|
|||||||
import java.io.{File, FilenameFilter, InputStream}
|
import java.io.{File, FilenameFilter, InputStream}
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
|
||||||
|
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
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
|
||||||
|
|
||||||
@@ -33,6 +35,14 @@ class PluginRegistry {
|
|||||||
private val receiveHooks = new ListBuffer[ReceiveHook]
|
private val receiveHooks = new ListBuffer[ReceiveHook]
|
||||||
receiveHooks += new ProtectedBranchReceiveHook()
|
receiveHooks += new ProtectedBranchReceiveHook()
|
||||||
|
|
||||||
|
private val globalMenus = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
|
||||||
|
private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
|
||||||
|
private val profileTabs = new ListBuffer[(Account, Context) => Option[Link]]
|
||||||
|
private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]]
|
||||||
|
|
||||||
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
||||||
plugins += pluginInfo
|
plugins += pluginInfo
|
||||||
}
|
}
|
||||||
@@ -107,17 +117,47 @@ class PluginRegistry {
|
|||||||
|
|
||||||
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
||||||
|
|
||||||
private case class GlobalAction(
|
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = {
|
||||||
method: String,
|
globalMenus += globalMenu
|
||||||
path: String,
|
}
|
||||||
function: (HttpServletRequest, HttpServletResponse, Context) => Any
|
|
||||||
)
|
|
||||||
|
|
||||||
private case class RepositoryAction(
|
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
|
||||||
method: String,
|
|
||||||
path: String,
|
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = {
|
||||||
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any
|
repositoryMenus += repositoryMenu
|
||||||
)
|
}
|
||||||
|
|
||||||
|
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq
|
||||||
|
|
||||||
|
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = {
|
||||||
|
repositorySettingTabs += repositorySettingTab
|
||||||
|
}
|
||||||
|
|
||||||
|
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq
|
||||||
|
|
||||||
|
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = {
|
||||||
|
profileTabs += profileTab
|
||||||
|
}
|
||||||
|
|
||||||
|
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq
|
||||||
|
|
||||||
|
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = {
|
||||||
|
systemSettingMenus += systemSettingMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq
|
||||||
|
|
||||||
|
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = {
|
||||||
|
accountSettingMenus += accountSettingMenu
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq
|
||||||
|
|
||||||
|
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = {
|
||||||
|
dashboardTabs += dashboardTab
|
||||||
|
}
|
||||||
|
|
||||||
|
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,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
|
||||||
))
|
))
|
||||||
@@ -201,6 +226,8 @@ object PluginRegistry {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case class Link(id: String, label: String, path: String, icon: Option[String] = None)
|
||||||
|
|
||||||
case class PluginInfo(
|
case class PluginInfo(
|
||||||
pluginId: String,
|
pluginId: String,
|
||||||
pluginName: String,
|
pluginName: String,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -53,7 +53,30 @@ trait RepositorySearchService { self: IssuesService =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
|
def countWikiPages(owner: String, repository: String, query: String): Int =
|
||||||
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
|
if(JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
||||||
|
}
|
||||||
|
|
||||||
|
def searchWikiPages(owner: String, repository: String, query: String): List[FileSearchResult] =
|
||||||
|
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))){ git =>
|
||||||
|
if(JGitUtil.isEmpty(git)){
|
||||||
|
Nil
|
||||||
|
} else {
|
||||||
|
val files = searchRepositoryFiles(git, query)
|
||||||
|
val commits = JGitUtil.getLatestCommitFromPaths(git, files.map(_._1), "HEAD")
|
||||||
|
files.map { case (path, text) =>
|
||||||
|
val (highlightText, lineNumber) = getHighlightText(text, query)
|
||||||
|
FileSearchResult(
|
||||||
|
path.replaceFirst("\\.md$", ""),
|
||||||
|
commits(path).getCommitterIdent.getWhen,
|
||||||
|
highlightText,
|
||||||
|
lineNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def searchRepositoryFiles(git: Git, query: String): List[(String, String)] = {
|
||||||
val revWalk = new RevWalk(git.getRepository)
|
val revWalk = new RevWalk(git.getRepository)
|
||||||
val objectId = git.getRepository.resolve("HEAD")
|
val objectId = git.getRepository.resolve("HEAD")
|
||||||
val revCommit = revWalk.parseCommit(objectId)
|
val revCommit = revWalk.parseCommit(objectId)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
|
||||||
import fr.brouillard.oss.security.xhub.XHub
|
import fr.brouillard.oss.security.xhub.XHub
|
||||||
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
|
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
@@ -12,7 +11,6 @@ import profile.simple._
|
|||||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
import gitbucket.core.util.RepositoryName
|
import gitbucket.core.util.RepositoryName
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
import org.apache.http.NameValuePair
|
import org.apache.http.NameValuePair
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity
|
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||||
import org.apache.http.message.BasicNameValuePair
|
import org.apache.http.message.BasicNameValuePair
|
||||||
@@ -22,6 +20,8 @@ import org.slf4j.LoggerFactory
|
|||||||
import scala.concurrent._
|
import scala.concurrent._
|
||||||
import org.apache.http.HttpRequest
|
import org.apache.http.HttpRequest
|
||||||
import org.apache.http.HttpResponse
|
import org.apache.http.HttpResponse
|
||||||
|
import gitbucket.core.model.WebHookContentType
|
||||||
|
import org.apache.http.client.entity.EntityBuilder
|
||||||
|
|
||||||
|
|
||||||
trait WebHookService {
|
trait WebHookService {
|
||||||
@@ -52,15 +52,15 @@ trait WebHookService {
|
|||||||
.map{ case (w,t) => w -> t.event }
|
.map{ case (w,t) => w -> t.event }
|
||||||
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
|
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
|
||||||
|
|
||||||
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
|
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
|
||||||
WebHooks insert WebHook(owner, repository, url, token)
|
WebHooks insert WebHook(owner, repository, url, ctype, token)
|
||||||
events.toSet.map{ event: WebHook.Event =>
|
events.toSet.map{ event: WebHook.Event =>
|
||||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], token: Option[String])(implicit s: Session): Unit = {
|
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])(implicit s: Session): Unit = {
|
||||||
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => w.token).update(token)
|
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).map(w => (w.ctype, w.token)).update((ctype, token))
|
||||||
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
|
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
|
||||||
events.toSet.map{ event: WebHook.Event =>
|
events.toSet.map{ event: WebHook.Event =>
|
||||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||||
@@ -100,19 +100,29 @@ trait WebHookService {
|
|||||||
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
|
val httpClient = HttpClientBuilder.create.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)
|
||||||
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
logger.info(s"Content-Type: ${webHook.ctype.ctype}")
|
||||||
|
httpPost.addHeader("Content-Type", webHook.ctype.ctype)
|
||||||
httpPost.addHeader("X-Github-Event", event.name)
|
httpPost.addHeader("X-Github-Event", event.name)
|
||||||
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
||||||
|
|
||||||
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
webHook.ctype match {
|
||||||
params.add(new BasicNameValuePair("payload", json))
|
case WebHookContentType.FORM => {
|
||||||
def postContent = new UrlEncodedFormEntity(params, "UTF-8")
|
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
||||||
httpPost.setEntity(postContent)
|
params.add(new BasicNameValuePair("payload", json))
|
||||||
|
def postContent = new UrlEncodedFormEntity(params, "UTF-8")
|
||||||
if (!webHook.token.isEmpty) {
|
httpPost.setEntity(postContent)
|
||||||
// TODO find a better way and see how to extract content from postContent
|
if (webHook.token.exists(_.trim.nonEmpty)) {
|
||||||
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
|
// TODO find a better way and see how to extract content from postContent
|
||||||
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, contentAsBytes))
|
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
|
||||||
|
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.get, contentAsBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case WebHookContentType.JSON => {
|
||||||
|
httpPost.setEntity(EntityBuilder.create().setText(json).build())
|
||||||
|
if (!webHook.token.isEmpty) {
|
||||||
|
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8")))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val res = httpClient.execute(httpPost)
|
val res = httpClient.execute(httpPost)
|
||||||
|
|||||||
@@ -1,186 +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, 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 = {
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.main(account.userName){
|
@html.main(account.userName){
|
||||||
<div class="container body">
|
<div class="container body">
|
||||||
<div style="float: left; width: 250px;">
|
<div class="main-sidebar">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="account-image">@avatar(account.userName, 240)</div>
|
<div class="account-image">@avatar(account.userName, 240)</div>
|
||||||
<div class="account-fullname">@account.fullName</div>
|
<div class="account-fullname">@account.fullName</div>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-left: 260px; overflow: hidden;">
|
<div class="main-content">
|
||||||
<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){
|
||||||
@@ -33,6 +33,11 @@
|
|||||||
} else {
|
} else {
|
||||||
<li@if(active == "activity"){ class="active"}><a href="@url(account.userName)?tab=activity">Public Activity</a></li>
|
<li@if(active == "activity"){ class="active"}><a href="@url(account.userName)?tab=activity">Public Activity</a></li>
|
||||||
}
|
}
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab =>
|
||||||
|
@tab(account, context).map { link =>
|
||||||
|
<li@if(active == link.id){ class="active"}><a href="@path/@link.path">@link.label</a></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
@if(loginAccount.isDefined && loginAccount.get.userName == account.userName){
|
@if(loginAccount.isDefined && loginAccount.get.userName == account.userName){
|
||||||
<li class="pull-right">
|
<li class="pull-right">
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
|
|||||||
@@ -13,6 +13,13 @@
|
|||||||
<li@if(active=="application"){ class="active"}>
|
<li@if(active=="application"){ class="active"}>
|
||||||
<a href="@path/@loginAccount.get.userName/_application">Applications</a>
|
<a href="@path/@loginAccount.get.userName/_application">Applications</a>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
|
|||||||
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>
|
||||||
@@ -12,9 +12,19 @@
|
|||||||
<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">H2 Console</a>
|
||||||
</li>
|
</li>
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
|
||||||
|
@menu(context).map { link =>
|
||||||
|
<li@if(active==link.id){ class="active"}>
|
||||||
|
<a href="@path/@link.path">@link.label</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
|
|||||||
@@ -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>DATBASE_URL</td>
|
||||||
|
<td>@gitbucket.core.util.DatabaseConfig.url</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<!-- Base URL -->
|
<!-- Base URL -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
|
|||||||
@@ -11,12 +11,10 @@
|
|||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.main("Issues"){
|
@html.main("Issues"){
|
||||||
@sidebar(recentRepositories, userRepositories){
|
@sidebar(recentRepositories, userRepositories){
|
||||||
<div style="overflow: hidden;">
|
@dashboard.html.tab("issues")
|
||||||
@dashboard.html.tab("issues")
|
<div class="container">
|
||||||
<div class="container">
|
@issuesnavi(filter, openCount, closedCount, condition)
|
||||||
@issuesnavi(filter, openCount, closedCount, condition)
|
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||||
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,10 @@
|
|||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.main("Pull Requests"){
|
@html.main("Pull Requests"){
|
||||||
@sidebar(recentRepositories, userRepositories){
|
@sidebar(recentRepositories, userRepositories){
|
||||||
<div style="overflow: hidden;">
|
@dashboard.html.tab("pulls")
|
||||||
@dashboard.html.tab("pulls")
|
<div class="container">
|
||||||
<div class="container">
|
@issuesnavi(filter, openCount, closedCount, condition)
|
||||||
@issuesnavi(filter, openCount, closedCount, condition)
|
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||||
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,5 +6,10 @@
|
|||||||
@if(loginAccount.isDefined){
|
@if(loginAccount.isDefined){
|
||||||
<li @if(active == "pulls" ){ class="active"}><a href="@path/dashboard/pulls">Pull Requests</a></li>
|
<li @if(active == "pulls" ){ class="active"}><a href="@path/dashboard/pulls">Pull Requests</a></li>
|
||||||
<li @if(active == "issues"){ class="active"}><a href="@path/dashboard/issues">Issues</a></li>
|
<li @if(active == "issues"){ class="active"}><a href="@path/dashboard/issues">Issues</a></li>
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
|
||||||
|
@tab(context).map { link =>
|
||||||
|
<li @if(active == link.id){ class="active"}><a href="@path/@link.path">@link.label</a></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -11,14 +11,12 @@
|
|||||||
@Html(information)
|
@Html(information)
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div style="overflow: hidden;">
|
@dashboard.html.tab()
|
||||||
@dashboard.html.tab()
|
<div class="container">
|
||||||
<div class="container">
|
<div class="pull-right">
|
||||||
<div class="pull-right">
|
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
|
||||||
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
|
|
||||||
</div>
|
|
||||||
@helper.html.activities(activities)
|
|
||||||
</div>
|
</div>
|
||||||
|
@helper.html.activities(activities)
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.menu("issues", repository){
|
@html.menu("issues", repository){
|
||||||
<form action="@url(repository)/issues/new" method="POST" validate="true" class="form-group">
|
<form action="@url(repository)/issues/new" method="POST" validate="true" class="form-group">
|
||||||
<div class="row">
|
<div class="row-fluid">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<span id="error-title" class="error"></span>
|
<span id="error-title" class="error"></span>
|
||||||
<input type="text" id="issue-title" name="title" class="form-control" value="" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
|
<input type="text" id="issue-title" name="title" class="form-control" value="" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row" style="margin-top: 15px;">
|
<div class="row-fluid" 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)
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -51,9 +50,7 @@
|
|||||||
*@
|
*@
|
||||||
<a class="navbar-brand" href="@path/">
|
<a class="navbar-brand" href="@path/">
|
||||||
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px; display: inline;"/>GitBucket
|
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px; display: inline;"/>GitBucket
|
||||||
@defining(AutoUpdate.getCurrentVersion){ version =>
|
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.get(0).getVersion</span>
|
||||||
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
|
||||||
}
|
|
||||||
</a>
|
</a>
|
||||||
@if(loginAccount.isDefined){
|
@if(loginAccount.isDefined){
|
||||||
@repository.map { repository =>
|
@repository.map { repository =>
|
||||||
@@ -71,6 +68,11 @@
|
|||||||
<input type="hidden" name="repository" value="@repository.name"/>
|
<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){
|
@if(loginAccount.isDefined){
|
||||||
<div class="pull-right" style="margin-top: 6px;">
|
<div class="pull-right" style="margin-top: 6px;">
|
||||||
<div class="btn-group" style="margin-right: 8px;">
|
<div class="btn-group" style="margin-right: 8px;">
|
||||||
|
|||||||
@@ -6,13 +6,16 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
|
|
||||||
@menuitem(path: String, name: String, label: 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">
|
<a href="@url(repository)@path">
|
||||||
@label
|
<i class="menu-icon octicon octicon-@icon"></i>
|
||||||
@if(count > 0){
|
<span class="pc">
|
||||||
<span class="badge">@count</span>
|
@label
|
||||||
}
|
@if(count > 0){
|
||||||
|
<span class="badge">@count</span>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
@@ -41,19 +44,24 @@
|
|||||||
<div class="container body">
|
<div class="container body">
|
||||||
<div class="main-sidebar">
|
<div class="main-sidebar">
|
||||||
<ul class="nav nav-pills nav-stacked">
|
<ul class="nav nav-pills nav-stacked">
|
||||||
@menuitem("" ,"files" ,"Files")
|
@menuitem("" ,"files" ,"Files", "code")
|
||||||
@if(repository.commitCount != 0) {
|
@if(repository.commitCount != 0) {
|
||||||
@menuitem("/branches" ,"branches" ,"Branches", repository.branchList.length)
|
@menuitem("/branches" ,"branches" ,"Branches", "git-branch", repository.branchList.length)
|
||||||
@menuitem("/tags" ,"tags" ,"Tags", repository.tags.length)
|
@menuitem("/tags" ,"tags" ,"Tags", "tag", repository.tags.length)
|
||||||
}
|
}
|
||||||
@menuitem("/issues" ,"issues" ,"Issues", repository.issueCount)
|
@menuitem("/issues" ,"issues" ,"Issues", "issue-opened", repository.issueCount)
|
||||||
@menuitem("/pulls" ,"pulls" ,"Pull Requests", repository.pullCount)
|
@menuitem("/pulls" ,"pulls" ,"Pull Requests", "git-pull-request", repository.pullCount)
|
||||||
@menuitem("/issues/labels" ,"labels" ,"Labels")
|
@menuitem("/issues/labels" ,"labels" ,"Labels", "tag")
|
||||||
@menuitem("/issues/milestones" ,"milestones" ,"Milestones")
|
@menuitem("/issues/milestones" ,"milestones" ,"Milestones", "milestone")
|
||||||
@menuitem("/wiki" ,"wiki" ,"Wiki")
|
@menuitem("/wiki" ,"wiki" ,"Wiki", "book")
|
||||||
@menuitem("/network/members", "fork", "Forks", repository.forkedCount)
|
@menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount)
|
||||||
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
|
@if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){
|
||||||
@menuitem("/settings" , "settings" , "Settings")
|
@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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@(files: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
|
@(files: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
|
||||||
issueCount: Int,
|
issueCount: Int,
|
||||||
|
wikiCount: Int,
|
||||||
query: String,
|
query: String,
|
||||||
page: Int,
|
page: Int,
|
||||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||||
@@ -7,7 +8,7 @@
|
|||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@import gitbucket.core.service.RepositorySearchService._
|
@import gitbucket.core.service.RepositorySearchService._
|
||||||
@html.main("Search Results", Some(repository)){
|
@html.main("Search Results", Some(repository)){
|
||||||
@menu("code", files.size, issueCount, query, repository){
|
@menu("code", files.size, issueCount, wikiCount, query, repository){
|
||||||
@if(files.isEmpty){
|
@if(files.isEmpty){
|
||||||
<h4>We couldn't find any code matching '@query'</h4>
|
<h4>We couldn't find any code matching '@query'</h4>
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@(issues: List[gitbucket.core.service.RepositorySearchService.IssueSearchResult],
|
@(fileCount: Int,
|
||||||
fileCount: Int,
|
issues: List[gitbucket.core.service.RepositorySearchService.IssueSearchResult],
|
||||||
|
wikiCount: Int,
|
||||||
query: String,
|
query: String,
|
||||||
page: Int,
|
page: Int,
|
||||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||||
@@ -7,7 +8,7 @@
|
|||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@import gitbucket.core.service.RepositorySearchService._
|
@import gitbucket.core.service.RepositorySearchService._
|
||||||
@html.main("Search Results", Some(repository)){
|
@html.main("Search Results", Some(repository)){
|
||||||
@menu("issue", fileCount, issues.size, query, repository){
|
@menu("issue", fileCount, issues.size, wikiCount, query, repository){
|
||||||
@if(issues.isEmpty){
|
@if(issues.isEmpty){
|
||||||
<h4>We couldn't find any code matching '@query'</h4>
|
<h4>We couldn't find any code matching '@query'</h4>
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
@(active: String, fileCount: Int, issueCount: Int, query: String,
|
@(active: String, fileCount: Int, issueCount: Int, wikiCount: Int, query: String,
|
||||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@html.menu("", repository){
|
@html.menu("", repository){
|
||||||
<div style="overflow: hidden;">
|
|
||||||
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
||||||
<li@if(active=="code"){ class="active"}>
|
<li@if(active=="code"){ class="active"}>
|
||||||
<a href="@url(repository)/search?q=@urlEncode(query)&type=code">
|
<a href="@url(repository)/search?q=@urlEncode(query)&type=code">
|
||||||
@@ -21,6 +20,14 @@
|
|||||||
}
|
}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li@if(active=="wiki"){ class="active"}>
|
||||||
|
<a href="@url(repository)/search?q=@urlEncode(query)&type=wiki">
|
||||||
|
Wiki
|
||||||
|
@if(wikiCount != 0){
|
||||||
|
<span class="badge">@wikiCount</span>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<form action="@url(repository)/search" method="GET" class="form-inline">
|
<form action="@url(repository)/search" method="GET" class="form-inline">
|
||||||
<input type="text" name="q" value="@query" class="form-control" style="width: 400px; margin-bottom: 0px;"/>
|
<input type="text" name="q" value="@query" class="form-control" style="width: 400px; margin-bottom: 0px;"/>
|
||||||
@@ -28,5 +35,4 @@
|
|||||||
<input type="hidden" name="type" value="@active"/>
|
<input type="hidden" name="type" value="@active"/>
|
||||||
</form>
|
</form>
|
||||||
@body
|
@body
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
27
src/main/twirl/gitbucket/core/search/wiki.scala.html
Normal file
27
src/main/twirl/gitbucket/core/search/wiki.scala.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@(fileCount: Int,
|
||||||
|
issueCount: Int,
|
||||||
|
wikis: List[gitbucket.core.service.RepositorySearchService.FileSearchResult],
|
||||||
|
query: String,
|
||||||
|
page: Int,
|
||||||
|
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||||
|
@import context._
|
||||||
|
@import gitbucket.core.view.helpers._
|
||||||
|
@import gitbucket.core.service.RepositorySearchService._
|
||||||
|
@html.main("Search Results", Some(repository)){
|
||||||
|
@menu("wiki", fileCount, issueCount, wikis.size, query, repository){
|
||||||
|
@if(wikis.isEmpty){
|
||||||
|
<h4>We couldn't find any code matching '@query'</h4>
|
||||||
|
} else {
|
||||||
|
<h4>We've found @wikis.size code @plural(wikis.size, "result")</h4>
|
||||||
|
}
|
||||||
|
@wikis.drop((page - 1) * CodeLimit).take(CodeLimit).map { file =>
|
||||||
|
<div>
|
||||||
|
<h5><a href="@url(repository)/wiki/@file.path">@file.path</a></h5>
|
||||||
|
<div class="small muted">Last committed @helper.html.datetimeago(file.lastModified)</div>
|
||||||
|
<pre class="prettyprint linenums:@file.highlightLineNumber" style="padding-left: 25px;">@Html(file.highlightText)</pre>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@helper.html.paginator(page, wikis.size, CodeLimit, 10,
|
||||||
|
s"${url(repository)}/search?q=${urlEncode(query)}&type=code")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -107,7 +107,7 @@ function submitForm(e){
|
|||||||
var protection = getValue();
|
var protection = getValue();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method:'PATCH',
|
method:'PATCH',
|
||||||
url:'/api/v3/repos/@repository.owner/@repository.name/branches/@encodeRefName(branch)',
|
url:'@path/api/v3/repos/@repository.owner/@repository.name/branches/@encodeRefName(branch)',
|
||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data:JSON.stringify({protection:protection}),
|
data:JSON.stringify({protection:protection}),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@import context._
|
@import context._
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
@import gitbucket.core.model.WebHook._
|
@import gitbucket.core.model.WebHook._
|
||||||
|
@import gitbucket.core.model.WebHookContentType
|
||||||
@check(name: String, event: Event)={
|
@check(name: String, event: Event)={
|
||||||
name="@(name).@event.name" value="on" @if(events(event)){checked}
|
name="@(name).@event.name" value="on" @if(events(event)){checked}
|
||||||
}
|
}
|
||||||
@@ -30,6 +31,14 @@
|
|||||||
}
|
}
|
||||||
<button class="btn btn-default" id="test">Test Hook</button>
|
<button class="btn btn-default" id="test">Test Hook</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<fieldset class="form-group">
|
||||||
|
<label class="strong">Content type</label>
|
||||||
|
<div></div>
|
||||||
|
<select name="ctype">
|
||||||
|
<option value="@WebHookContentType.FORM.code" @if(webHook.ctype == WebHookContentType.FORM){selected}>@WebHookContentType.FORM.ctype</option>
|
||||||
|
<option value="@WebHookContentType.JSON.code" @if(webHook.ctype == WebHookContentType.JSON){selected}>@WebHookContentType.JSON.ctype</option>
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<label class="strong">Security Token</label>
|
<label class="strong">Security Token</label>
|
||||||
<div></div>
|
<div></div>
|
||||||
@@ -129,6 +138,7 @@ $(function(){
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var url = this.form.url.value;
|
var url = this.form.url.value;
|
||||||
var token = this.form.token.value;
|
var token = this.form.token.value;
|
||||||
|
var ctype = this.form.ctype.value;
|
||||||
if(!/^https?:\/\/.+/.test(url)){
|
if(!/^https?:\/\/.+/.test(url)){
|
||||||
alert("invalid url");
|
alert("invalid url");
|
||||||
return;
|
return;
|
||||||
@@ -136,9 +146,13 @@ $(function(){
|
|||||||
$("#test-modal-url").text(url)
|
$("#test-modal-url").text(url)
|
||||||
$("#test-report-modal").modal('show')
|
$("#test-report-modal").modal('show')
|
||||||
$("#test-report").hide();
|
$("#test-report").hide();
|
||||||
|
var targetUrl = '@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url) + '&token=';
|
||||||
|
if (token) {
|
||||||
|
targetUrl = targetUrl + encodeURIComponent(token);
|
||||||
|
}
|
||||||
$.ajax({
|
$.ajax({
|
||||||
method:'POST',
|
method:'POST',
|
||||||
url:'@url(repository)/settings/hooks/test?url=' + encodeURIComponent(url) + '&token=' + encodeURIComponent(token),
|
url:targetUrl,
|
||||||
success: function(e){
|
success: function(e){
|
||||||
//console.log(e);
|
//console.log(e);
|
||||||
$('#test-report-tab a:first').tab('show');
|
$('#test-report-tab a:first').tab('show');
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
@(active: String, repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
@(active: String, repository: 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 style="overflow: hidden;">
|
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
||||||
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
<li@if(active=="options"){ class="active"}>
|
||||||
<li@if(active=="options"){ class="active"}>
|
<a href="@url(repository)/settings/options">Options</a>
|
||||||
<a href="@url(repository)/settings/options">Options</a>
|
</li>
|
||||||
</li>
|
<li@if(active=="collaborators"){ class="active"}>
|
||||||
<li@if(active=="collaborators"){ class="active"}>
|
<a href="@url(repository)/settings/collaborators">Collaborators</a>
|
||||||
<a href="@url(repository)/settings/collaborators">Collaborators</a>
|
</li>
|
||||||
</li>
|
@if(!repository.branchList.isEmpty){
|
||||||
@if(!repository.branchList.isEmpty){
|
<li@if(active=="branches"){ class="active"}>
|
||||||
<li@if(active=="branches"){ class="active"}>
|
<a href="@url(repository)/settings/branches">Branches</a>
|
||||||
<a href="@url(repository)/settings/branches">Branches</a>
|
</li>
|
||||||
</li>
|
}
|
||||||
|
<li@if(active=="hooks"){ class="active"}>
|
||||||
|
<a href="@url(repository)/settings/hooks">Service Hooks</a>
|
||||||
|
</li>
|
||||||
|
<li@if(active=="danger"){ class="active"}>
|
||||||
|
<a href="@url(repository)/settings/danger">Danger Zone</a>
|
||||||
|
</li>
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getRepositorySettingTabs.map { tab =>
|
||||||
|
@tab(repository, context).map { link =>
|
||||||
|
<li@if(active==link.id){ class="active"}>
|
||||||
|
<a href="@url(repository)/@link.path">@link.label</a>
|
||||||
|
</li>
|
||||||
}
|
}
|
||||||
<li@if(active=="hooks"){ class="active"}>
|
}
|
||||||
<a href="@url(repository)/settings/hooks">Service Hooks</a>
|
</ul>
|
||||||
</li>
|
@body
|
||||||
<li@if(active=="danger"){ class="active"}>
|
|
||||||
<a href="@url(repository)/settings/danger">Danger Zone</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
@body
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@(pageName: String,
|
@(pageName: String,
|
||||||
page: Option[gitbucket.core.service.WikiService.WikiPageInfo],
|
page: Option[gitbucket.core.service.WikiService.WikiPageInfo],
|
||||||
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
|
||||||
@import context._
|
|
||||||
@import gitbucket.core.view.helpers._
|
@import gitbucket.core.view.helpers._
|
||||||
|
@import gitbucket.core.util.FileUtil
|
||||||
@html.main(s"${if(pageName.isEmpty) "New Page" else pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"${if(pageName.isEmpty) "New Page" else pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.menu("wiki", repository){
|
@html.menu("wiki", repository){
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
}
|
}
|
||||||
<a class="btn btn-small btn-success" href="@url(repository)/wiki/_new">New Page</a>
|
<a class="btn btn-small btn-success" href="@url(repository)/wiki/_new">New Page</a>
|
||||||
</div>
|
</div>
|
||||||
<h1><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||||
<div style="overflow: hidden;">
|
<form action="@url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
|
||||||
<form action="@url(repository)/wiki/@if(page.isEmpty){_new} else {_edit}" method="POST" validate="true">
|
<span id="error-pageName" class="error"></span>
|
||||||
<span id="error-pageName" class="error"></span>
|
<input type="text" name="pageName" value="@pageName" class="form-control" style="font-weight: bold; margin-bottom: 10px;" placeholder="Input a page name."/>
|
||||||
<input type="text" name="pageName" value="@pageName" class="form-control" style="font-weight: bold; margin-bottom: 10px;" placeholder="Input a page name."/>
|
<div class="muted attachable">
|
||||||
@helper.html.preview(
|
@helper.html.preview(
|
||||||
repository = repository,
|
repository = repository,
|
||||||
content = page.map(_.content).getOrElse(""),
|
content = page.map(_.content).getOrElse(""),
|
||||||
@@ -27,23 +27,44 @@
|
|||||||
hasWritePermission = false,
|
hasWritePermission = false,
|
||||||
style = "height: 400px;",
|
style = "height: 400px;",
|
||||||
styleClass = "monospace",
|
styleClass = "monospace",
|
||||||
placeholder = ""
|
placeholder = "",
|
||||||
|
uid = 1
|
||||||
)
|
)
|
||||||
<div class="form-group">
|
<div class="clickable">Attach images or documents by dragging & dropping, or selecting them.</div>
|
||||||
<label for="message">Edit Message</label>
|
</div>
|
||||||
<input type="text" id="message" name="message" value="" class="form-control" placeholder="Write a small message here explaining this change. (Optional)"/>
|
<div class="form-group">
|
||||||
</div>
|
<label for="message">Edit Message</label>
|
||||||
<div class="form-group pull-right">
|
<input type="text" id="message" name="message" value="" class="form-control" placeholder="Write a small message here explaining this change. (Optional)"/>
|
||||||
<input type="hidden" name="currentPageName" value="@pageName"/>
|
</div>
|
||||||
<input type="hidden" name="id" value="@page.map(_.id)"/>
|
<div class="form-group pull-right">
|
||||||
<input type="submit" value="Save" class="btn btn-success">
|
<input type="hidden" name="currentPageName" value="@pageName"/>
|
||||||
</div>
|
<input type="hidden" name="id" value="@page.map(_.id)"/>
|
||||||
</form>
|
<input type="submit" value="Save" class="btn btn-success">
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
|
try {
|
||||||
|
$('.clickable').dropzone({
|
||||||
|
url: '@context.path/upload/wiki/@repository.owner/@repository.name',
|
||||||
|
maxFilesize: 10,
|
||||||
|
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
|
||||||
|
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
|
||||||
|
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||||
|
success: function(file, id) {
|
||||||
|
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + '](' + file.name + ')';
|
||||||
|
$('#content1').val($('#content1').val() + attachFile);
|
||||||
|
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
if (e.message !== "Dropzone already attached.") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$('#delete').click(function(){
|
$('#delete').click(function(){
|
||||||
return confirm('Are you sure you want to delete this page?');
|
return confirm('Are you sure you want to delete this page?');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<span class="muted">History for</span> @pageName.get
|
<span class="muted">History for</span> @pageName.get
|
||||||
}
|
}
|
||||||
</h1>
|
</h1>
|
||||||
<table class="table table-bordered fill-width pull-left">
|
<table class="table table-bordered">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="3">
|
<th colspan="3">
|
||||||
@@ -38,9 +38,9 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
@commits.map { commit =>
|
@commits.map { commit =>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="0%"><input type="checkbox" name="commitId" value="@commit.id"></td>
|
<td style="width: 32px; text-align: center ;"><input type="checkbox" name="commitId" value="@commit.id"></td>
|
||||||
<td>@avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress)</td>
|
<td style="width: 200px;">@avatar(commit, 20) @user(commit.authorName, commit.authorEmailAddress)</td>
|
||||||
<td width="80%">
|
<td>
|
||||||
<span class="muted">@helper.html.datetimeago(commit.authorTime):</span> @commit.shortMessage
|
<span class="muted">@helper.html.datetimeago(commit.authorTime):</span> @commit.shortMessage
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -106,8 +106,8 @@
|
|||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('#show-more-pages').click(function(e){
|
$('#show-more-pages').click(function(e){
|
||||||
$('div.page-link').show();
|
$('li.page-link').show();
|
||||||
$(e.target).parents('div.show-more').remove();
|
$(e.target).parents('li.show-more').remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#show-pages-index').click(function(e){
|
$('#show-pages-index').click(function(e){
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ div.main-sidebar {
|
|||||||
|
|
||||||
div.main-content {
|
div.main-content {
|
||||||
margin-left: 260px;
|
margin-left: 260px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.main-center {
|
div.main-center {
|
||||||
@@ -215,6 +216,7 @@ div.dashboard-sidebar {
|
|||||||
|
|
||||||
div.dashboard-content {
|
div.dashboard-content {
|
||||||
margin-left: 310px;
|
margin-left: 310px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.body {
|
div.body {
|
||||||
@@ -1204,7 +1206,8 @@ table.diff td.body{
|
|||||||
}
|
}
|
||||||
|
|
||||||
table.diff th.line-num{
|
table.diff th.line-num{
|
||||||
min-width: 20px;
|
/*min-width: 20px;*/
|
||||||
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
table.diff .add-comment {
|
table.diff .add-comment {
|
||||||
@@ -1863,19 +1866,29 @@ div.container.blame-container{
|
|||||||
input[name=query] {
|
input[name=query] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#dashboard-signin-form {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.container {
|
.container {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
.body>div.pull-left {
|
|
||||||
width: auto !important;
|
|
||||||
}
|
|
||||||
.pc {
|
.pc {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.dashboard-sidebar {
|
||||||
|
display: none;;
|
||||||
|
}
|
||||||
|
div.dashboard-content {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.main-sidebar {
|
||||||
|
width: 40px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
div.main-content {
|
||||||
|
margin-left: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Adjust issue / comment form */
|
/* Adjust issue / comment form */
|
||||||
#issue-title {
|
#issue-title {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@@ -1903,17 +1916,8 @@ div.container.blame-container{
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
.nav-tabs a.btn[href$="/_edit"] {
|
|
||||||
width: 24px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 4px 6px;
|
|
||||||
margin: 3px 4px 0 0;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
body>div.container.body {
|
body>div.container.body {
|
||||||
margin: 0 -12px 40px -12px;
|
margin: 0 0 40px -12px;
|
||||||
}
|
}
|
||||||
/* Adjust sidemenu */
|
/* Adjust sidemenu */
|
||||||
.container.body>div[style="width: 170px;"]{
|
.container.body>div[style="width: 170px;"]{
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package gitbucket.core.service
|
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, DatabaseConfig, FileUtil}
|
||||||
import gitbucket.core.util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import gitbucket.core.model._
|
import gitbucket.core.model._
|
||||||
import gitbucket.core.model.Profile._
|
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 profile.simple._
|
||||||
|
import scalaz._
|
||||||
|
import Scalaz._
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
@@ -22,7 +27,9 @@ trait ServiceSpecBase {
|
|||||||
val (url, user, pass) = (DatabaseConfig.url(Some(dir.toString)), DatabaseConfig.user, DatabaseConfig.password)
|
val (url, user, pass) = (DatabaseConfig.url(Some(dir.toString)), DatabaseConfig.user, DatabaseConfig.password)
|
||||||
org.h2.Driver.load()
|
org.h2.Driver.load()
|
||||||
using(DriverManager.getConnection(url, user, pass)){ conn =>
|
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 =>
|
Database.forURL(url, user, pass).withSession { session =>
|
||||||
action(session)
|
action(session)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package gitbucket.core.service
|
|||||||
|
|
||||||
import gitbucket.core.model.WebHook
|
import gitbucket.core.model.WebHook
|
||||||
import org.scalatest.FunSuite
|
import org.scalatest.FunSuite
|
||||||
|
import gitbucket.core.model.WebHookContentType
|
||||||
|
|
||||||
|
|
||||||
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
||||||
@@ -16,12 +17,12 @@ class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
|||||||
val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser="root")
|
val (issue3, pullreq3) = generateNewPullRequest("user3/repo3/master3", "user2/repo2/master2", loginUser="root")
|
||||||
val (issue32, pullreq32) = generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser="root")
|
val (issue32, pullreq32) = generateNewPullRequest("user3/repo3/master32", "user2/repo2/master2", loginUser="root")
|
||||||
generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2")
|
generateNewPullRequest("user2/repo2/master2", "user1/repo1/master2")
|
||||||
service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest), Some("key"))
|
service.addWebHook("user1", "repo1", "webhook1-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||||
service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest), Some("key"))
|
service.addWebHook("user1", "repo1", "webhook1-2", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||||
service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest), Some("key"))
|
service.addWebHook("user2", "repo2", "webhook2-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||||
service.addWebHook("user2", "repo2", "webhook2-2", Set(WebHook.PullRequest), Some("key"))
|
service.addWebHook("user2", "repo2", "webhook2-2", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||||
service.addWebHook("user3", "repo3", "webhook3-1", Set(WebHook.PullRequest), Some("key"))
|
service.addWebHook("user3", "repo3", "webhook3-1", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||||
service.addWebHook("user3", "repo3", "webhook3-2", Set(WebHook.PullRequest), Some("key"))
|
service.addWebHook("user3", "repo3", "webhook3-2", Set(WebHook.PullRequest), WebHookContentType.FORM, Some("key"))
|
||||||
|
|
||||||
assert(service.getPullRequestsByRequestForWebhook("user1","repo1","master1") == Map.empty)
|
assert(service.getPullRequestsByRequestForWebhook("user1","repo1","master1") == Map.empty)
|
||||||
|
|
||||||
@@ -43,33 +44,36 @@ class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
|
|||||||
|
|
||||||
test("add and get and update and delete") { withTestDB { implicit session =>
|
test("add and get and update and delete") { withTestDB { implicit session =>
|
||||||
val user1 = generateNewUserWithDBRepository("user1","repo1")
|
val user1 = generateNewUserWithDBRepository("user1","repo1")
|
||||||
service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest), Some("key"))
|
val formType = WebHookContentType.FORM
|
||||||
assert(service.getWebHooks("user1", "repo1") == List((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.PullRequest))))
|
val jsonType = WebHookContentType.JSON
|
||||||
assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.PullRequest))))
|
service.addWebHook("user1", "repo1", "http://example.com", Set(WebHook.PullRequest), formType, Some("key"))
|
||||||
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((WebHook("user1","repo1","http://example.com", Some("key")))))
|
assert(service.getWebHooks("user1", "repo1") == List((WebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest))))
|
||||||
|
assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", formType, Some("key")),Set(WebHook.PullRequest))))
|
||||||
|
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List((WebHook("user1","repo1","http://example.com", formType, Some("key")))))
|
||||||
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == Nil)
|
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == Nil)
|
||||||
assert(service.getWebHook("user1", "repo1", "http://example.com2") == None)
|
assert(service.getWebHook("user1", "repo1", "http://example.com2") == None)
|
||||||
assert(service.getWebHook("user2", "repo1", "http://example.com") == None)
|
assert(service.getWebHook("user2", "repo1", "http://example.com") == None)
|
||||||
assert(service.getWebHook("user1", "repo2", "http://example.com") == None)
|
assert(service.getWebHook("user1", "repo2", "http://example.com") == None)
|
||||||
service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues), Some("key"))
|
service.updateWebHook("user1", "repo1", "http://example.com", Set(WebHook.Push, WebHook.Issues), jsonType, Some("key"))
|
||||||
assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", Some("key")),Set(WebHook.Push, WebHook.Issues))))
|
assert(service.getWebHook("user1", "repo1", "http://example.com") == Some((WebHook("user1","repo1","http://example.com", jsonType, Some("key")),Set(WebHook.Push, WebHook.Issues))))
|
||||||
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == Nil)
|
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == Nil)
|
||||||
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((WebHook("user1","repo1","http://example.com", Some("key")))))
|
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.Push) == List((WebHook("user1","repo1","http://example.com", jsonType, Some("key")))))
|
||||||
service.deleteWebHook("user1", "repo1", "http://example.com")
|
service.deleteWebHook("user1", "repo1", "http://example.com")
|
||||||
assert(service.getWebHook("user1", "repo1", "http://example.com") == None)
|
assert(service.getWebHook("user1", "repo1", "http://example.com") == None)
|
||||||
} }
|
} }
|
||||||
|
|
||||||
test("getWebHooks, getWebHooksByEvent") { withTestDB { implicit session =>
|
test("getWebHooks, getWebHooksByEvent") { withTestDB { implicit session =>
|
||||||
val user1 = generateNewUserWithDBRepository("user1","repo1")
|
val user1 = generateNewUserWithDBRepository("user1","repo1")
|
||||||
service.addWebHook("user1", "repo1", "http://example.com/1", Set(WebHook.PullRequest), Some("key"))
|
val ctype = WebHookContentType.FORM
|
||||||
service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push), Some("key"))
|
service.addWebHook("user1", "repo1", "http://example.com/1", Set(WebHook.PullRequest), ctype, Some("key"))
|
||||||
service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push), Some("key"))
|
service.addWebHook("user1", "repo1", "http://example.com/2", Set(WebHook.Push), ctype, Some("key"))
|
||||||
|
service.addWebHook("user1", "repo1", "http://example.com/3", Set(WebHook.PullRequest,WebHook.Push), ctype, Some("key"))
|
||||||
assert(service.getWebHooks("user1", "repo1") == List(
|
assert(service.getWebHooks("user1", "repo1") == List(
|
||||||
WebHook("user1","repo1","http://example.com/1", Some("key"))->Set(WebHook.PullRequest),
|
WebHook("user1","repo1","http://example.com/1", ctype, Some("key"))->Set(WebHook.PullRequest),
|
||||||
WebHook("user1","repo1","http://example.com/2", Some("key"))->Set(WebHook.Push),
|
WebHook("user1","repo1","http://example.com/2", ctype, Some("key"))->Set(WebHook.Push),
|
||||||
WebHook("user1","repo1","http://example.com/3", Some("key"))->Set(WebHook.PullRequest,WebHook.Push)))
|
WebHook("user1","repo1","http://example.com/3", ctype, Some("key"))->Set(WebHook.PullRequest,WebHook.Push)))
|
||||||
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List(
|
assert(service.getWebHooksByEvent("user1", "repo1", WebHook.PullRequest) == List(
|
||||||
WebHook("user1","repo1","http://example.com/1", Some("key")),
|
WebHook("user1","repo1","http://example.com/1", ctype, Some("key")),
|
||||||
WebHook("user1","repo1","http://example.com/3", Some("key"))))
|
WebHook("user1","repo1","http://example.com/3", ctype, Some("key"))))
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user