mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 23:27:43 +02:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3134bb0428 | ||
|
|
134b5df6a2 | ||
|
|
aab84d9275 | ||
|
|
5209c29d0f | ||
|
|
cc975f8ffa | ||
|
|
125040f90b | ||
|
|
7009aaeb24 | ||
|
|
a135c6977f | ||
|
|
55991a6f17 | ||
|
|
17e1de6174 | ||
|
|
12a58f393e | ||
|
|
ebb57a80e3 | ||
|
|
7a53bd8766 | ||
|
|
d8b46b194d | ||
|
|
5db7d863ff | ||
|
|
7d7c11aa1a | ||
|
|
95748a2f2f | ||
|
|
e77045abe3 | ||
|
|
d73ddbdbcb | ||
|
|
8893a4a456 | ||
|
|
608112ce22 | ||
|
|
2e9155fbcc | ||
|
|
f33a30c697 | ||
|
|
e208dd5966 | ||
|
|
a6cb71f9c3 | ||
|
|
91deeb969b | ||
|
|
040d812f2a | ||
|
|
772ac80764 | ||
|
|
ef67c94272 | ||
|
|
fdb4a6bdc6 | ||
|
|
57902af87c | ||
|
|
92fea3ff01 | ||
|
|
cbd16169e4 | ||
|
|
299df34bf4 | ||
|
|
48a92df719 | ||
|
|
b806439e2f | ||
|
|
1db586c0bd | ||
|
|
26e2bfbf43 | ||
|
|
28c4ac6a19 | ||
|
|
c0d9d68fca | ||
|
|
122ed1dd0f | ||
|
|
f18b83999a | ||
|
|
fc28aacb52 | ||
|
|
2872da4f94 | ||
|
|
5d3c5e7f3c | ||
|
|
a86fb480b2 | ||
|
|
7daf33c149 | ||
|
|
0d9afdc939 | ||
|
|
a9a26193cd | ||
|
|
684973ea85 | ||
|
|
0149593272 | ||
|
|
1ae3df76bd | ||
|
|
d8e0a06d93 | ||
|
|
b2d842ddd0 | ||
|
|
580374208f | ||
|
|
8ab96f09cb | ||
|
|
f5b6728358 | ||
|
|
c7d46ee18f | ||
|
|
eafdd0fb61 | ||
|
|
a14394dd88 | ||
|
|
e28b0394ec | ||
|
|
11903e9728 | ||
|
|
0e498d1a81 | ||
|
|
f073112814 | ||
|
|
9ee71e9f25 | ||
|
|
0aafc16648 | ||
|
|
58246a91b0 | ||
|
|
53ae59271a | ||
|
|
1a2e4e72bd | ||
|
|
2a83c1b9ba | ||
|
|
5b245978d4 | ||
|
|
1463cee2a4 | ||
|
|
f4b910c268 | ||
|
|
d39c371635 | ||
|
|
19b3c2a265 | ||
|
|
28efc38fc4 | ||
|
|
6a7e948e89 | ||
|
|
645d23b531 | ||
|
|
50ae5bb7cc | ||
|
|
38728910cb | ||
|
|
e2f695777d | ||
|
|
06a98d0f94 | ||
|
|
944cbf04ed | ||
|
|
84891abc04 | ||
|
|
a848bb43b6 | ||
|
|
d57a2e5eae | ||
|
|
d1adcb876d | ||
|
|
8505d8ae0e | ||
|
|
3a567cb4a7 | ||
|
|
20dbba116a | ||
|
|
f7d7b5bd7b | ||
|
|
dd15420f2c | ||
|
|
31945533c2 | ||
|
|
9288e0abe0 | ||
|
|
5641fee39a | ||
|
|
db88458a14 | ||
|
|
3ff89bc648 | ||
|
|
61f3d2d513 | ||
|
|
788f253ad0 | ||
|
|
947d93ddc7 | ||
|
|
74063885b1 | ||
|
|
554fd6d700 | ||
|
|
1fb6861565 | ||
|
|
6c5350a51b | ||
|
|
00da7e9a82 | ||
|
|
e18bed12c0 | ||
|
|
d2bb7e912f | ||
|
|
73ed69a4ad | ||
|
|
d8fe6a0a55 | ||
|
|
b278bfd159 | ||
|
|
872beb777f | ||
|
|
aebcf5d183 | ||
|
|
aab9b71901 | ||
|
|
9cbab137fc | ||
|
|
358bc23931 | ||
|
|
7396bf0675 | ||
|
|
61166c4388 | ||
|
|
4b9f2c7728 | ||
|
|
6b496bdef2 | ||
|
|
0e795f58dd | ||
|
|
e2ac8e29fe | ||
|
|
bc80adc412 | ||
|
|
2f634625ea | ||
|
|
d80774d8d0 | ||
|
|
ecf3e97518 | ||
|
|
3758d1f5ad | ||
|
|
3e53008d35 | ||
|
|
afde5c2685 | ||
|
|
224c355d44 | ||
|
|
269718bfa6 | ||
|
|
d145fdbb23 | ||
|
|
a60848b16c | ||
|
|
45db917ee7 | ||
|
|
42494ce58a | ||
|
|
93c75a3ffd | ||
|
|
b9283fb544 | ||
|
|
68d090f81a | ||
|
|
f3271846ea | ||
|
|
d39d6691e6 | ||
|
|
832b33f949 | ||
|
|
1373a93c75 | ||
|
|
b2773ff5b7 | ||
|
|
8ee017c3fa | ||
|
|
11fccf38a6 | ||
|
|
7e7e45e794 | ||
|
|
c760af7810 |
11
.github/CONTRIBUTING.md
vendored
11
.github/CONTRIBUTING.md
vendored
@@ -1,7 +1,8 @@
|
|||||||
# Guideline for Issues
|
# The guidelines for contributing
|
||||||
|
|
||||||
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues and pull requests whether there is a same request in the past.
|
||||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles. If you don't wanna waste your time to make a pull request, ask us about your idea at [gitter room](https://gitter.im/gitbucket/gitbucket) before staring your work.
|
||||||
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||||
- Write an issue in English. At least, write subject in English.
|
- You can edit the GitBucket documentation on Wiki if you have a GitHub account. When you find any mistakes or lacks in the documentation, please update it directly.
|
||||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
- Write an issue, a pull request, commit messages and comments in source code in English.
|
||||||
|
- All your contributions are handled as [Apache Software License, Version 2.0](https://github.com/gitbucket/gitbucket/blob/master/LICENSE). When you create a pull request or update the documentation, we assume you agreed this clause.
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,4 +1,4 @@
|
|||||||
### Before submitting an issue to Gitbucket I have first:
|
### Before submitting an issue to GitBucket I have first:
|
||||||
|
|
||||||
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||||
- [] searched for similar already existing issue
|
- [] searched for similar already existing issue
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
## Issue
|
## Issue
|
||||||
**Impacted version**: xxxx
|
**Impacted version**: xxxx
|
||||||
|
|
||||||
**Deployment mode**: *explain here how you use gitbucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
**Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
||||||
|
|
||||||
**Problem description**:
|
**Problem description**:
|
||||||
- *be as explicit has you can*
|
- *be as explicit has you can*
|
||||||
|
|||||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,4 +1,4 @@
|
|||||||
### Before submitting a pull-request to Gitbucket I have first:
|
### Before submitting a pull-request to GitBucket I have first:
|
||||||
|
|
||||||
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||||
- [] rebased my branch over master
|
- [] rebased my branch over master
|
||||||
|
|||||||
6
.github/SUPPORT.md
vendored
Normal file
6
.github/SUPPORT.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# The support guidelines
|
||||||
|
|
||||||
|
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||||
|
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
|
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||||
|
- Write an issue in English. Since we can't support issues written in other languages, we close them forcibly.
|
||||||
32
.travis.yml
32
.travis.yml
@@ -1,5 +1,7 @@
|
|||||||
language: scala
|
language: scala
|
||||||
sudo: true
|
sudo: true
|
||||||
|
jdk:
|
||||||
|
- oraclejdk8
|
||||||
script:
|
script:
|
||||||
- sbt test
|
- sbt test
|
||||||
before_script:
|
before_script:
|
||||||
@@ -14,33 +16,3 @@ cache:
|
|||||||
- $HOME/.coursier
|
- $HOME/.coursier
|
||||||
- $HOME/.embedmysql
|
- $HOME/.embedmysql
|
||||||
- $HOME/.embedpostgresql
|
- $HOME/.embedpostgresql
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- jdk: oraclejdk8
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- libaio1
|
|
||||||
- dist: trusty
|
|
||||||
group: edge
|
|
||||||
sudo: required
|
|
||||||
jdk: oraclejdk9
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- libaio1
|
|
||||||
- oracle-java9-installer
|
|
||||||
script:
|
|
||||||
# https://github.com/sbt/sbt/pull/2951
|
|
||||||
- git clone https://github.com/retronym/java9-rt-export
|
|
||||||
- cd java9-rt-export/
|
|
||||||
- git checkout 1019a2873d057dd7214f4135e84283695728395d
|
|
||||||
- jdk_switcher use oraclejdk8
|
|
||||||
- sbt package
|
|
||||||
- jdk_switcher use oraclejdk9
|
|
||||||
- java -version
|
|
||||||
- mkdir -p $HOME/.sbt/0.13/java9-rt-ext; java -jar target/java9-rt-export-*.jar $HOME/.sbt/0.13/java9-rt-ext/rt.jar
|
|
||||||
- jar tf $HOME/.sbt/0.13/java9-rt-ext/rt.jar | grep java/lang/Object
|
|
||||||
- cd ..
|
|
||||||
- wget https://raw.githubusercontent.com/paulp/sbt-extras/9ade5fa54914ca8aded44105bf4b9a60966f3ccd/sbt && chmod +x ./sbt
|
|
||||||
- ./sbt -Dscala.ext.dirs=$HOME/.sbt/0.13/java9-rt-ext test
|
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -57,8 +57,9 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
|
|||||||
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
|
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
|
||||||
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
|
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
|
||||||
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
|
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
|
||||||
|
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin)
|
||||||
|
|
||||||
You can find more plugins made by the community at [GitBucket community plugins](http://gitbucket-plugins.github.io/).
|
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
||||||
|
|
||||||
Support
|
Support
|
||||||
--------
|
--------
|
||||||
@@ -71,11 +72,26 @@ Support
|
|||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
-------------
|
-------------
|
||||||
### 4.14 - 1 Jul 2017
|
### 4.16.0 - 2 Sep 2017
|
||||||
|
- Support AdminLTE color skin
|
||||||
|
- Improve unexpected error handling
|
||||||
|
- Show commit status on the commits list
|
||||||
|
|
||||||
|
### 4.15.0 - 5 Aug 2017
|
||||||
|
- Bundle GitBucket organization plugins
|
||||||
|
- Notifications plugin
|
||||||
|
- Plugin hot deployment
|
||||||
|
- Update Slick to 3.2.1 from 3.2.0
|
||||||
|
- Support ed25519 keys for SSH
|
||||||
|
- Markdown preview in comment editing forms
|
||||||
|
|
||||||
|
### 4.14.1 - 4 Jul 2017
|
||||||
|
- Bug fix: Possibility of error in forking repository
|
||||||
|
|
||||||
|
### 4.14 - 1 Jul 2017
|
||||||
- Support priority in issues and pull requests
|
- Support priority in issues and pull requests
|
||||||
- Show icons when the sidebar is collapsed
|
- Show icons when the sidebar is collapsed
|
||||||
- Support gollumn events in web hook
|
- Support gollum events in web hook
|
||||||
- Support account (user / group) level web hook
|
- Support account (user / group) level web hook
|
||||||
- Add `--max_file_size` option
|
- Add `--max_file_size` option
|
||||||
- Configuration by system property or environment variable
|
- Configuration by system property or environment variable
|
||||||
|
|||||||
38
build.sbt
38
build.sbt
@@ -1,6 +1,6 @@
|
|||||||
val Organization = "io.github.gitbucket"
|
val Organization = "io.github.gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "4.14.0"
|
val GitBucketVersion = "4.16.0"
|
||||||
val ScalatraVersion = "2.5.0"
|
val ScalatraVersion = "2.5.0"
|
||||||
val JettyVersion = "9.3.19.v20170502"
|
val JettyVersion = "9.3.19.v20170502"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ sourcesInBase := false
|
|||||||
organization := Organization
|
organization := Organization
|
||||||
name := Name
|
name := Name
|
||||||
version := GitBucketVersion
|
version := GitBucketVersion
|
||||||
scalaVersion := "2.12.2"
|
scalaVersion := "2.12.3"
|
||||||
|
|
||||||
// dependency settings
|
// dependency settings
|
||||||
resolvers ++= Seq(
|
resolvers ++= Seq(
|
||||||
@@ -21,25 +21,25 @@ resolvers ++= Seq(
|
|||||||
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||||
)
|
)
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.7.0.201704051617-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.8.0.201706111038-r",
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.7.0.201704051617-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.8.0.201706111038-r",
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
"org.json4s" %% "json4s-jackson" % "3.5.1",
|
"org.json4s" %% "json4s-jackson" % "3.5.1",
|
||||||
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
|
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
|
||||||
"commons-io" % "commons-io" % "2.5",
|
"commons-io" % "commons-io" % "2.5",
|
||||||
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
"io.github.gitbucket" % "solidbase" % "1.0.2",
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.12",
|
"io.github.gitbucket" % "markedj" % "1.0.14",
|
||||||
"org.apache.commons" % "commons-compress" % "1.13",
|
"org.apache.commons" % "commons-compress" % "1.13",
|
||||||
"org.apache.commons" % "commons-email" % "1.4",
|
"org.apache.commons" % "commons-email" % "1.4",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
|
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
|
||||||
"org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"),
|
"org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"),
|
||||||
"org.apache.tika" % "tika-core" % "1.14",
|
"org.apache.tika" % "tika-core" % "1.14",
|
||||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.8",
|
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
||||||
"joda-time" % "joda-time" % "2.9.9",
|
"joda-time" % "joda-time" % "2.9.9",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.195",
|
"com.h2database" % "h2" % "1.4.195",
|
||||||
"mysql" % "mysql-connector-java" % "6.0.6",
|
"org.mariadb.jdbc" % "mariadb-java-client" % "2.0.3",
|
||||||
"org.postgresql" % "postgresql" % "42.0.0",
|
"org.postgresql" % "postgresql" % "42.0.0",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||||
"com.zaxxer" % "HikariCP" % "2.6.1",
|
"com.zaxxer" % "HikariCP" % "2.6.1",
|
||||||
@@ -50,13 +50,15 @@ libraryDependencies ++= Seq(
|
|||||||
"org.cache2k" % "cache2k-all" % "1.0.0.CR1",
|
"org.cache2k" % "cache2k-all" % "1.0.0.CR1",
|
||||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"),
|
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"),
|
||||||
"net.coobird" % "thumbnailator" % "0.4.8",
|
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||||
|
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
"junit" % "junit" % "4.12" % "test",
|
"junit" % "junit" % "4.12" % "test",
|
||||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||||
"org.mockito" % "mockito-core" % "2.7.22" % "test",
|
"org.mockito" % "mockito-core" % "2.7.22" % "test",
|
||||||
"com.wix" % "wix-embedded-mysql" % "2.1.4" % "test",
|
"com.wix" % "wix-embedded-mysql" % "2.1.4" % "test",
|
||||||
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.0" % "test"
|
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.0" % "test",
|
||||||
|
"net.i2p.crypto" % "eddsa" % "0.1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Compiler settings
|
// Compiler settings
|
||||||
@@ -143,12 +145,28 @@ executableKey := {
|
|||||||
IO copyFile (classDir / name, temp / name)
|
IO copyFile (classDir / name, temp / name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// include plugins
|
||||||
|
val pluginsDir = temp / "WEB-INF" / "classes" / "plugins"
|
||||||
|
IO createDirectory (pluginsDir)
|
||||||
|
IO copyFile(Keys.baseDirectory.value / "plugins.json", pluginsDir / "plugins.json")
|
||||||
|
|
||||||
|
val json = IO read(Keys.baseDirectory.value / "plugins.json")
|
||||||
|
PluginsJson.parse(json).foreach { case (plugin, version) =>
|
||||||
|
val url = if(plugin == "gitbucket-pages-plugin"){
|
||||||
|
s"https://github.com/gitbucket/${plugin}/releases/download/v${version}/${plugin}_${scalaBinaryVersion.value}-${version}.jar"
|
||||||
|
} else {
|
||||||
|
s"https://github.com/gitbucket/${plugin}/releases/download/${version}/${plugin}_${scalaBinaryVersion.value}-${version}.jar"
|
||||||
|
}
|
||||||
|
log info s"Download: ${url}"
|
||||||
|
IO download(new java.net.URL(url), pluginsDir / s"${plugin}_${scalaBinaryVersion.value}-${version}.jar")
|
||||||
|
}
|
||||||
|
|
||||||
// zip it up
|
// zip it up
|
||||||
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||||
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
|
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
|
||||||
val manifest = new JarManifest
|
val manifest = new JarManifest
|
||||||
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||||
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||||
val outputFile = workDir / warName
|
val outputFile = workDir / warName
|
||||||
IO jar (contentMappings, outputFile, manifest)
|
IO jar (contentMappings, outputFile, manifest)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
JRebel integration (optional)
|
JRebel integration (optional)
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
[JRebel](http://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
[JRebel](https://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
||||||
JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change:
|
JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -22,12 +22,12 @@ Sign up for a [usage plan](https://my.jrebel.com/). You will need to create an a
|
|||||||
|
|
||||||
## 2. Download JRebel
|
## 2. Download JRebel
|
||||||
|
|
||||||
Download the most recent ["nosetup" JRebel zip](http://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
Download the most recent ["nosetup" JRebel zip](https://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
||||||
Next, unzip the downloaded file.
|
Next, unzip the downloaded file.
|
||||||
|
|
||||||
## 3. Activate
|
## 3. Activate
|
||||||
|
|
||||||
Follow the [instructions on the JRebel website](http://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
|
Follow the [instructions on the JRebel website](https://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
|
||||||
|
|
||||||
You can use the default settings for all the configurations.
|
You can use the default settings for all the configurations.
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
Notification Email
|
|
||||||
========
|
|
||||||
|
|
||||||
GitBucket can send email notification to users if this feature is enabled by an administrator.
|
|
||||||
|
|
||||||
The timing of the notification are as follows:
|
|
||||||
|
|
||||||
##### at the issue registration (new issue, new pull request)
|
|
||||||
When a record is saved into the ```ISSUE``` table, GitBucket does the notification.
|
|
||||||
|
|
||||||
##### at the comment registration
|
|
||||||
Among the records in the ```ISSUE_COMMENT``` table, them to be counted as a comment (i.e. the record ```ACTION``` column value is "comment" or "close_comment" or "reopen_comment") are saved, GitBucket does the notification.
|
|
||||||
|
|
||||||
##### at the status update (close, reopen, merge)
|
|
||||||
When the ```CLOSED``` column value is updated, GitBucket does the notification.
|
|
||||||
|
|
||||||
Notified users are as follows:
|
|
||||||
|
|
||||||
* individual repository's owner
|
|
||||||
* group members of group repository
|
|
||||||
* collaborators
|
|
||||||
* participants
|
|
||||||
|
|
||||||
However, the person performing the operation is excluded from the notification.
|
|
||||||
@@ -6,7 +6,6 @@ Developer's Guide
|
|||||||
* [Authentication in Controller](authenticator.md)
|
* [Authentication in Controller](authenticator.md)
|
||||||
* [About Action in Issue Comment](comment_action.md)
|
* [About Action in Issue Comment](comment_action.md)
|
||||||
* [Activity Types](activity.md)
|
* [Activity Types](activity.md)
|
||||||
* [Notification Email](notification.md)
|
|
||||||
* [Automatic Schema Updating](auto_update.md)
|
* [Automatic Schema Updating](auto_update.md)
|
||||||
* [Release Operation](release.md)
|
* [Release Operation](release.md)
|
||||||
* [JRebel integration (optional)](jrebel.md)
|
* [JRebel integration (optional)](jrebel.md)
|
||||||
|
|||||||
54
plugins.json
Normal file
54
plugins.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "notifications",
|
||||||
|
"name": "Notifications Plugin",
|
||||||
|
"description": "Provides notifications feature on GitBucket.",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.1.0",
|
||||||
|
"range": ">=4.16.0",
|
||||||
|
"file": "gitbucket-notifications-plugin_2.12-1.1.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "emoji",
|
||||||
|
"name": "Emoji Plugin",
|
||||||
|
"description": "Provides Emoji support for GitBucket.",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "4.4.0",
|
||||||
|
"range": ">=4.10.0",
|
||||||
|
"file": "gitbucket-emoji-plugin_2.12-4.4.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gist",
|
||||||
|
"name": "Gist Plugin",
|
||||||
|
"description": "Provides Gist feature on GitBucket.",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "4.10.0",
|
||||||
|
"range": ">=4.15.0",
|
||||||
|
"file": "gitbucket-gist-plugin_2.12-4.10.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pages",
|
||||||
|
"name": "Pages Plugin",
|
||||||
|
"description": "Project pages for gitbucket",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"version": "1.5.0",
|
||||||
|
"range": ">=4.15.0",
|
||||||
|
"file": "gitbucket-pages-plugin_2.12-1.5.0.jar"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest
|
||||||
import scala.annotation._
|
import scala.annotation._
|
||||||
import sbt._
|
import sbt._
|
||||||
import sbt.Using._
|
|
||||||
|
|
||||||
object Checksums {
|
object Checksums {
|
||||||
private val bufferSize = 2048
|
private val bufferSize = 2048
|
||||||
|
|||||||
17
project/PluginsJson.scala
Normal file
17
project/PluginsJson.scala
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import com.eclipsesource.json.Json
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
object PluginsJson {
|
||||||
|
|
||||||
|
def parse(json: String): Seq[(String, String)] = {
|
||||||
|
val value = Json.parse(json)
|
||||||
|
value.asArray.values.asScala.map { plugin =>
|
||||||
|
val obj = plugin.asObject.get("versions").asArray.asScala.head.asObject
|
||||||
|
val pluginName = obj.get("file").asString.split("_2.12-").head
|
||||||
|
val version = obj.get("version").asString
|
||||||
|
(pluginName, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
1
project/build.sbt
Normal file
1
project/build.sbt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.4"
|
||||||
@@ -40,7 +40,7 @@ public class JettyLauncher {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "--max_file_size":
|
case "--max_file_size":
|
||||||
System.setProperty("gitbucket.maxFileSize", dim[2]);
|
System.setProperty("gitbucket.maxFileSize", dim[1]);
|
||||||
break;
|
break;
|
||||||
case "--gitbucket.home":
|
case "--gitbucket.home":
|
||||||
System.setProperty("gitbucket.home", dim[1]);
|
System.setProperty("gitbucket.home", dim[1]);
|
||||||
@@ -48,6 +48,12 @@ public class JettyLauncher {
|
|||||||
case "--temp_dir":
|
case "--temp_dir":
|
||||||
tmpDirPath = dim[1];
|
tmpDirPath = dim[1];
|
||||||
break;
|
break;
|
||||||
|
case "--plugin_dir":
|
||||||
|
System.setProperty("gitbucket.pluginDir", dim[1]);
|
||||||
|
break;
|
||||||
|
case "--validate_password":
|
||||||
|
System.setProperty("gitbucket.validate.password", dim[1]);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,15 +25,12 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
|
|||||||
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||||
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
|
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
|
||||||
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
|
||||||
context.addFilter("ghCompatRepositoryAccessFilter", new GHCompatRepositoryAccessFilter)
|
|
||||||
context.getFilterRegistration("ghCompatRepositoryAccessFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
|
||||||
|
|
||||||
// Register controllers
|
// Register controllers
|
||||||
context.mount(new AnonymousAccessController, "/*")
|
context.mount(new PreProcessController, "/*")
|
||||||
|
|
||||||
PluginRegistry().getControllers.foreach { case (controller, path) =>
|
context.addFilter("pluginControllerFilter", new PluginControllerFilter)
|
||||||
context.mount(controller, path)
|
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
}
|
|
||||||
|
|
||||||
context.mount(new IndexController, "/")
|
context.mount(new IndexController, "/")
|
||||||
context.mount(new ApiController, "/api/v3")
|
context.mount(new ApiController, "/api/v3")
|
||||||
|
|||||||
@@ -38,5 +38,8 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
|||||||
new Version("4.14.0",
|
new Version("4.14.0",
|
||||||
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
|
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
|
||||||
new SqlMigration("update/gitbucket-core_4.14.sql")
|
new SqlMigration("update/gitbucket-core_4.14.sql")
|
||||||
)
|
),
|
||||||
|
new Version("4.14.1"),
|
||||||
|
new Version("4.15.0"),
|
||||||
|
new Version("4.16.0")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -232,6 +232,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/captures/(.*)".r) {
|
||||||
|
multiParams("captures").head
|
||||||
|
}
|
||||||
|
|
||||||
get("/:userName/_delete")(oneselfOnly {
|
get("/:userName/_delete")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
|
|
||||||
@@ -594,22 +598,23 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
// Insert default labels
|
// Insert default labels
|
||||||
insertDefaultLabels(accountName, repository.name)
|
insertDefaultLabels(accountName, repository.name)
|
||||||
|
// Insert default priorities
|
||||||
|
insertDefaultPriorities(accountName, repository.name)
|
||||||
|
|
||||||
// clone repository actually
|
// clone repository actually
|
||||||
JGitUtil.cloneRepository(
|
JGitUtil.cloneRepository(
|
||||||
getRepositoryDir(repository.owner, repository.name),
|
getRepositoryDir(repository.owner, repository.name),
|
||||||
getRepositoryDir(accountName, repository.name))
|
FileUtil.deleteIfExists(getRepositoryDir(accountName, repository.name)))
|
||||||
|
|
||||||
// Create Wiki repository
|
// Create Wiki repository
|
||||||
JGitUtil.cloneRepository(
|
JGitUtil.cloneRepository(getWikiRepositoryDir(repository.owner, repository.name),
|
||||||
getWikiRepositoryDir(repository.owner, repository.name),
|
FileUtil.deleteIfExists(getWikiRepositoryDir(accountName, repository.name)))
|
||||||
getWikiRepositoryDir(accountName, repository.name))
|
|
||||||
|
|
||||||
// Copy files
|
// Copy LFS files
|
||||||
FileUtils.copyDirectory(
|
val lfsDir = getLfsDir(repository.owner, repository.name)
|
||||||
Directory.getRepositoryFilesDir(repository.owner, repository.name),
|
if(lfsDir.exists){
|
||||||
Directory.getRepositoryFilesDir(accountName, repository.name)
|
FileUtils.copyDirectory(lfsDir, FileUtil.deleteIfExists(getLfsDir(accountName, repository.name)))
|
||||||
)
|
}
|
||||||
|
|
||||||
// Record activity
|
// Record activity
|
||||||
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package gitbucket.core.controller
|
|
||||||
|
|
||||||
class AnonymousAccessController extends AnonymousAccessControllerBase
|
|
||||||
|
|
||||||
trait AnonymousAccessControllerBase extends ControllerBase {
|
|
||||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
|
||||||
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
|
||||||
!context.currentPath.startsWith("/register")) {
|
|
||||||
Unauthorized()
|
|
||||||
} else {
|
|
||||||
pass()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,14 +5,15 @@ import gitbucket.core.model._
|
|||||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
import gitbucket.core.service.PullRequestService._
|
import gitbucket.core.service.PullRequestService._
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.JGitUtil._
|
|
||||||
import gitbucket.core.util._
|
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.view.helpers.{renderMarkup, isRenderable}
|
import gitbucket.core.util.JGitUtil._
|
||||||
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
|
import gitbucket.core.util._
|
||||||
|
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.scalatra.{NoContent, UnprocessableEntity, Created}
|
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
class ApiController extends ApiControllerBase
|
class ApiController extends ApiControllerBase
|
||||||
@@ -124,10 +125,10 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||||
*/
|
*/
|
||||||
get ("/api/v3/repos/:owner/:repo/branches/:branch")(referrersOnly { repository =>
|
get ("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository =>
|
||||||
//import gitbucket.core.api._
|
//import gitbucket.core.api._
|
||||||
(for{
|
(for{
|
||||||
branch <- params.get("branch") if repository.branchList.contains(branch)
|
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||||
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||||
} yield {
|
} yield {
|
||||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||||
@@ -286,10 +287,10 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||||
*/
|
*/
|
||||||
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
patch("/api/v3/repos/:owner/:repo/branches/*")(ownerOnly { repository =>
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
(for{
|
(for{
|
||||||
branch <- params.get("branch") if repository.branchList.contains(branch)
|
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||||
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||||
} yield {
|
} yield {
|
||||||
@@ -381,7 +382,7 @@ trait ApiControllerBase extends ControllerBase {
|
|||||||
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
||||||
(for{
|
(for{
|
||||||
issueId <- params("id").toIntOpt
|
issueId <- params("id").toIntOpt
|
||||||
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
|
comments = getCommentsForApi(repository.owner, repository.name, issueId)
|
||||||
} yield {
|
} yield {
|
||||||
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import org.eclipse.jgit.lib.ObjectId
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import org.eclipse.jgit.treewalk._
|
import org.eclipse.jgit.treewalk._
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides generic features for controller implementations.
|
* Provides generic features for controller implementations.
|
||||||
@@ -34,6 +35,8 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
|
||||||
with SystemSettingsService {
|
with SystemSettingsService {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(getClass)
|
||||||
|
|
||||||
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||||
|
|
||||||
before("/api/v3/*") {
|
before("/api/v3/*") {
|
||||||
@@ -147,6 +150,20 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error{
|
||||||
|
case e => {
|
||||||
|
logger.error(s"Catch unhandled error in request: ${request}", e)
|
||||||
|
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||||
|
org.scalatra.InternalServerError()
|
||||||
|
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||||
|
contentType = formats("json")
|
||||||
|
org.scalatra.InternalServerError(ApiError("Internal Server Error"))
|
||||||
|
} else {
|
||||||
|
org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
||||||
absolutize: Boolean = true, withSessionId: Boolean = true)
|
absolutize: Boolean = true, withSessionId: Boolean = true)
|
||||||
@@ -163,7 +180,7 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] =
|
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] =
|
||||||
valueType.validate(name, trim(value), params, messages)
|
valueType.validate(name, trim(value), params, messages)
|
||||||
|
|
||||||
private def trim(value: String): String = if(value == null) null else value.replaceAll("\r\n", "").trim
|
private def trim(value: String): String = if(value == null) null else value.replace("\r\n", "").trim
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
getComment(owner, name, params("id")).map { comment =>
|
||||||
if(isEditableContent(owner, name, comment.commentedUserName)){
|
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||||
updateComment(comment.commentId, form.content)
|
updateComment(comment.issueId, comment.commentId, form.content)
|
||||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
@@ -204,7 +204,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
getComment(owner, name, params("id")).map { comment =>
|
||||||
if(isEditableContent(owner, name, comment.commentedUserName)){
|
if(isEditableContent(owner, name, comment.commentedUserName)){
|
||||||
Ok(deleteComment(comment.commentId))
|
Ok(deleteComment(comment.issueId, comment.commentId))
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package gitbucket.core.controller
|
||||||
|
|
||||||
|
import org.scalatra.MovedPermanently
|
||||||
|
|
||||||
|
class PreProcessController extends PreProcessControllerBase
|
||||||
|
|
||||||
|
trait PreProcessControllerBase extends ControllerBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides GitHub compatible URLs for Git client.
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>git clone http://localhost:8080/owner/repo</li>
|
||||||
|
* <li>git clone http://localhost:8080/owner/repo.git</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @see https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols
|
||||||
|
*/
|
||||||
|
get("/*/*/info/refs") {
|
||||||
|
val query = Option(request.getQueryString).map("?" + _).getOrElse("")
|
||||||
|
halt(MovedPermanently(baseUrl + "/git" + request.getRequestURI + query))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter requests from anonymous users.
|
||||||
|
*
|
||||||
|
* If anonymous access is allowed, pass all requests.
|
||||||
|
* But if it's not allowed, demands authentication except some paths.
|
||||||
|
*/
|
||||||
|
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||||
|
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||||
|
!context.currentPath.startsWith("/register")) {
|
||||||
|
Unauthorized()
|
||||||
|
} else {
|
||||||
|
pass()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -251,7 +251,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||||
// mark issue as merged and close.
|
// mark issue as merged and close.
|
||||||
val loginAccount = context.loginAccount.get
|
val loginAccount = context.loginAccount.get
|
||||||
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
|
val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
|
||||||
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
|
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
|
||||||
updateClosed(owner, name, issueId, true)
|
updateClosed(owner, name, issueId, true)
|
||||||
|
|
||||||
@@ -282,7 +282,10 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
|
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||||
|
|
||||||
// call hooks
|
// call hooks
|
||||||
PluginRegistry().getPullRequestHooks.foreach(_.merged(issue, repository))
|
PluginRegistry().getPullRequestHooks.foreach{ h =>
|
||||||
|
h.addedComment(commentId, form.message, issue, repository)
|
||||||
|
h.merged(issue, repository)
|
||||||
|
}
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/settings/branches")(ownerOnly { repository =>
|
get("/:owner/:repository/settings/branches")(ownerOnly { repository =>
|
||||||
val protecteions = getProtectedBranchList(repository.owner, repository.name)
|
val protecteions = getProtectedBranchList(repository.owner, repository.name)
|
||||||
html.branches(repository, protecteions, flash.get("info"))
|
html.branches(repository, protecteions, flash.get("info"))
|
||||||
});
|
})
|
||||||
|
|
||||||
/** Update default branch */
|
/** Update default branch */
|
||||||
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
|
||||||
@@ -355,7 +355,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
FileUtils.moveDirectory(dir, getAttachedDir(form.newOwner, repository.name))
|
FileUtils.moveDirectory(dir, getAttachedDir(form.newOwner, repository.name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Delere parent directory
|
// Delete parent directory
|
||||||
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
|
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
|
||||||
|
|
||||||
// Call hooks
|
// Call hooks
|
||||||
@@ -393,7 +393,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
|
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
git.gc();
|
git.gc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flash += "info" -> "Garbage collection has been executed."
|
flash += "info" -> "Garbage collection has been executed."
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import gitbucket.core.util.StringUtil._
|
|||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.model.{Account, WebHook}
|
import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook}
|
||||||
import gitbucket.core.service.WebHookService._
|
import gitbucket.core.service.WebHookService._
|
||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
import gitbucket.core.view.helpers
|
import gitbucket.core.view.helpers
|
||||||
@@ -174,13 +174,24 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val (branchName, path) = repository.splitPath(multiParams("splat").head)
|
val (branchName, path) = repository.splitPath(multiParams("splat").head)
|
||||||
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
|
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
|
||||||
|
|
||||||
|
def getStatuses(sha: String): List[CommitStatus] = {
|
||||||
|
getCommitStatues(repository.owner, repository.name, sha)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getSummary(statuses: List[CommitStatus]): (CommitState, String) = {
|
||||||
|
val stateMap = statuses.groupBy(_.state)
|
||||||
|
val state = CommitState.combine(stateMap.keySet)
|
||||||
|
val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")
|
||||||
|
state -> summary
|
||||||
|
}
|
||||||
|
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
||||||
case Right((logs, hasNext)) =>
|
case Right((logs, hasNext)) =>
|
||||||
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||||
logs.splitWith{ (commit1, commit2) =>
|
logs.splitWith{ (commit1, commit2) =>
|
||||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||||
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), getStatuses, getSummary)
|
||||||
case Left(_) => NotFound()
|
case Left(_) => NotFound()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,7 +224,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
|
||||||
val files = form.uploadFiles.split("\n").map { line =>
|
val files = form.uploadFiles.split("\n").map { line =>
|
||||||
val i = line.indexOf(":")
|
val i = line.indexOf(':')
|
||||||
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
|
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +233,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
branch = form.branch,
|
branch = form.branch,
|
||||||
path = form.path,
|
path = form.path,
|
||||||
files = files,
|
files = files,
|
||||||
message = form.message.getOrElse(s"Add files via upload")
|
message = form.message.getOrElse("Add files via upload")
|
||||||
)
|
)
|
||||||
|
|
||||||
if(form.path.length == 0){
|
if(form.path.length == 0){
|
||||||
@@ -630,8 +641,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
case class UploadFiles(branch: String, path: String, fileIds : Map[String,String], message: String) {
|
case class UploadFiles(branch: String, path: String, fileIds: Map[String,String], message: String) {
|
||||||
lazy val isValid: Boolean = fileIds.size > 0
|
lazy val isValid: Boolean = fileIds.nonEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
case class CommitFile(id: String, name: String)
|
case class CommitFile(id: String, name: String)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import gitbucket.core.admin.html
|
|||||||
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||||
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||||
import gitbucket.core.ssh.SshServer
|
import gitbucket.core.ssh.SshServer
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository}
|
||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
@@ -15,6 +15,10 @@ import gitbucket.core.util.StringUtil._
|
|||||||
import io.github.gitbucket.scalatra.forms._
|
import io.github.gitbucket.scalatra.forms._
|
||||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
import com.github.zafarkhaja.semver.{Version => Semver}
|
||||||
|
import gitbucket.core.GitBucketCoreModule
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
with AccountService with RepositoryService with AdminAuthenticator
|
with AccountService with RepositoryService with AdminAuthenticator
|
||||||
@@ -59,7 +63,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
||||||
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
|
||||||
"keystore" -> trim(label("Keystore", optional(text())))
|
"keystore" -> trim(label("Keystore", optional(text())))
|
||||||
)(Ldap.apply))
|
)(Ldap.apply)),
|
||||||
|
"skinName" -> trim(label("AdminLTE skin name", text(required)))
|
||||||
)(SystemSettings.apply).verifying { settings =>
|
)(SystemSettings.apply).verifying { settings =>
|
||||||
Vector(
|
Vector(
|
||||||
if(settings.ssh && settings.baseUrl.isEmpty){
|
if(settings.ssh && settings.baseUrl.isEmpty){
|
||||||
@@ -170,8 +175,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
||||||
try {
|
try {
|
||||||
new Mailer(form.smtp).send(form.testAddress,
|
new Mailer(form.smtp).send(form.testAddress,
|
||||||
"Test message from GitBucket", "This is a test message from GitBucket.",
|
"Test message from GitBucket", context.loginAccount.get,
|
||||||
context.loginAccount.get)
|
"This is a test message from GitBucket.", None
|
||||||
|
)
|
||||||
|
|
||||||
"Test mail has been sent to: " + form.testAddress
|
"Test mail has been sent to: " + form.testAddress
|
||||||
|
|
||||||
@@ -181,7 +187,71 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/plugins")(adminOnly {
|
get("/admin/plugins")(adminOnly {
|
||||||
html.plugins(PluginRegistry().getPlugins())
|
// Installed plugins
|
||||||
|
val enabledPlugins = PluginRegistry().getPlugins()
|
||||||
|
|
||||||
|
val gitbucketVersion = Semver.valueOf(GitBucketCoreModule.getVersions.asScala.last.getVersion)
|
||||||
|
|
||||||
|
// Plugins in the local repository
|
||||||
|
val repositoryPlugins = PluginRepository.getPlugins()
|
||||||
|
.filterNot { meta =>
|
||||||
|
enabledPlugins.exists { plugin => plugin.pluginId == meta.id &&
|
||||||
|
Semver.valueOf(plugin.pluginVersion).greaterThanOrEqualTo(Semver.valueOf(meta.latestVersion.version))
|
||||||
|
}
|
||||||
|
}.map { meta =>
|
||||||
|
(meta, meta.versions.reverse.find { version => gitbucketVersion.satisfies(version.range) })
|
||||||
|
}.collect { case (meta, Some(version)) =>
|
||||||
|
new PluginInfoBase(
|
||||||
|
pluginId = meta.id,
|
||||||
|
pluginName = meta.name,
|
||||||
|
pluginVersion = version.version,
|
||||||
|
description = meta.description
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge
|
||||||
|
val plugins = enabledPlugins.map((_, true)) ++ repositoryPlugins.map((_, false))
|
||||||
|
|
||||||
|
html.plugins(plugins, flash.get("info"))
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/plugins/_reload")(adminOnly {
|
||||||
|
PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn)
|
||||||
|
flash += "info" -> "All plugins were reloaded."
|
||||||
|
redirect("/admin/plugins")
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/plugins/:pluginId/:version/_uninstall")(adminOnly {
|
||||||
|
val pluginId = params("pluginId")
|
||||||
|
val version = params("version")
|
||||||
|
PluginRegistry().getPlugins()
|
||||||
|
.collect { case plugin if (plugin.pluginId == pluginId && plugin.pluginVersion == version) => plugin }
|
||||||
|
.foreach { _ =>
|
||||||
|
PluginRegistry.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn)
|
||||||
|
flash += "info" -> s"${pluginId} was uninstalled."
|
||||||
|
}
|
||||||
|
redirect("/admin/plugins")
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/admin/plugins/:pluginId/:version/_install")(adminOnly {
|
||||||
|
val pluginId = params("pluginId")
|
||||||
|
val version = params("version")
|
||||||
|
/// TODO!!!!
|
||||||
|
PluginRepository.getPlugins()
|
||||||
|
.collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version) )}
|
||||||
|
.foreach { case (meta, version) =>
|
||||||
|
version.foreach { version =>
|
||||||
|
// TODO Install version!
|
||||||
|
PluginRegistry.install(
|
||||||
|
new java.io.File(PluginHome, s".repository/${version.file}"),
|
||||||
|
request.getServletContext,
|
||||||
|
loadSystemSettings(),
|
||||||
|
request2Session(request).conn
|
||||||
|
)
|
||||||
|
flash += "info" -> s"${pluginId} was installed."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
redirect("/admin/plugins")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,16 @@ abstract class Plugin {
|
|||||||
*/
|
*/
|
||||||
def pullRequestHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PullRequestHook] = Nil
|
def pullRequestHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PullRequestHook] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository headers.
|
||||||
|
*/
|
||||||
|
val repositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = Nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override to add repository headers.
|
||||||
|
*/
|
||||||
|
def repositoryHeaders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Html]] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override to add global menus.
|
* Override to add global menus.
|
||||||
*/
|
*/
|
||||||
@@ -266,6 +276,9 @@ abstract class Plugin {
|
|||||||
(pullRequestHooks ++ pullRequestHooks(registry, context, settings)).foreach { pullRequestHook =>
|
(pullRequestHooks ++ pullRequestHooks(registry, context, settings)).foreach { pullRequestHook =>
|
||||||
registry.addPullRequestHook(pullRequestHook)
|
registry.addPullRequestHook(pullRequestHook)
|
||||||
}
|
}
|
||||||
|
(repositoryHeaders ++ repositoryHeaders(registry, context, settings)).foreach { repositoryHeader =>
|
||||||
|
registry.addRepositoryHeader(repositoryHeader)
|
||||||
|
}
|
||||||
(globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu =>
|
(globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu =>
|
||||||
registry.addGlobalMenu(globalMenu)
|
registry.addGlobalMenu(globalMenu)
|
||||||
}
|
}
|
||||||
@@ -287,8 +300,8 @@ abstract class Plugin {
|
|||||||
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
|
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
|
||||||
registry.addDashboardTab(dashboardTab)
|
registry.addDashboardTab(dashboardTab)
|
||||||
}
|
}
|
||||||
(issueSidebars ++ issueSidebars(registry, context, settings)).foreach { issueSidebar =>
|
(issueSidebars ++ issueSidebars(registry, context, settings)).foreach { issueSidebarComponent =>
|
||||||
registry.addIssueSidebar(issueSidebar)
|
registry.addIssueSidebar(issueSidebarComponent)
|
||||||
}
|
}
|
||||||
(assetsMappings ++ assetsMappings(registry, context, settings)).foreach { assetMapping =>
|
(assetsMappings ++ assetsMappings(registry, context, settings)).foreach { assetMapping =>
|
||||||
registry.addAssetsMapping((assetMapping._1, assetMapping._2, getClass.getClassLoader))
|
registry.addAssetsMapping((assetMapping._1, assetMapping._2, getClass.getClassLoader))
|
||||||
@@ -302,11 +315,17 @@ abstract class Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is invoked in shutdown of plugin system.
|
* This method is invoked when the plugin system is shutting down.
|
||||||
* If the plugin has any resources, release them in this method.
|
* If the plugin has any resources, release them in this method.
|
||||||
*/
|
*/
|
||||||
def shutdown(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {}
|
def shutdown(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * This method is invoked when this plugin is uninstalled.
|
||||||
|
// * Cleanup database or any other resources in this method if necessary.
|
||||||
|
// */
|
||||||
|
// def uninstall(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to get a resource from classpath.
|
* Helper method to get a resource from classpath.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,13 +2,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 java.nio.file.{Files, Paths, StandardWatchEventKinds}
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
|
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
import gitbucket.core.model.{Account, Issue}
|
import gitbucket.core.model.{Account, Issue}
|
||||||
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
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.DatabaseConfig
|
import gitbucket.core.util.DatabaseConfig
|
||||||
@@ -16,55 +21,53 @@ import gitbucket.core.util.Directory._
|
|||||||
import io.github.gitbucket.solidbase.Solidbase
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
import io.github.gitbucket.solidbase.model.Module
|
import io.github.gitbucket.solidbase.model.Module
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.JavaConverters._
|
||||||
import scala.collection.mutable.ListBuffer
|
|
||||||
|
|
||||||
class PluginRegistry {
|
class PluginRegistry {
|
||||||
|
|
||||||
private val plugins = new ListBuffer[PluginInfo]
|
private val plugins = new ConcurrentLinkedQueue[PluginInfo]
|
||||||
private val javaScripts = new ListBuffer[(String, String)]
|
private val javaScripts = new ConcurrentLinkedQueue[(String, String)]
|
||||||
private val controllers = new ListBuffer[(ControllerBase, String)]
|
private val controllers = new ConcurrentLinkedQueue[(ControllerBase, String)]
|
||||||
private val images = mutable.Map[String, String]()
|
private val images = new ConcurrentHashMap[String, String]
|
||||||
private val renderers = mutable.Map[String, Renderer]()
|
private val renderers = new ConcurrentHashMap[String, Renderer]
|
||||||
renderers ++= Seq(
|
renderers.put("md", MarkdownRenderer)
|
||||||
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer
|
renderers.put("markdown", MarkdownRenderer)
|
||||||
)
|
private val repositoryRoutings = new ConcurrentLinkedQueue[GitRepositoryRouting]
|
||||||
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting]
|
private val accountHooks = new ConcurrentLinkedQueue[AccountHook]
|
||||||
private val accountHooks = new ListBuffer[AccountHook]
|
private val receiveHooks = new ConcurrentLinkedQueue[ReceiveHook]
|
||||||
private val receiveHooks = new ListBuffer[ReceiveHook]
|
receiveHooks.add(new ProtectedBranchReceiveHook())
|
||||||
receiveHooks += new ProtectedBranchReceiveHook()
|
|
||||||
|
|
||||||
private val repositoryHooks = new ListBuffer[RepositoryHook]
|
private val repositoryHooks = new ConcurrentLinkedQueue[RepositoryHook]
|
||||||
private val issueHooks = new ListBuffer[IssueHook]
|
private val issueHooks = new ConcurrentLinkedQueue[IssueHook]
|
||||||
issueHooks += new gitbucket.core.util.Notifier.IssueHook()
|
|
||||||
|
|
||||||
private val pullRequestHooks = new ListBuffer[PullRequestHook]
|
private val pullRequestHooks = new ConcurrentLinkedQueue[PullRequestHook]
|
||||||
pullRequestHooks += new gitbucket.core.util.Notifier.PullRequestHook()
|
|
||||||
|
|
||||||
private val globalMenus = new ListBuffer[(Context) => Option[Link]]
|
private val repositoryHeaders = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Html]]
|
||||||
private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
|
private val globalMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
|
||||||
private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
|
private val repositoryMenus = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Link]]
|
||||||
private val profileTabs = new ListBuffer[(Account, Context) => Option[Link]]
|
private val repositorySettingTabs = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Link]]
|
||||||
private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]]
|
private val profileTabs = new ConcurrentLinkedQueue[(Account, Context) => Option[Link]]
|
||||||
private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]]
|
private val systemSettingMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
|
||||||
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]]
|
private val accountSettingMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
|
||||||
private val issueSidebars = new ListBuffer[(Issue, RepositoryInfo, Context) => Option[Html]]
|
private val dashboardTabs = new ConcurrentLinkedQueue[(Context) => Option[Link]]
|
||||||
private val assetsMappings = new ListBuffer[(String, String, ClassLoader)]
|
private val issueSidebars = new ConcurrentLinkedQueue[(Issue, RepositoryInfo, Context) => Option[Html]]
|
||||||
private val textDecorators = new ListBuffer[TextDecorator]
|
private val assetsMappings = new ConcurrentLinkedQueue[(String, String, ClassLoader)]
|
||||||
|
private val textDecorators = new ConcurrentLinkedQueue[TextDecorator]
|
||||||
|
|
||||||
private val suggestionProviders = new ListBuffer[SuggestionProvider]
|
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
||||||
suggestionProviders += new UserNameSuggestionProvider()
|
suggestionProviders.add(new UserNameSuggestionProvider())
|
||||||
|
|
||||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo
|
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
||||||
|
|
||||||
def getPlugins(): List[PluginInfo] = plugins.toList
|
def getPlugins(): List[PluginInfo] = plugins.asScala.toList
|
||||||
|
|
||||||
def addImage(id: String, bytes: Array[Byte]): Unit = {
|
def addImage(id: String, bytes: Array[Byte]): Unit = {
|
||||||
val encoded = Base64.getEncoder.encodeToString(bytes)
|
val encoded = Base64.getEncoder.encodeToString(bytes)
|
||||||
images += ((id, encoded))
|
images.put(id, encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0")
|
@deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0")
|
||||||
@@ -77,28 +80,28 @@ class PluginRegistry {
|
|||||||
addImage(id, bytes)
|
addImage(id, bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getImage(id: String): String = images(id)
|
def getImage(id: String): String = images.get(id)
|
||||||
|
|
||||||
def addController(path: String, controller: ControllerBase): Unit = controllers += ((controller, path))
|
def addController(path: String, controller: ControllerBase): Unit = controllers.add((controller, path))
|
||||||
|
|
||||||
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
|
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
|
||||||
def addController(controller: ControllerBase, path: String): Unit = addController(path, controller)
|
def addController(controller: ControllerBase, path: String): Unit = addController(path, controller)
|
||||||
|
|
||||||
def getControllers(): Seq[(ControllerBase, String)] = controllers.toSeq
|
def getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq
|
||||||
|
|
||||||
def addJavaScript(path: String, script: String): Unit = javaScripts += ((path, script))
|
def addJavaScript(path: String, script: String): Unit = javaScripts.add((path, script)) //javaScripts += ((path, script))
|
||||||
|
|
||||||
def getJavaScript(currentPath: String): List[String] = javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
def getJavaScript(currentPath: String): List[String] = javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
||||||
|
|
||||||
def addRenderer(extension: String, renderer: Renderer): Unit = renderers += ((extension, renderer))
|
def addRenderer(extension: String, renderer: Renderer): Unit = renderers.put(extension, renderer)
|
||||||
|
|
||||||
def getRenderer(extension: String): Renderer = renderers.getOrElse(extension, DefaultRenderer)
|
def getRenderer(extension: String): Renderer = renderers.asScala.getOrElse(extension, DefaultRenderer)
|
||||||
|
|
||||||
def renderableExtensions: Seq[String] = renderers.keys.toSeq
|
def renderableExtensions: Seq[String] = renderers.keys.asScala.toSeq
|
||||||
|
|
||||||
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings += routing
|
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings.add(routing)
|
||||||
|
|
||||||
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.toSeq
|
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.asScala.toSeq
|
||||||
|
|
||||||
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
|
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
|
||||||
PluginRegistry().getRepositoryRoutings().find {
|
PluginRegistry().getRepositoryRoutings().find {
|
||||||
@@ -108,69 +111,73 @@ class PluginRegistry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def addAccountHook(accountHook: AccountHook): Unit = accountHooks += accountHook
|
def addAccountHook(accountHook: AccountHook): Unit = accountHooks.add(accountHook)
|
||||||
|
|
||||||
def getAccountHooks: Seq[AccountHook] = accountHooks.toSeq
|
def getAccountHooks: Seq[AccountHook] = accountHooks.asScala.toSeq
|
||||||
|
|
||||||
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks += commitHook
|
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks.add(commitHook)
|
||||||
|
|
||||||
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.asScala.toSeq
|
||||||
|
|
||||||
def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks += repositoryHook
|
def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks.add(repositoryHook)
|
||||||
|
|
||||||
def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.toSeq
|
def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.asScala.toSeq
|
||||||
|
|
||||||
def addIssueHook(issueHook: IssueHook): Unit = issueHooks += issueHook
|
def addIssueHook(issueHook: IssueHook): Unit = issueHooks.add(issueHook)
|
||||||
|
|
||||||
def getIssueHooks: Seq[IssueHook] = issueHooks.toSeq
|
def getIssueHooks: Seq[IssueHook] = issueHooks.asScala.toSeq
|
||||||
|
|
||||||
def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks += pullRequestHook
|
def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks.add(pullRequestHook)
|
||||||
|
|
||||||
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.toSeq
|
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.asScala.toSeq
|
||||||
|
|
||||||
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu
|
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders.add(repositoryHeader)
|
||||||
|
|
||||||
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
|
def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.asScala.toSeq
|
||||||
|
|
||||||
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus += repositoryMenu
|
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus.add(globalMenu)
|
||||||
|
|
||||||
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq
|
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.asScala.toSeq
|
||||||
|
|
||||||
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs += repositorySettingTab
|
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus.add(repositoryMenu)
|
||||||
|
|
||||||
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq
|
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.asScala.toSeq
|
||||||
|
|
||||||
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs += profileTab
|
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs.add(repositorySettingTab)
|
||||||
|
|
||||||
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq
|
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.asScala.toSeq
|
||||||
|
|
||||||
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus += systemSettingMenu
|
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs.add(profileTab)
|
||||||
|
|
||||||
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq
|
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.asScala.toSeq
|
||||||
|
|
||||||
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus += accountSettingMenu
|
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus.add(systemSettingMenu)
|
||||||
|
|
||||||
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq
|
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.asScala.toSeq
|
||||||
|
|
||||||
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs += dashboardTab
|
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus.add(accountSettingMenu)
|
||||||
|
|
||||||
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq
|
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.asScala.toSeq
|
||||||
|
|
||||||
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars += issueSidebar
|
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs.add(dashboardTab)
|
||||||
|
|
||||||
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.toSeq
|
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.asScala.toSeq
|
||||||
|
|
||||||
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings += assetsMapping
|
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars.add(issueSidebar)
|
||||||
|
|
||||||
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.toSeq
|
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.asScala.toSeq
|
||||||
|
|
||||||
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators += textDecorator
|
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings.add(assetsMapping)
|
||||||
|
|
||||||
def getTextDecorators: Seq[TextDecorator] = textDecorators.toSeq
|
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.asScala.toSeq
|
||||||
|
|
||||||
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders += suggestionProvider
|
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators.add(textDecorator)
|
||||||
|
|
||||||
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.toSeq
|
def getTextDecorators: Seq[TextDecorator] = textDecorators.asScala.toSeq
|
||||||
|
|
||||||
|
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders.add(suggestionProvider)
|
||||||
|
|
||||||
|
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,79 +187,233 @@ object PluginRegistry {
|
|||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[PluginRegistry])
|
private val logger = LoggerFactory.getLogger(classOf[PluginRegistry])
|
||||||
|
|
||||||
private val instance = new PluginRegistry()
|
private var instance = new PluginRegistry()
|
||||||
|
|
||||||
|
private var watcher: PluginWatchThread = null
|
||||||
|
private var extraWatcher: PluginWatchThread = null
|
||||||
|
private val initializing = new AtomicBoolean(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the PluginRegistry singleton instance.
|
* Returns the PluginRegistry singleton instance.
|
||||||
*/
|
*/
|
||||||
def apply(): PluginRegistry = instance
|
def apply(): PluginRegistry = instance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload all plugins.
|
||||||
|
*/
|
||||||
|
def reload(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
|
||||||
|
shutdown(context, settings)
|
||||||
|
instance = new PluginRegistry()
|
||||||
|
initialize(context, settings, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstall a specified plugin.
|
||||||
|
*/
|
||||||
|
def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
|
||||||
|
instance.getPlugins()
|
||||||
|
.collect { case plugin if plugin.pluginId == pluginId => plugin }
|
||||||
|
.foreach { plugin =>
|
||||||
|
// try {
|
||||||
|
// plugin.pluginClass.uninstall(instance, context, settings)
|
||||||
|
// } catch {
|
||||||
|
// case e: Exception =>
|
||||||
|
// logger.error(s"Error during uninstalling plugin: ${plugin.pluginJar.getName}", e)
|
||||||
|
// }
|
||||||
|
shutdown(context, settings)
|
||||||
|
plugin.pluginJar.delete()
|
||||||
|
instance = new PluginRegistry()
|
||||||
|
initialize(context, settings, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Install a plugin from a specified jar file.
|
||||||
|
*/
|
||||||
|
def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
|
||||||
|
shutdown(context, settings)
|
||||||
|
FileUtils.copyFile(file, new File(PluginHome, file.getName))
|
||||||
|
instance = new PluginRegistry()
|
||||||
|
initialize(context, settings, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def listPluginJars(dir: File): Seq[File] = {
|
||||||
|
dir.listFiles(new FilenameFilter {
|
||||||
|
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||||
|
}).toSeq.sortBy(_.getName).reverse
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val extraPluginDir: Option[String] = Option(System.getProperty("gitbucket.pluginDir"))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes all installed plugins.
|
* Initializes all installed plugins.
|
||||||
*/
|
*/
|
||||||
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = {
|
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
|
||||||
val pluginDir = new File(PluginHome)
|
val pluginDir = new File(PluginHome)
|
||||||
val manager = new JDBCVersionManager(conn)
|
val manager = new JDBCVersionManager(conn)
|
||||||
|
|
||||||
if(pluginDir.exists && pluginDir.isDirectory){
|
// Clean installed directory
|
||||||
pluginDir.listFiles(new FilenameFilter {
|
val installedDir = new File(PluginHome, ".installed")
|
||||||
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
if(installedDir.exists){
|
||||||
}).sortBy(_.getName).foreach { pluginJar =>
|
FileUtils.deleteDirectory(installedDir)
|
||||||
val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
|
}
|
||||||
try {
|
installedDir.mkdir()
|
||||||
val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin]
|
|
||||||
|
|
||||||
// Migration
|
val pluginJars = listPluginJars(pluginDir)
|
||||||
val solidbase = new Solidbase()
|
val extraJars = extraPluginDir.map { extraDir => listPluginJars(new File(extraDir)) }.getOrElse(Nil)
|
||||||
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
|
|
||||||
|
|
||||||
// Check version
|
(extraJars ++ pluginJars).foreach { pluginJar =>
|
||||||
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
|
val installedJar = new File(installedDir, pluginJar.getName)
|
||||||
val pluginVersion = plugin.versions.last.getVersion
|
FileUtils.copyFile(pluginJar, installedJar)
|
||||||
if(databaseVersion != pluginVersion){
|
|
||||||
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
|
logger.info(s"Initialize ${pluginJar.getName}")
|
||||||
|
val classLoader = new URLClassLoader(Array(installedJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
|
||||||
|
try {
|
||||||
|
val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin]
|
||||||
|
val pluginId = plugin.pluginId
|
||||||
|
|
||||||
|
// Check duplication
|
||||||
|
instance.getPlugins().find(_.pluginId == pluginId) match {
|
||||||
|
case Some(x) => {
|
||||||
|
logger.warn(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.")
|
||||||
}
|
}
|
||||||
|
case None => {
|
||||||
|
// Migration
|
||||||
|
val solidbase = new Solidbase()
|
||||||
|
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
|
||||||
|
|
||||||
// Initialize
|
// Check database version
|
||||||
plugin.initialize(instance, context, settings)
|
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
|
||||||
instance.addPlugin(PluginInfo(
|
val pluginVersion = plugin.versions.last.getVersion
|
||||||
pluginId = plugin.pluginId,
|
if (databaseVersion != pluginVersion) {
|
||||||
pluginName = plugin.pluginName,
|
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
|
||||||
pluginVersion = plugin.versions.last.getVersion,
|
}
|
||||||
description = plugin.description,
|
|
||||||
pluginClass = plugin
|
|
||||||
))
|
|
||||||
|
|
||||||
} catch {
|
// Initialize
|
||||||
case e: Throwable => {
|
plugin.initialize(instance, context, settings)
|
||||||
logger.error(s"Error during plugin initialization: ${pluginJar.getAbsolutePath}", e)
|
instance.addPlugin(PluginInfo(
|
||||||
|
pluginId = plugin.pluginId,
|
||||||
|
pluginName = plugin.pluginName,
|
||||||
|
pluginVersion = plugin.versions.last.getVersion,
|
||||||
|
description = plugin.description,
|
||||||
|
pluginClass = plugin,
|
||||||
|
pluginJar = pluginJar,
|
||||||
|
classLoader = classLoader
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
case e: Throwable => logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(watcher == null){
|
||||||
|
watcher = new PluginWatchThread(context, PluginHome)
|
||||||
|
watcher.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
extraPluginDir.foreach { extraDir =>
|
||||||
|
if(extraWatcher == null){
|
||||||
|
extraWatcher = new PluginWatchThread(context, extraDir)
|
||||||
|
extraWatcher.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def shutdown(context: ServletContext, settings: SystemSettings): Unit = {
|
def shutdown(context: ServletContext, settings: SystemSettings): Unit = synchronized {
|
||||||
instance.getPlugins().foreach { pluginInfo =>
|
instance.getPlugins().foreach { plugin =>
|
||||||
try {
|
try {
|
||||||
pluginInfo.pluginClass.shutdown(instance, context, settings)
|
plugin.pluginClass.shutdown(instance, context, settings)
|
||||||
|
if(watcher != null){
|
||||||
|
watcher.interrupt()
|
||||||
|
watcher = null
|
||||||
|
}
|
||||||
|
if(extraWatcher != null){
|
||||||
|
extraWatcher.interrupt()
|
||||||
|
extraWatcher = null
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case e: Exception => {
|
case e: Exception => {
|
||||||
logger.error(s"Error during plugin shutdown", e)
|
logger.error(s"Error during plugin shutdown: ${plugin.pluginJar.getName}", e)
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
plugin.classLoader.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case class Link(id: String, label: String, path: String, icon: Option[String] = None)
|
case class Link(
|
||||||
|
id: String,
|
||||||
|
label: String,
|
||||||
|
path: String,
|
||||||
|
icon: Option[String] = None
|
||||||
|
)
|
||||||
|
|
||||||
|
class PluginInfoBase(
|
||||||
|
val pluginId: String,
|
||||||
|
val pluginName: String,
|
||||||
|
val pluginVersion: String,
|
||||||
|
val description: String
|
||||||
|
)
|
||||||
|
|
||||||
case class PluginInfo(
|
case class PluginInfo(
|
||||||
pluginId: String,
|
override val pluginId: String,
|
||||||
pluginName: String,
|
override val pluginName: String,
|
||||||
pluginVersion: String,
|
override val pluginVersion: String,
|
||||||
description: String,
|
override val description: String,
|
||||||
pluginClass: Plugin
|
pluginClass: Plugin,
|
||||||
)
|
pluginJar: File,
|
||||||
|
classLoader: URLClassLoader
|
||||||
|
) extends PluginInfoBase(pluginId, pluginName, pluginVersion, description)
|
||||||
|
|
||||||
|
class PluginWatchThread(context: ServletContext, dir: String) extends Thread with SystemSettingsService {
|
||||||
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[PluginWatchThread])
|
||||||
|
|
||||||
|
override def run(): Unit = {
|
||||||
|
val path = Paths.get(dir)
|
||||||
|
if(!Files.exists(path)){
|
||||||
|
Files.createDirectories(path)
|
||||||
|
}
|
||||||
|
val fs = path.getFileSystem
|
||||||
|
val watcher = fs.newWatchService
|
||||||
|
|
||||||
|
val watchKey = path.register(watcher,
|
||||||
|
StandardWatchEventKinds.ENTRY_CREATE,
|
||||||
|
StandardWatchEventKinds.ENTRY_MODIFY,
|
||||||
|
StandardWatchEventKinds.ENTRY_DELETE,
|
||||||
|
StandardWatchEventKinds.OVERFLOW)
|
||||||
|
|
||||||
|
logger.info("Start PluginWatchThread: " + path)
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (watchKey.isValid()) {
|
||||||
|
val detectedWatchKey = watcher.take()
|
||||||
|
val events = detectedWatchKey.pollEvents.asScala.filter { e =>
|
||||||
|
e.context.toString != ".installed" && !e.context.toString.endsWith(".bak")
|
||||||
|
}
|
||||||
|
if(events.nonEmpty){
|
||||||
|
events.foreach { event =>
|
||||||
|
logger.info(event.kind + ": " + event.context)
|
||||||
|
}
|
||||||
|
|
||||||
|
gitbucket.core.servlet.Database() withTransaction { session =>
|
||||||
|
logger.info("Reloading plugins...")
|
||||||
|
PluginRegistry.reload(context, loadSystemSettings(), session.conn)
|
||||||
|
logger.info("Reloading finished.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
detectedWatchKey.reset()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
case _: InterruptedException => watchKey.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Shutdown PluginWatchThread")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
41
src/main/scala/gitbucket/core/plugin/PluginRepository.scala
Normal file
41
src/main/scala/gitbucket/core/plugin/PluginRepository.scala
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import org.json4s._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
|
|
||||||
|
object PluginRepository {
|
||||||
|
implicit val formats = DefaultFormats
|
||||||
|
|
||||||
|
def parsePluginJson(json: String): Seq[PluginMetadata] = {
|
||||||
|
org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]]
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy val LocalRepositoryDir = new java.io.File(PluginHome, ".repository")
|
||||||
|
lazy val LocalRepositoryIndexFile = new java.io.File(LocalRepositoryDir, "plugins.json")
|
||||||
|
|
||||||
|
def getPlugins(): Seq[PluginMetadata] = {
|
||||||
|
if(LocalRepositoryIndexFile.exists){
|
||||||
|
parsePluginJson(FileUtils.readFileToString(LocalRepositoryIndexFile, "UTF-8"))
|
||||||
|
} else Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapped from plugins.json
|
||||||
|
case class PluginMetadata(
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
versions: Seq[VersionDef],
|
||||||
|
default: Boolean = false
|
||||||
|
){
|
||||||
|
lazy val latestVersion: VersionDef = versions.last
|
||||||
|
}
|
||||||
|
|
||||||
|
case class VersionDef(
|
||||||
|
version: String,
|
||||||
|
file: String,
|
||||||
|
range: String
|
||||||
|
)
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ trait HandleCommentService {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
actionActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) )
|
actionActivity.foreach { f => f(owner, name, userName, issue.issueId, issue.title) }
|
||||||
|
|
||||||
// call web hooks
|
// call web hooks
|
||||||
action match {
|
action match {
|
||||||
|
|||||||
@@ -32,8 +32,11 @@ trait IssuesService {
|
|||||||
.list
|
.list
|
||||||
|
|
||||||
def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = {
|
def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = {
|
||||||
getCommentsForApi(owner, repository, issueId)
|
IssueComments.filter(_.byIssue(owner, repository, issueId))
|
||||||
.collectFirst { case (comment, account, _) if comment.action == "merged" => (comment, account) }
|
.filter(_.action === "merge".bind)
|
||||||
|
.join(Accounts).on { case t1 ~ t2 => t1.commentedUserName === t2.userName }
|
||||||
|
.map { case t1 ~ t2 => (t1, t2)}
|
||||||
|
.firstOption
|
||||||
}
|
}
|
||||||
|
|
||||||
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session): Option[IssueComment] = {
|
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session): Option[IssueComment] = {
|
||||||
@@ -327,6 +330,7 @@ trait IssuesService {
|
|||||||
|
|
||||||
def createComment(owner: String, repository: String, loginUser: String,
|
def createComment(owner: String, repository: String, loginUser: String,
|
||||||
issueId: Int, content: String, action: String)(implicit s: Session): Int = {
|
issueId: Int, content: String, action: String)(implicit s: Session): Int = {
|
||||||
|
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
|
||||||
IssueComments returning IssueComments.map(_.commentId) insert IssueComment(
|
IssueComments returning IssueComments.map(_.commentId) insert IssueComment(
|
||||||
userName = owner,
|
userName = owner,
|
||||||
repositoryName = repository,
|
repositoryName = repository,
|
||||||
@@ -342,31 +346,33 @@ trait IssuesService {
|
|||||||
Issues
|
Issues
|
||||||
.filter (_.byPrimaryKey(owner, repository, issueId))
|
.filter (_.byPrimaryKey(owner, repository, issueId))
|
||||||
.map { t => (t.title, t.content.?, t.updatedDate) }
|
.map { t => (t.title, t.content.?, t.updatedDate) }
|
||||||
.update (title, content, currentDate)
|
.update(title, content, currentDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String])(implicit s: Session): Int = {
|
def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String])(implicit s: Session): Int = {
|
||||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName)
|
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.assignedUserName?, t.updatedDate)).update(assignedUserName, currentDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int])(implicit s: Session): Int = {
|
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int])(implicit s: Session): Int = {
|
||||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
|
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.milestoneId?, t.updatedDate)).update(milestoneId, currentDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int])(implicit s: Session): Int = {
|
def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int])(implicit s: Session): Int = {
|
||||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.priorityId?).update (priorityId)
|
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.priorityId?, t.updatedDate)).update(priorityId, currentDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateComment(commentId: Int, content: String)(implicit s: Session): Int = {
|
def updateComment(issueId: Int, commentId: Int, content: String)(implicit s: Session): Int = {
|
||||||
IssueComments.filter (_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
|
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
|
||||||
|
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
def deleteComment(commentId: Int)(implicit s: Session): Int = {
|
def deleteComment(issueId: Int, commentId: Int)(implicit s: Session): Int = {
|
||||||
IssueComments filter (_.byPrimaryKey(commentId)) delete
|
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
|
||||||
|
IssueComments.filter(_.byPrimaryKey(commentId)).delete
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session): Int = {
|
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session): Int = {
|
||||||
(Issues filter (_.byPrimaryKey(owner, repository, issueId)) map(t => (t.closed, t.updatedDate))).update((closed, currentDate))
|
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.closed, t.updatedDate)).update(closed, currentDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -497,14 +503,14 @@ object IssuesService {
|
|||||||
).flatten ++
|
).flatten ++
|
||||||
labels.map(label => s"label:${label}") ++
|
labels.map(label => s"label:${label}") ++
|
||||||
List(
|
List(
|
||||||
milestone.map { _ match {
|
milestone.map {
|
||||||
case Some(x) => s"milestone:${x}"
|
case Some(x) => s"milestone:${x}"
|
||||||
case None => "no:milestone"
|
case None => "no:milestone"
|
||||||
}},
|
},
|
||||||
priority.map { _ match {
|
priority.map {
|
||||||
case Some(x) => s"priority:${x}"
|
case Some(x) => s"priority:${x}"
|
||||||
case None => "no:priority"
|
case None => "no:priority"
|
||||||
}},
|
},
|
||||||
(sort, direction) match {
|
(sort, direction) match {
|
||||||
case ("created" , "desc") => None
|
case ("created" , "desc") => None
|
||||||
case ("created" , "asc" ) => Some("sort:created-asc")
|
case ("created" , "asc" ) => Some("sort:created-asc")
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ object MergeService{
|
|||||||
case e: NoMergeBaseException => true
|
case e: NoMergeBaseException => true
|
||||||
}
|
}
|
||||||
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
|
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
|
||||||
val committer = mergeTipCommit.getCommitterIdent;
|
val committer = mergeTipCommit.getCommitterIdent
|
||||||
def updateBranch(treeId:ObjectId, message:String, branchName:String){
|
def updateBranch(treeId:ObjectId, message:String, branchName:String){
|
||||||
// creates merge commit
|
// creates merge commit
|
||||||
val mergeCommitId = createMergeCommit(treeId, committer, message)
|
val mergeCommitId = createMergeCommit(treeId, committer, message)
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ trait ProtectedBranchService {
|
|||||||
.filter(_._1.byPrimaryKey(owner, repository, branch))
|
.filter(_._1.byPrimaryKey(owner, repository, branch))
|
||||||
.list
|
.list
|
||||||
.groupBy(_._1)
|
.groupBy(_._1)
|
||||||
|
.headOption
|
||||||
.map { p => p._1 -> p._2.flatMap(_._2) }
|
.map { p => p._1 -> p._2.flatMap(_._2) }
|
||||||
.map { case (t1, contexts) =>
|
.map { case (t1, contexts) =>
|
||||||
new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin)
|
new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin)
|
||||||
}.headOption
|
}
|
||||||
|
|
||||||
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit session: Session): ProtectedBranchInfo =
|
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit session: Session): ProtectedBranchInfo =
|
||||||
getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(ProtectedBranchInfo.disabled(owner, repository))
|
getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(ProtectedBranchInfo.disabled(owner, repository))
|
||||||
|
|||||||
@@ -94,9 +94,9 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* for repository viewer.
|
* for repository viewer.
|
||||||
* 1. find pull request from from `branch` to othre branch on same repository
|
* 1. find pull request from `branch` to other branch on same repository
|
||||||
* 1. return if exists pull request to `defaultBranch`
|
* 1. return if exists pull request to `defaultBranch`
|
||||||
* 2. return if exists pull request to othre branch
|
* 2. return if exists pull request to other branch
|
||||||
* 2. return None
|
* 2. return None
|
||||||
*/
|
*/
|
||||||
def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String)
|
def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String)
|
||||||
@@ -256,7 +256,7 @@ object PullRequestService {
|
|||||||
val statuses: List[CommitStatus] =
|
val statuses: List[CommitStatus] =
|
||||||
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
|
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
|
||||||
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS))
|
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS))
|
||||||
val hasProblem = hasRequiredStatusProblem || hasConflict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
||||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||||
lazy val commitStateSummary:(CommitState, String) = {
|
lazy val commitStateSummary:(CommitState, String) = {
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ trait RepositorySearchService { self: IssuesService =>
|
|||||||
files.map { case (path, text) =>
|
files.map { case (path, text) =>
|
||||||
val (highlightText, lineNumber) = getHighlightText(text, query)
|
val (highlightText, lineNumber) = getHighlightText(text, query)
|
||||||
FileSearchResult(
|
FileSearchResult(
|
||||||
path.replaceFirst("\\.md$", ""),
|
path.stripSuffix(".md"),
|
||||||
commits(path).getCommitterIdent.getWhen,
|
commits(path).getCommitterIdent.getWhen,
|
||||||
highlightText,
|
highlightText,
|
||||||
lineNumber)
|
lineNumber)
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ trait RepositoryService { self: AccountService =>
|
|||||||
repositoryName = newRepositoryName
|
repositoryName = newRepositoryName
|
||||||
)) :_*)
|
)) :_*)
|
||||||
|
|
||||||
// TODO Drop transfered owner from collaborators?
|
// TODO Drop transferred owner from collaborators?
|
||||||
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||||
|
|
||||||
// Update activity messages
|
// Update activity messages
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ trait SystemSettingsService {
|
|||||||
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
props.setProperty(SkinName, settings.skinName.toString)
|
||||||
using(new java.io.FileOutputStream(GitBucketConf)){ out =>
|
using(new java.io.FileOutputStream(GitBucketConf)){ out =>
|
||||||
props.store(out, null)
|
props.store(out, null)
|
||||||
}
|
}
|
||||||
@@ -111,7 +112,8 @@ trait SystemSettingsService {
|
|||||||
getOptionValue(props, LdapKeystore, None)))
|
getOptionValue(props, LdapKeystore, None)))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
},
|
||||||
|
getValue(props, SkinName, "skin-blue")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +138,8 @@ object SystemSettingsService {
|
|||||||
useSMTP: Boolean,
|
useSMTP: Boolean,
|
||||||
smtp: Option[Smtp],
|
smtp: Option[Smtp],
|
||||||
ldapAuthentication: Boolean,
|
ldapAuthentication: Boolean,
|
||||||
ldap: Option[Ldap]){
|
ldap: Option[Ldap],
|
||||||
|
skinName: String){
|
||||||
def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
|
def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
|
||||||
|
|
||||||
def sshAddress:Option[SshAddress] =
|
def sshAddress:Option[SshAddress] =
|
||||||
@@ -219,6 +222,7 @@ object SystemSettingsService {
|
|||||||
private val LdapTls = "ldap.tls"
|
private val LdapTls = "ldap.tls"
|
||||||
private val LdapSsl = "ldap.ssl"
|
private val LdapSsl = "ldap.ssl"
|
||||||
private val LdapKeystore = "ldap.keystore"
|
private val LdapKeystore = "ldap.keystore"
|
||||||
|
private val SkinName = "skinName"
|
||||||
|
|
||||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||||
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {
|
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {
|
||||||
|
|||||||
@@ -367,9 +367,9 @@ object WebHookService {
|
|||||||
repository: ApiRepository
|
repository: ApiRepository
|
||||||
) extends FieldSerializable with WebHookPayload {
|
) extends FieldSerializable with WebHookPayload {
|
||||||
val compare = commits.size match {
|
val compare = commits.size match {
|
||||||
case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initalied repository
|
case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initialized repository
|
||||||
case 1 => ApiPath(s"/${repository.full_name}/commit/${after}")
|
case 1 => ApiPath(s"/${repository.full_name}/commit/${after}")
|
||||||
case _ if before.filterNot(_=='0').isEmpty => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}")
|
case _ if before.forall(_=='0') => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}")
|
||||||
case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}")
|
case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}")
|
||||||
}
|
}
|
||||||
val head_commit = commits.lastOption
|
val head_commit = commits.lastOption
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ trait WikiService {
|
|||||||
builder.finish()
|
builder.finish()
|
||||||
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||||
Constants.HEAD, committer.fullName, committer.mailAddress,
|
Constants.HEAD, committer.fullName, committer.mailAddress,
|
||||||
if(message.trim.length == 0) {
|
if(message.trim.isEmpty) {
|
||||||
if(removed){
|
if(removed){
|
||||||
s"Rename ${currentPageName} to ${newPageName}"
|
s"Rename ${currentPageName} to ${newPageName}"
|
||||||
} else if(created){
|
} else if(created){
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package gitbucket.core.servlet
|
|
||||||
|
|
||||||
import javax.servlet._
|
|
||||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
|
||||||
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A controller to provide GitHub compatible URL for Git clients.
|
|
||||||
*/
|
|
||||||
class GHCompatRepositoryAccessFilter extends Filter with SystemSettingsService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pattern of GitHub compatible repository URL.
|
|
||||||
* <code>/:user/:repo.git/</code>
|
|
||||||
*/
|
|
||||||
private val githubRepositoryPattern = """^/[^/]+/[^/]+\.git/.*""".r
|
|
||||||
|
|
||||||
override def init(filterConfig: FilterConfig) = {}
|
|
||||||
|
|
||||||
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) = {
|
|
||||||
implicit val request = req.asInstanceOf[HttpServletRequest]
|
|
||||||
val agent = request.getHeader("USER-AGENT")
|
|
||||||
val response = res.asInstanceOf[HttpServletResponse]
|
|
||||||
val requestPath = request.getRequestURI.substring(request.getContextPath.length)
|
|
||||||
|
|
||||||
requestPath match {
|
|
||||||
case githubRepositoryPattern() if agent != null && agent.toLowerCase.indexOf("git") >= 0 =>
|
|
||||||
response.sendRedirect(baseUrl + "/git" + requestPath)
|
|
||||||
case _ =>
|
|
||||||
chain.doFilter(req, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override def destroy() = {}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -74,7 +74,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
|
|||||||
val action = request.paths match {
|
val action = request.paths match {
|
||||||
case Array(_, repositoryOwner, repositoryName, _*) =>
|
case Array(_, repositoryOwner, repositoryName, _*) =>
|
||||||
Database() withSession { implicit session =>
|
Database() withSession { implicit session =>
|
||||||
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match {
|
getRepository(repositoryOwner, repositoryName.replaceFirst("(\\.wiki)?\\.git$", "")) match {
|
||||||
case Some(repository) => {
|
case Some(repository) => {
|
||||||
val execute = if (!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess) {
|
val execute = if (!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess) {
|
||||||
// Authentication is not required
|
// Authentication is not required
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
|
|
||||||
if(repository.endsWith(".wiki")){
|
if(repository.endsWith(".wiki")){
|
||||||
defining(request) { implicit r =>
|
defining(request) { implicit r =>
|
||||||
receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.replaceFirst("\\.wiki$", ""), pusher, baseUrl))
|
receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
package gitbucket.core.servlet
|
package gitbucket.core.servlet
|
||||||
|
|
||||||
import java.io.File
|
import java.io.{File, FileOutputStream}
|
||||||
|
|
||||||
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.GitBucketCoreModule
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.{PluginRegistry, PluginRepository}
|
||||||
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
||||||
import gitbucket.core.util.DatabaseConfig
|
import gitbucket.core.util.DatabaseConfig
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.util.JDBCUtil._
|
import gitbucket.core.util.JDBCUtil._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import io.github.gitbucket.solidbase.Solidbase
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
import javax.servlet.{ServletContextListener, ServletContextEvent}
|
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||||
import org.apache.commons.io.FileUtils
|
|
||||||
|
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import akka.actor.{Actor, Props, ActorSystem}
|
import akka.actor.{Actor, ActorSystem, Props}
|
||||||
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
||||||
|
import com.github.zafarkhaja.semver.{Version => Semver}
|
||||||
|
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize GitBucket system.
|
* Initialize GitBucket system.
|
||||||
* Update database schema and load plug-ins automatically in the context initializing.
|
* Update database schema and load plug-ins automatically in the context initializing.
|
||||||
@@ -54,44 +59,11 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
|||||||
val manager = new JDBCVersionManager(conn)
|
val manager = new JDBCVersionManager(conn)
|
||||||
|
|
||||||
// Check version
|
// Check version
|
||||||
val versionFile = new File(GitBucketHome, "version")
|
checkVersion(manager, conn)
|
||||||
|
|
||||||
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
|
|
||||||
manager.initialize()
|
|
||||||
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.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
|
// Run normal migration
|
||||||
logger.info("Start schema update")
|
logger.info("Start schema update")
|
||||||
val solidbase = new Solidbase()
|
new Solidbase().migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
||||||
solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
|
||||||
|
|
||||||
// Rescue code for users who updated from 3.14 to 4.0.0
|
// Rescue code for users who updated from 3.14 to 4.0.0
|
||||||
// https://github.com/gitbucket/gitbucket/issues/1227
|
// https://github.com/gitbucket/gitbucket/issues/1227
|
||||||
@@ -106,6 +78,9 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
|||||||
throw new IllegalStateException(s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}.")
|
throw new IllegalStateException(s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Install bundled plugins
|
||||||
|
extractBundledPlugins(gitbucketVersion)
|
||||||
|
|
||||||
// Load plugins
|
// Load plugins
|
||||||
logger.info("Initialize plugins")
|
logger.info("Initialize plugins")
|
||||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||||
@@ -117,7 +92,76 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
|||||||
scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
|
scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = {
|
||||||
|
logger.info("Check version")
|
||||||
|
val versionFile = new File(GitBucketHome, "version")
|
||||||
|
|
||||||
|
if(versionFile.exists()){
|
||||||
|
val version = FileUtils.readFileToString(versionFile, "UTF-8")
|
||||||
|
if(version == "3.14"){
|
||||||
|
// Initialization for GitBucket 3.14
|
||||||
|
logger.info("Migration to GitBucket 4.x start")
|
||||||
|
|
||||||
|
// Backup current data
|
||||||
|
val dataMvFile = new File(GitBucketHome, "data.mv.db")
|
||||||
|
if(dataMvFile.exists) {
|
||||||
|
FileUtils.copyFile(dataMvFile, new File(GitBucketHome, "data.mv.db_3.14"))
|
||||||
|
}
|
||||||
|
val dataTraceFile = new File(GitBucketHome, "data.trace.db")
|
||||||
|
if(dataTraceFile.exists) {
|
||||||
|
FileUtils.copyFile(dataTraceFile, new File(GitBucketHome, "data.trace.db_3.14"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change form
|
||||||
|
manager.initialize()
|
||||||
|
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def extractBundledPlugins(gitbucketVersion: String): Unit = {
|
||||||
|
logger.info("Extract bundled plugins")
|
||||||
|
val cl = Thread.currentThread.getContextClassLoader
|
||||||
|
try {
|
||||||
|
using(cl.getResourceAsStream("plugins/plugins.json")){ pluginsFile =>
|
||||||
|
if(pluginsFile != null){
|
||||||
|
val pluginsJson = IOUtils.toString(pluginsFile, "UTF-8")
|
||||||
|
|
||||||
|
FileUtils.forceMkdir(PluginRepository.LocalRepositoryDir)
|
||||||
|
FileUtils.write(PluginRepository.LocalRepositoryIndexFile, pluginsJson, "UTF-8")
|
||||||
|
|
||||||
|
val plugins = PluginRepository.parsePluginJson(pluginsJson)
|
||||||
|
plugins.foreach { plugin =>
|
||||||
|
plugin.versions.sortBy { x => Semver.valueOf(x.version) }.reverse.zipWithIndex.foreach { case (version, i) =>
|
||||||
|
val file = new File(PluginRepository.LocalRepositoryDir, version.file)
|
||||||
|
if(!file.exists) {
|
||||||
|
logger.info(s"Copy ${plugin} to ${file.getAbsolutePath}")
|
||||||
|
FileUtils.forceMkdirParent(file)
|
||||||
|
using(cl.getResourceAsStream("plugins/" + version.file), new FileOutputStream(file)){ case (in, out) => IOUtils.copy(in, out) }
|
||||||
|
|
||||||
|
if(plugin.default && i == 0){
|
||||||
|
logger.info(s"Enable ${file.getName} in default")
|
||||||
|
FileUtils.copyFile(file, new File(PluginHome, version.file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
case e: Exception => logger.error("Error in extracting bundled plugin", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def contextDestroyed(event: ServletContextEvent): Unit = {
|
override def contextDestroyed(event: ServletContextEvent): Unit = {
|
||||||
// Shutdown Quartz scheduler
|
// Shutdown Quartz scheduler
|
||||||
@@ -146,4 +190,4 @@ class DeleteOldActivityActor extends Actor with SystemSettingsService with Activ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class PluginAssetsServlet extends HttpServlet {
|
|||||||
.find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) }
|
.find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) }
|
||||||
.flatMap { case (prefix, resourcePath, classLoader) =>
|
.flatMap { case (prefix, resourcePath, classLoader) =>
|
||||||
val resourceName = path.substring(("/plugin-assets" + prefix).length)
|
val resourceName = path.substring(("/plugin-assets" + prefix).length)
|
||||||
Option(classLoader.getResourceAsStream(resourcePath.replaceFirst("^/", "") + resourceName))
|
Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName))
|
||||||
}
|
}
|
||||||
.map { in =>
|
.map { in =>
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package gitbucket.core.servlet
|
||||||
|
|
||||||
|
import javax.servlet._
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
|
||||||
|
class PluginControllerFilter extends Filter {
|
||||||
|
|
||||||
|
private var filterConfig: FilterConfig = null
|
||||||
|
|
||||||
|
override def init(filterConfig: FilterConfig): Unit = {
|
||||||
|
this.filterConfig = filterConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
override def destroy(): Unit = {
|
||||||
|
PluginRegistry().getControllers().foreach { case (controller, _) =>
|
||||||
|
controller.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||||
|
val controller = PluginRegistry().getControllers().filter { case (_, path) =>
|
||||||
|
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI
|
||||||
|
val start = path.replaceFirst("/\\*$", "/")
|
||||||
|
path.endsWith("/*") && (requestUri + "/").startsWith(start)
|
||||||
|
}
|
||||||
|
|
||||||
|
val filterChainWrapper = controller.foldLeft(chain){ case (chain, (controller, _)) =>
|
||||||
|
new FilterChainWrapper(controller, chain)
|
||||||
|
}
|
||||||
|
filterChainWrapper.doFilter(request, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterChainWrapper(controller: ControllerBase, chain: FilterChain) extends FilterChain {
|
||||||
|
override def doFilter(request: ServletRequest, response: ServletResponse): Unit = {
|
||||||
|
if(controller.config == null){
|
||||||
|
controller.init(filterConfig)
|
||||||
|
}
|
||||||
|
controller.doFilter(request, response, chain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ import org.apache.sshd.server.scp.UnknownCommand
|
|||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||||
|
|
||||||
object GitCommand {
|
object GitCommand {
|
||||||
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
|
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
|
||||||
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
|
|||||||
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||||
import gitbucket.core.util.{Directory}
|
import gitbucket.core.util.Directory
|
||||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
|
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ object DatabaseType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object MySQL extends DatabaseType {
|
object MySQL extends DatabaseType {
|
||||||
val jdbcDriver = "com.mysql.jdbc.Driver"
|
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
||||||
val slickDriver = BlockingMySQLDriver
|
val slickDriver = BlockingMySQLDriver
|
||||||
val liquiDriver = new MySQLDatabase()
|
val liquiDriver = new MySQLDatabase()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,4 +90,4 @@ object Directory {
|
|||||||
def getWikiRepositoryDir(owner: String, repository: String): File =
|
def getWikiRepositoryDir(owner: String, repository: String): File =
|
||||||
new File(s"${RepositoryHome}/${owner}/${repository}.wiki.git")
|
new File(s"${RepositoryHome}/${owner}/${repository}.wiki.git")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,9 +68,24 @@ object FileUtil {
|
|||||||
|
|
||||||
def readableSize(size: Long): String = FileUtils.byteCountToDisplaySize(size)
|
def readableSize(size: Long): String = FileUtils.byteCountToDisplaySize(size)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the given directory if it's empty.
|
||||||
|
* Do nothing if the given File is not a directory or not empty.
|
||||||
|
*/
|
||||||
def deleteDirectoryIfEmpty(dir: File): Unit = {
|
def deleteDirectoryIfEmpty(dir: File): Unit = {
|
||||||
if(dir.isDirectory() && dir.list().isEmpty) {
|
if(dir.isDirectory() && dir.list().isEmpty) {
|
||||||
FileUtils.deleteDirectory(dir)
|
FileUtils.deleteDirectory(dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete file or directory forcibly.
|
||||||
|
*/
|
||||||
|
def deleteIfExists(file: java.io.File): java.io.File = {
|
||||||
|
if(file.exists){
|
||||||
|
FileUtils.forceDelete(file)
|
||||||
|
}
|
||||||
|
file
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ object JDBCUtil {
|
|||||||
var stringLiteral = false
|
var stringLiteral = false
|
||||||
|
|
||||||
while({ length = in.read(bytes); length != -1 }){
|
while({ length = in.read(bytes); length != -1 }){
|
||||||
for(i <- 0 to length - 1){
|
for(i <- 0 until length){
|
||||||
val c = bytes(i)
|
val c = bytes(i)
|
||||||
if(c == '\''){
|
if(c == '\''){
|
||||||
stringLiteral = !stringLiteral
|
stringLiteral = !stringLiteral
|
||||||
@@ -146,13 +146,11 @@ object JDBCUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val columnValues = values.map { value =>
|
val columnValues = values.map {
|
||||||
value match {
|
case x: String => "'" + x.replace("'", "''") + "'"
|
||||||
case x: String => "'" + x.replace("'", "''") + "'"
|
case x: Timestamp => "'" + dateFormat.format(x) + "'"
|
||||||
case x: Timestamp => "'" + dateFormat.format(x) + "'"
|
case null => "NULL"
|
||||||
case null => "NULL"
|
case x => x
|
||||||
case x => x
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sb.append(columnValues.mkString(", "))
|
sb.append(columnValues.mkString(", "))
|
||||||
sb.append(");\n")
|
sb.append(");\n")
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import org.eclipse.jgit.revwalk.filter._
|
|||||||
import org.eclipse.jgit.treewalk._
|
import org.eclipse.jgit.treewalk._
|
||||||
import org.eclipse.jgit.treewalk.filter._
|
import org.eclipse.jgit.treewalk.filter._
|
||||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||||
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
|
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
import org.eclipse.jgit.transport.RefSpec
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -93,7 +93,7 @@ object JGitUtil {
|
|||||||
|
|
||||||
val summary = getSummaryMessage(fullMessage, shortMessage)
|
val summary = getSummaryMessage(fullMessage, shortMessage)
|
||||||
|
|
||||||
val description = defining(fullMessage.trim.indexOf("\n")){ i =>
|
val description = defining(fullMessage.trim.indexOf('\n')){ i =>
|
||||||
if(i >= 0){
|
if(i >= 0){
|
||||||
Some(fullMessage.trim.substring(i).trim)
|
Some(fullMessage.trim.substring(i).trim)
|
||||||
} else None
|
} else None
|
||||||
@@ -226,9 +226,14 @@ object JGitUtil {
|
|||||||
ref.getName.stripPrefix("refs/heads/")
|
ref.getName.stripPrefix("refs/heads/")
|
||||||
}.toList,
|
}.toList,
|
||||||
// tags
|
// tags
|
||||||
git.tagList.call.asScala.map { ref =>
|
git.tagList.call.asScala.flatMap { ref =>
|
||||||
val revCommit = getRevCommitFromId(git, ref.getObjectId)
|
try {
|
||||||
TagInfo(ref.getName.stripPrefix("refs/tags/"), revCommit.getCommitterIdent.getWhen, revCommit.getName)
|
val revCommit = getRevCommitFromId(git, ref.getObjectId)
|
||||||
|
Some(TagInfo(ref.getName.stripPrefix("refs/tags/"), revCommit.getCommitterIdent.getWhen, revCommit.getName))
|
||||||
|
} catch {
|
||||||
|
case _: IncorrectObjectTypeException =>
|
||||||
|
None
|
||||||
|
}
|
||||||
}.sortBy(_.time).toList
|
}.sortBy(_.time).toList
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -288,7 +293,7 @@ object JGitUtil {
|
|||||||
@tailrec
|
@tailrec
|
||||||
def findLastCommits(result:List[(ObjectId, FileMode, String, String, Option[String], RevCommit)],
|
def findLastCommits(result:List[(ObjectId, FileMode, String, String, Option[String], RevCommit)],
|
||||||
restList:List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])],
|
restList:List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])],
|
||||||
revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] ={
|
revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] = {
|
||||||
if(restList.isEmpty){
|
if(restList.isEmpty){
|
||||||
result
|
result
|
||||||
} else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty
|
} else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty
|
||||||
@@ -359,9 +364,9 @@ object JGitUtil {
|
|||||||
(file1.isDirectory, file2.isDirectory) match {
|
(file1.isDirectory, file2.isDirectory) match {
|
||||||
case (true , false) => true
|
case (true , false) => true
|
||||||
case (false, true ) => false
|
case (false, true ) => false
|
||||||
case _ => file1.name.compareTo(file2.name) < 0
|
case _ => file1.name.compareTo(file2.name) < 0
|
||||||
}
|
}
|
||||||
}.toList
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,7 +374,7 @@ object JGitUtil {
|
|||||||
* Returns the first line of the commit message.
|
* Returns the first line of the commit message.
|
||||||
*/
|
*/
|
||||||
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
||||||
defining(fullMessage.trim.indexOf("\n")){ i =>
|
defining(fullMessage.trim.indexOf('\n')){ i =>
|
||||||
defining(if(i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage){ firstLine =>
|
defining(if(i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage){ firstLine =>
|
||||||
if(firstLine.length > shortMessage.length) shortMessage else firstLine
|
if(firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||||
}
|
}
|
||||||
@@ -994,13 +999,13 @@ object JGitUtil {
|
|||||||
|
|
||||||
def getBlame(git: Git, id: String, path: String): Iterable[BlameInfo] = {
|
def getBlame(git: Git, id: String, path: String): Iterable[BlameInfo] = {
|
||||||
Option(git.getRepository.resolve(id)).map{ commitId =>
|
Option(git.getRepository.resolve(id)).map{ commitId =>
|
||||||
val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository);
|
val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository)
|
||||||
blamer.setStartCommit(commitId)
|
blamer.setStartCommit(commitId)
|
||||||
blamer.setFilePath(path)
|
blamer.setFilePath(path)
|
||||||
val blame = blamer.call()
|
val blame = blamer.call()
|
||||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||||
var idLine = List[(String, Int)]()
|
var idLine = List[(String, Int)]()
|
||||||
val commits = 0.to(blame.getResultContents().size()-1).map{ i =>
|
val commits = 0.to(blame.getResultContents().size() - 1).map{ i =>
|
||||||
val c = blame.getSourceCommit(i)
|
val c = blame.getSourceCommit(i)
|
||||||
if(!blameMap.contains(c.name)){
|
if(!blameMap.contains(c.name)){
|
||||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||||
@@ -1010,7 +1015,7 @@ object JGitUtil {
|
|||||||
c.getAuthorIdent.getWhen,
|
c.getAuthorIdent.getWhen,
|
||||||
Option(git.log.add(c).addPath(blame.getSourcePath(i)).setSkip(1).setMaxCount(2).call.iterator.next)
|
Option(git.log.add(c).addPath(blame.getSourcePath(i)).setSkip(1).setMaxCount(2).call.iterator.next)
|
||||||
.map(_.name),
|
.map(_.name),
|
||||||
if(blame.getSourcePath(i)==path){ None }else{ Some(blame.getSourcePath(i)) },
|
if(blame.getSourcePath(i)==path){ None } else { Some(blame.getSourcePath(i)) },
|
||||||
c.getCommitterIdent.getWhen,
|
c.getCommitterIdent.getWhen,
|
||||||
c.getShortMessage,
|
c.getShortMessage,
|
||||||
Set.empty)
|
Set.empty)
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import gitbucket.core.model.{Session, Issue, Account}
|
import gitbucket.core.model.{Session, Account}
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, SystemSettingsService}
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import gitbucket.core.servlet.Database
|
import gitbucket.core.servlet.Database
|
||||||
import gitbucket.core.view.Markdown
|
|
||||||
|
|
||||||
import scala.concurrent._
|
import scala.concurrent._
|
||||||
import scala.util.{Success, Failure}
|
import scala.util.{Success, Failure}
|
||||||
@@ -20,8 +19,12 @@ import SystemSettingsService.Smtp
|
|||||||
* Please see the plugin for details.
|
* Please see the plugin for details.
|
||||||
*/
|
*/
|
||||||
trait Notifier {
|
trait Notifier {
|
||||||
|
def toNotify(subject: String, textMsg: String)
|
||||||
|
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
|
||||||
|
toNotify(subject, textMsg, None)(recipients)
|
||||||
|
}
|
||||||
|
|
||||||
def toNotify(subject: String, msg: String)
|
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String])
|
||||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit
|
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -31,131 +34,12 @@ object Notifier {
|
|||||||
case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get)
|
case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get)
|
||||||
case _ => new MockMailer
|
case _ => new MockMailer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO This class is temporary keeping the current feature until Notifications Plugin is available.
|
|
||||||
class IssueHook extends gitbucket.core.plugin.IssueHook
|
|
||||||
with RepositoryService with AccountService with IssuesService {
|
|
||||||
|
|
||||||
override def created(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
|
|
||||||
Notifier().toNotify(
|
|
||||||
subject(issue, r),
|
|
||||||
message(issue.content getOrElse "", r)(content => s"""
|
|
||||||
|$content<br/>
|
|
||||||
|--<br/>
|
|
||||||
|<a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}"}">View it on GitBucket</a>
|
|
||||||
""".stripMargin)
|
|
||||||
)(recipients(issue))
|
|
||||||
}
|
|
||||||
|
|
||||||
override def addedComment(commentId: Int, content: String, issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
|
|
||||||
Notifier().toNotify(
|
|
||||||
subject(issue, r),
|
|
||||||
message(content, r)(content => s"""
|
|
||||||
|$content<br/>
|
|
||||||
|--<br/>
|
|
||||||
|<a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}#comment-$commentId"}">View it on GitBucket</a>
|
|
||||||
""".stripMargin)
|
|
||||||
)(recipients(issue))
|
|
||||||
}
|
|
||||||
|
|
||||||
override def closed(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
|
|
||||||
Notifier().toNotify(
|
|
||||||
subject(issue, r),
|
|
||||||
message("close", r)(content => s"""
|
|
||||||
|$content <a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}"}">#${issue.issueId}</a>
|
|
||||||
""".stripMargin)
|
|
||||||
)(recipients(issue))
|
|
||||||
}
|
|
||||||
|
|
||||||
override def reopened(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
|
|
||||||
Notifier().toNotify(
|
|
||||||
subject(issue, r),
|
|
||||||
message("reopen", r)(content => s"""
|
|
||||||
|$content <a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}"}">#${issue.issueId}</a>
|
|
||||||
""".stripMargin)
|
|
||||||
)(recipients(issue))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected def subject(issue: Issue, r: RepositoryService.RepositoryInfo): String =
|
|
||||||
s"[${r.owner}/${r.name}] ${issue.title} (#${issue.issueId})"
|
|
||||||
|
|
||||||
protected def message(content: String, r: RepositoryService.RepositoryInfo)(msg: String => String)(implicit context: Context): String =
|
|
||||||
msg(Markdown.toHtml(
|
|
||||||
markdown = content,
|
|
||||||
repository = r,
|
|
||||||
enableWikiLink = false,
|
|
||||||
enableRefsLink = true,
|
|
||||||
enableAnchor = false,
|
|
||||||
enableLineBreaks = false
|
|
||||||
))
|
|
||||||
|
|
||||||
protected val recipients: Issue => Account => Session => Seq[String] = {
|
|
||||||
issue => loginAccount => implicit session =>
|
|
||||||
(
|
|
||||||
// individual repository's owner
|
|
||||||
issue.userName ::
|
|
||||||
// group members of group repository
|
|
||||||
getGroupMembers(issue.userName).map(_.userName) :::
|
|
||||||
// collaborators
|
|
||||||
getCollaboratorUserNames(issue.userName, issue.repositoryName) :::
|
|
||||||
// participants
|
|
||||||
issue.openedUserName ::
|
|
||||||
getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
|
|
||||||
)
|
|
||||||
.distinct
|
|
||||||
.withFilter ( _ != loginAccount.userName ) // the operation in person is excluded
|
|
||||||
.flatMap (
|
|
||||||
getAccountByUserName(_)
|
|
||||||
.filterNot (_.isGroupAccount)
|
|
||||||
.filterNot (LDAPUtil.isDummyMailAddress)
|
|
||||||
.map (_.mailAddress)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO This class is temporary keeping the current feature until Notifications Plugin is available.
|
|
||||||
class PullRequestHook extends IssueHook with gitbucket.core.plugin.PullRequestHook {
|
|
||||||
override def created(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
|
|
||||||
val url = s"${context.baseUrl}/${r.owner}/${r.name}/pull/${issue.issueId}"
|
|
||||||
Notifier().toNotify(
|
|
||||||
subject(issue, r),
|
|
||||||
message(issue.content getOrElse "", r)(content => s"""
|
|
||||||
|$content<hr/>
|
|
||||||
|View, comment on, or merge it at:<br/>
|
|
||||||
|<a href="$url">$url</a>
|
|
||||||
""".stripMargin)
|
|
||||||
)(recipients(issue))
|
|
||||||
}
|
|
||||||
|
|
||||||
override def addedComment(commentId: Int, content: String, issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
|
|
||||||
Notifier().toNotify(
|
|
||||||
subject(issue, r),
|
|
||||||
message(content, r)(content => s"""
|
|
||||||
|$content<br/>
|
|
||||||
|--<br/>
|
|
||||||
|<a href="${s"${context.baseUrl}/${r.owner}/${r.name}/pull/${issue.issueId}#comment-$commentId"}">View it on GitBucket</a>
|
|
||||||
""".stripMargin)
|
|
||||||
)(recipients(issue))
|
|
||||||
}
|
|
||||||
|
|
||||||
override def merged(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
|
|
||||||
Notifier().toNotify(
|
|
||||||
subject(issue, r),
|
|
||||||
message("merge", r)(content => s"""
|
|
||||||
|$content <a href="${s"${context.baseUrl}/${r.owner}/${r.name}/pull/${issue.issueId}"}">#${issue.issueId}</a>
|
|
||||||
""".stripMargin)
|
|
||||||
)(recipients(issue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Mailer(private val smtp: Smtp) extends Notifier {
|
class Mailer(private val smtp: Smtp) extends Notifier {
|
||||||
private val logger = LoggerFactory.getLogger(classOf[Mailer])
|
private val logger = LoggerFactory.getLogger(classOf[Mailer])
|
||||||
|
|
||||||
def toNotify(subject: String, msg: String)
|
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
|
||||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
|
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
|
||||||
context.loginAccount.foreach { loginAccount =>
|
context.loginAccount.foreach { loginAccount =>
|
||||||
val database = Database()
|
val database = Database()
|
||||||
@@ -163,7 +47,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
val f = Future {
|
val f = Future {
|
||||||
database withSession { session =>
|
database withSession { session =>
|
||||||
recipients(loginAccount)(session) foreach { to =>
|
recipients(loginAccount)(session) foreach { to =>
|
||||||
send(to, subject, msg, loginAccount)
|
send(to, subject, loginAccount, textMsg, htmlMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Notifications Successful."
|
"Notifications Successful."
|
||||||
@@ -175,7 +59,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def send(to: String, subject: String, msg: String, loginAccount: Account): Unit = {
|
def send(to: String, subject: String, loginAccount: Account, textMsg: String, htmlMsg: Option[String] = None): Unit = {
|
||||||
val email = new HtmlEmail
|
val email = new HtmlEmail
|
||||||
email.setHostName(smtp.host)
|
email.setHostName(smtp.host)
|
||||||
email.setSmtpPort(smtp.port.get)
|
email.setSmtpPort(smtp.port.get)
|
||||||
@@ -200,13 +84,16 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
|||||||
}
|
}
|
||||||
email.setCharset("UTF-8")
|
email.setCharset("UTF-8")
|
||||||
email.setSubject(subject)
|
email.setSubject(subject)
|
||||||
email.setHtmlMsg(msg)
|
email.setTextMsg(textMsg)
|
||||||
|
htmlMsg.foreach { msg =>
|
||||||
|
email.setHtmlMsg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
email.addTo(to).send
|
email.addTo(to).send
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
class MockMailer extends Notifier {
|
class MockMailer extends Notifier {
|
||||||
def toNotify(subject: String, msg: String)
|
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
|
||||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
|
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ trait Validations {
|
|||||||
*/
|
*/
|
||||||
def password: Constraint = new Constraint(){
|
def password: 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.matches("[a-zA-Z0-9\\-_.]+")){
|
if(System.getProperty("gitbucket.validate.password") != "false" && !value.matches("[a-zA-Z0-9\\-_.]+")){
|
||||||
Some(s"${name} contains invalid character.")
|
Some(s"${name} contains invalid character.")
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
|||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean)(implicit context: Context): Html = {
|
enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean)(implicit context: Context): Html = {
|
||||||
|
|
||||||
val fileName = filePath.reverse.head.toLowerCase
|
val fileName = filePath.last.toLowerCase
|
||||||
val extension = FileUtil.getExtension(fileName)
|
val extension = FileUtil.getExtension(fileName)
|
||||||
val renderer = PluginRegistry().getRenderer(extension)
|
val renderer = PluginRegistry().getRenderer(extension)
|
||||||
renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context))
|
renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context))
|
||||||
|
|||||||
@@ -1,18 +1,32 @@
|
|||||||
@(plugins: List[gitbucket.core.plugin.PluginInfo])(implicit context: gitbucket.core.controller.Context)
|
@(plugins: List[(gitbucket.core.plugin.PluginInfoBase, Boolean)], info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||||
@gitbucket.core.html.main("Plugins"){
|
@gitbucket.core.html.main("Plugins"){
|
||||||
@gitbucket.core.admin.html.menu("plugins") {
|
@gitbucket.core.admin.html.menu("plugins") {
|
||||||
<h1>Installed plugins</h1>
|
@gitbucket.core.helper.html.information(info)
|
||||||
|
<form action="@context.path/admin/plugins/_reload" method="POST" class="pull-right">
|
||||||
|
<input type="submit" value="Reload plugins" class="btn btn-default">
|
||||||
|
</form>
|
||||||
|
<h1>Plugins</h1>
|
||||||
@if(plugins.size > 0) {
|
@if(plugins.size > 0) {
|
||||||
<ul>
|
<ul>
|
||||||
@plugins.map { plugin =>
|
@plugins.map { case (plugin, enabled) =>
|
||||||
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.pluginVersion</a></li>
|
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.pluginVersion</a></li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@plugins.map { plugin =>
|
@plugins.map { case (plugin, enabled) =>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading strong" id="@plugin.pluginId">@plugin.pluginName</div>
|
<div class="panel-heading strong" id="@plugin.pluginId">
|
||||||
|
@if(enabled){
|
||||||
|
<form action="@{context.path}/admin/plugins/@{plugin.pluginId}/@{plugin.pluginVersion}/_uninstall" method="POST" class="pull-right uninstall-form">
|
||||||
|
<input type="submit" value="Uninstall" class="btn btn-danger btn-sm" style="position: relative; top: -5px; left: 10px;" data-name="@plugin.pluginName">
|
||||||
|
</form>
|
||||||
|
} else {
|
||||||
|
<form action="@{context.path}/admin/plugins/@{plugin.pluginId}/@{plugin.pluginVersion}/_install" method="POST" class="pull-right install-form">
|
||||||
|
<input type="submit" value="Install" class="btn btn-success btn-sm" style="position: relative; top: -5px; left: 10px;" data-name="@plugin.pluginName">
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
@plugin.pluginName
|
||||||
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label class="col-md-2">Id</label>
|
<label class="col-md-2">Id</label>
|
||||||
@@ -38,3 +52,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('.uninstall-form').click(function(e){
|
||||||
|
var name = $(e.target).data('name');
|
||||||
|
return confirm('Uninstall ' + name + '. Are you sure?');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.install-form').click(function(e){
|
||||||
|
var name = $(e.target).data('name');
|
||||||
|
return confirm('Install ' + name + '. Are you sure?');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||||
@import gitbucket.core.util.DatabaseConfig
|
@import gitbucket.core.util.DatabaseConfig
|
||||||
|
@import gitbucket.core.view.helpers
|
||||||
@gitbucket.core.html.main("System settings"){
|
@gitbucket.core.html.main("System settings"){
|
||||||
@gitbucket.core.admin.html.menu("system"){
|
@gitbucket.core.admin.html.menu("system"){
|
||||||
@gitbucket.core.helper.html.information(info)
|
@gitbucket.core.helper.html.information(info)
|
||||||
@@ -344,6 +345,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
*@
|
*@
|
||||||
|
<!--====================================================================-->
|
||||||
|
<!-- AdminLTE SkinName -->
|
||||||
|
<!--====================================================================-->
|
||||||
|
<hr>
|
||||||
|
<label class="strong">
|
||||||
|
AdminLTE skin name
|
||||||
|
</label>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label col-md-3" for="skinName">Skin name</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<select id="skinName" name="skinName">
|
||||||
|
@Seq(
|
||||||
|
"skin-black",
|
||||||
|
"skin-black-light",
|
||||||
|
"skin-blue",
|
||||||
|
"skin-blue-light",
|
||||||
|
"skin-green",
|
||||||
|
"skin-green-light",
|
||||||
|
"skin-purple",
|
||||||
|
"skin-purple-light",
|
||||||
|
"skin-red",
|
||||||
|
"skin-red-light",
|
||||||
|
"skin-yellow",
|
||||||
|
"skin-yellow-light",
|
||||||
|
).map{ skin =>
|
||||||
|
<option @if(skin == context.settings.skinName){selected}>@skin</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="align-right" style="margin-top: 20px;">
|
<div class="align-right" style="margin-top: 20px;">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
} else {
|
} else {
|
||||||
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
|
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
|
||||||
@userRepositories.zipWithIndex.map { case (repository, i) =>
|
@userRepositories.zipWithIndex.map { case (repository, i) =>
|
||||||
<li class="menu-item-hover">
|
<li class="repo-link menu-item-hover">
|
||||||
@if(repository.owner == context.loginAccount.get.userName){
|
@if(repository.owner == context.loginAccount.get.userName){
|
||||||
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
|
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
|
||||||
} else {
|
} else {
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
} else {
|
} else {
|
||||||
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
|
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
|
||||||
@recentRepositories.zipWithIndex.map { case (repository, i) =>
|
@recentRepositories.zipWithIndex.map { case (repository, i) =>
|
||||||
<li class="menu-item-hover">
|
<li class="repo-link menu-item-hover">
|
||||||
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span>@repository.owner/<span class="strong">@repository.name</span></span></a>
|
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span>@repository.owner/<span class="strong">@repository.name</span></span></a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
@(title: String)(implicit context: gitbucket.core.controller.Context)
|
@(title: String, e: Option[Throwable]=None)(implicit context: gitbucket.core.controller.Context)
|
||||||
@gitbucket.core.html.main("Error"){
|
@gitbucket.core.html.main("Error"){
|
||||||
<div class="content-wrapper main-center">
|
<div class="content-wrapper main-center">
|
||||||
<div class="content body">
|
<div class="content body">
|
||||||
<h1>@title</h1>
|
<h1>@title</h1>
|
||||||
|
@if(context.loginAccount.map{_.isAdmin}.getOrElse(false)){
|
||||||
|
@e.map { ex =>
|
||||||
|
<h2>@ex.getMessage</h2>
|
||||||
|
<table class="table table-condensed table-striped table-hover">
|
||||||
|
<tbody>
|
||||||
|
@ex.getStackTrace.map{ st =>
|
||||||
|
<tr><td>@st</td></tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
<div>Please contact your administrator.</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
<div class="input-group" style="margin-bottom: 0px;">
|
<div class="input-group" style="margin-bottom: 0px;">
|
||||||
@html
|
@html
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
|
<span id="@copyButtonId" class="btn btn-sm btn-default" @if(style.nonEmpty){style="@style"}
|
||||||
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
|
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
<span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
|
<span id="@copyButtonId" class="btn btn-sm btn-default" @if(style.nonEmpty){style="@style"}
|
||||||
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
|
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
if (document.queryCommandSupported('copy')) {
|
if (document.queryCommandSupported('copy')) {
|
||||||
var title = $('#@copyButtonId').attr('title');
|
var title = $('#@copyButtonId').attr('title');
|
||||||
$('#@copyButtonId').tooltip({
|
$('#@copyButtonId').tooltip({
|
||||||
@* if default container is used then 2 lines tooltip text is displayd because tooptip width is narrow. *@
|
@* if default container is used then 2 lines tooltip text is displayed because tooptip width is narrow. *@
|
||||||
container: 'body'
|
container: 'body'
|
||||||
});
|
});
|
||||||
$('#@copyButtonId').on('click', function() {
|
$('#@copyButtonId').on('click', function() {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
@import gitbucket.core.view.helpers
|
@import gitbucket.core.view.helpers
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
|
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
|
||||||
<id>tag:@context.host,2013:gitbucket</id>
|
<id>tag:@context.host,2013:gitbucket</id>
|
||||||
<title>Gitbucket's activities</title>
|
<title>GitBucket's activities</title>
|
||||||
<link type="application/atom+xml" rel="self" href="@context.baseUrl/activities.atom"/>
|
<link type="application/atom+xml" rel="self" href="@context.baseUrl/activities.atom"/>
|
||||||
<author>
|
<author>
|
||||||
<name>Gitbucket</name>
|
<name>GitBucket</name>
|
||||||
<uri>@context.baseUrl</uri>
|
<uri>@context.baseUrl</uri>
|
||||||
</author>
|
</author>
|
||||||
<updated>@helpers.datetimeRFC3339(if(activities.isEmpty) new java.util.Date else activities.map(_.activityDate).max)</updated>
|
<updated>@helpers.datetimeRFC3339(if(activities.isEmpty) new java.util.Date else activities.map(_.activityDate).max)</updated>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<div class="tabbable">
|
<div class="tabbable">
|
||||||
<ul class="nav nav-tabs fill-width" style="margin-bottom: 10px;">
|
<ul class="nav nav-tabs fill-width" style="margin-bottom: 10px;">
|
||||||
<li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li>
|
<li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li>
|
||||||
<li><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li>
|
<li><a href="#tab@(uid + 1)" data-toggle="tab" id="preview@uid">Preview</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane active" style="margin-top: 4px;" id="tab@uid">
|
<div class="tab-pane active" style="margin-top: 4px;" id="tab@uid">
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
generateScript = !enableWikiLink
|
generateScript = !enableWikiLink
|
||||||
)(textarea)
|
)(textarea)
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane" id="tab@(uid+1)">
|
<div class="tab-pane" id="tab@(uid + 1)">
|
||||||
<div class="markdown-body" id="preview-area@uid">
|
<div class="markdown-body" id="preview-area@uid">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
@(content: String, commentId: Int,
|
@(content: String, commentId: 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)
|
||||||
<span id="error-edit-content-@commentId" class="error"></span>
|
<span id="error-edit-content-@commentId" class="error"></span>
|
||||||
@gitbucket.core.helper.html.attached(repository, "issues"){
|
@gitbucket.core.helper.html.preview(
|
||||||
<textarea id="edit-content-@commentId" class="form-control">@content</textarea>
|
repository = repository,
|
||||||
}
|
content = content,
|
||||||
<div>
|
enableWikiLink = false,
|
||||||
<input type="button" id="cancel-comment-@commentId" class="btn btn-danger" value="Cancel"/>
|
enableRefsLink = true,
|
||||||
<input type="button" id="update-comment-@commentId" class="btn btn-default pull-right" value="Update comment"/>
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
|
hasWritePermission = true,
|
||||||
|
completionContext = "issues",
|
||||||
|
style = "",
|
||||||
|
elastic = true,
|
||||||
|
tabIndex = 1
|
||||||
|
)
|
||||||
|
<div class="pull-right">
|
||||||
|
<input type="button" id="cancel-comment-@commentId" class="btn btn-default" value="Cancel"/>
|
||||||
|
<input type="button" id="update-comment-@commentId" class="btn btn-success" value="Update comment"/>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
@@ -17,13 +27,14 @@ $(function(){
|
|||||||
};
|
};
|
||||||
|
|
||||||
$('#update-comment-@commentId').click(function(){
|
$('#update-comment-@commentId').click(function(){
|
||||||
|
var content = $(this).parent().parent().find('textarea[name=content]').val();
|
||||||
$('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
|
$('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '@context.path/@repository.owner/@repository.name/issue_comments/edit/@commentId',
|
url: '@context.path/@repository.owner/@repository.name/issue_comments/edit/@commentId',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
issueId : 0, // TODO
|
issueId : 0, // TODO
|
||||||
content : $('#edit-content-@commentId').val()
|
content : content
|
||||||
}
|
}
|
||||||
}).done(
|
}).done(
|
||||||
callback
|
callback
|
||||||
|
|||||||
@@ -1,27 +1,37 @@
|
|||||||
@(content: Option[String], issueId: Int,
|
@(content: Option[String], issueId: 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)
|
||||||
@gitbucket.core.helper.html.attached(repository, "issues"){
|
@gitbucket.core.helper.html.preview(
|
||||||
<textarea id="edit-content" class="form-control">@content.getOrElse("")</textarea>
|
repository = repository,
|
||||||
}
|
content = content.getOrElse(""),
|
||||||
<div>
|
enableWikiLink = false,
|
||||||
<input type="button" id="cancel-issue" class="btn btn-danger" value="Cancel"/>
|
enableRefsLink = true,
|
||||||
<input type="button" id="update-issue" class="btn btn-default pull-right" value="Update comment"/>
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
|
hasWritePermission = true,
|
||||||
|
completionContext = "issues",
|
||||||
|
style = "",
|
||||||
|
elastic = true,
|
||||||
|
tabIndex = 1
|
||||||
|
)
|
||||||
|
<div class="pull-right">
|
||||||
|
<input type="button" id="cancel-issue" class="btn btn-default" value="Cancel"/>
|
||||||
|
<input type="button" id="update-issue" class="btn btn-success" value="Update comment"/>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
var callback = function(data){
|
var callback = function(data){
|
||||||
$('#update, #cancel').removeAttr('disabled');
|
$('#update, #cancel').removeAttr('disabled');
|
||||||
$('#issueContent').empty().html(data.content);
|
$('#issueContent').empty().html(data.content);
|
||||||
|
prettyPrint();
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#update-issue').click(function(){
|
$('#update-issue').click(function(){
|
||||||
|
var content = $(this).parent().parent().find('textarea[name=content]').val();
|
||||||
$('#update, #cancel').attr('disabled', 'disabled');
|
$('#update, #cancel').attr('disabled', 'disabled');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '@context.path/@repository.owner/@repository.name/issues/edit/@issueId',
|
url: '@context.path/@repository.owner/@repository.name/issues/edit/@issueId',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: { content : content }
|
||||||
content : $('#edit-content').val()
|
|
||||||
}
|
|
||||||
}).done(
|
}).done(
|
||||||
callback
|
callback
|
||||||
).fail(function(req) {
|
).fail(function(req) {
|
||||||
|
|||||||
@@ -148,8 +148,8 @@
|
|||||||
<input type="hidden" name="assignedUserName" value=""/>
|
<input type="hidden" name="assignedUserName" value=""/>
|
||||||
}
|
}
|
||||||
@issue.map { issue =>
|
@issue.map { issue =>
|
||||||
@gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebar =>
|
@gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebarComponent =>
|
||||||
@sidebar(issue, repository, context)
|
@sidebarComponent(issue, repository, context)
|
||||||
}
|
}
|
||||||
<hr/>
|
<hr/>
|
||||||
<div style="margin-bottom: 14px;">
|
<div style="margin-bottom: 14px;">
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<link href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/>
|
<link href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/>
|
||||||
<link href="@helpers.assets("/vendors/facebox/facebox.css")" rel="stylesheet"/>
|
<link href="@helpers.assets("/vendors/facebox/facebox.css")" rel="stylesheet"/>
|
||||||
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/AdminLTE.min.css")" rel="stylesheet">
|
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/AdminLTE.min.css")" rel="stylesheet">
|
||||||
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/skins/skin-blue.min.css")" rel="stylesheet">
|
<link href="@helpers.assets(s"/vendors/AdminLTE-2.3.11/css/skins/${context.settings.skinName}.min.css")" rel="stylesheet">
|
||||||
<link href="@helpers.assets("/vendors/font-awesome-4.6.3/css/font-awesome.min.css")" rel="stylesheet">
|
<link href="@helpers.assets("/vendors/font-awesome-4.6.3/css/font-awesome.min.css")" rel="stylesheet">
|
||||||
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.css")" rel="stylesheet">
|
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.css")" rel="stylesheet">
|
||||||
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
|
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
|
||||||
@@ -42,11 +42,11 @@
|
|||||||
}
|
}
|
||||||
<script src="@helpers.assets("/vendors/AdminLTE-2.3.11/js/app.js")" type="text/javascript"></script>
|
<script src="@helpers.assets("/vendors/AdminLTE-2.3.11/js/app.js")" type="text/javascript"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="skin-blue page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
|
<body class="@context.settings.skinName page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<header class="main-header">
|
<header class="main-header">
|
||||||
<a href="@context.path/" class="logo">
|
<a href="@context.path/" class="logo">
|
||||||
<img src="@helpers.assets("/common/images/gitbucket.png")" style="width: 24px; height: 24px; display: inline;"/>
|
<img src="@helpers.assets("/common/images/gitbucket.svg")" style="width: 24px; height: 24px; display: inline;"/>
|
||||||
GitBucket
|
GitBucket
|
||||||
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
|
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -74,6 +74,11 @@
|
|||||||
@gitbucket.core.helper.html.information(info)
|
@gitbucket.core.helper.html.information(info)
|
||||||
@gitbucket.core.helper.html.error(error)
|
@gitbucket.core.helper.html.error(error)
|
||||||
<div class="head">
|
<div class="head">
|
||||||
|
<div class="pull-right">
|
||||||
|
@gitbucket.core.plugin.PluginRegistry().getRepositoryHeaders.map { repositoryHeaderComponent =>
|
||||||
|
@repositoryHeaderComponent(repository, context)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@gitbucket.core.helper.html.repositoryicon(repository, true)
|
@gitbucket.core.helper.html.repositoryicon(repository, true)
|
||||||
<a href="@helpers.url(repository.owner)">@repository.owner</a> / <a href="@helpers.url(repository)" class="strong">@repository.name</a>
|
<a href="@helpers.url(repository.owner)">@repository.owner</a> / <a href="@helpers.url(repository)" class="strong">@repository.name</a>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
@status.statusesAndRequired.map { case (status, required) =>
|
@status.statusesAndRequired.map { case (status, required) =>
|
||||||
<div class="build-status-item">
|
<div class="build-status-item">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@if(required){ <span class="label">Required</span> }
|
@if(required){ <span class="label label-success">Required</span> }
|
||||||
@status.targetUrl.map { url => <a href="@url">Details</a> }
|
@status.targetUrl.map { url => <a href="@url">Details</a> }
|
||||||
</div>
|
</div>
|
||||||
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>
|
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if(content.viewType == "text"){
|
@if(content.viewType == "text"){
|
||||||
@defining(helpers.isRenderable(pathList.reverse.head)){ isRenderable =>
|
@defining(helpers.isRenderable(pathList.last)){ isRenderable =>
|
||||||
@if(!isBlame && isRenderable) {
|
@if(!isBlame && isRenderable) {
|
||||||
<div class="box-content-bottom markdown-body" style="padding-left: 20px; padding-right: 20px;">
|
<div class="box-content-bottom markdown-body" style="padding-left: 20px; padding-right: 20px;">
|
||||||
@helpers.renderMarkup(pathList, content.content.get, branch, repository, false, false, true)
|
@helpers.renderMarkup(pathList, content.content.get, branch, repository, false, false, true)
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||||
page: Int,
|
page: Int,
|
||||||
hasNext: Boolean,
|
hasNext: Boolean,
|
||||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
hasWritePermission: Boolean,
|
||||||
|
getStatuses: String => List[gitbucket.core.model.CommitStatus],
|
||||||
|
getSummary: List[gitbucket.core.model.CommitStatus] => (gitbucket.core.model.CommitState, String))(implicit context: gitbucket.core.controller.Context)
|
||||||
@import gitbucket.core.view.helpers
|
@import gitbucket.core.view.helpers
|
||||||
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@gitbucket.core.html.menu("files", repository){
|
@gitbucket.core.html.menu("files", repository){
|
||||||
@@ -63,6 +65,30 @@
|
|||||||
}
|
}
|
||||||
@helpers.user(commit.committerName, commit.committerEmailAddress, "username")
|
@helpers.user(commit.committerName, commit.committerEmailAddress, "username")
|
||||||
<span class="muted">committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span>
|
<span class="muted">committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span>
|
||||||
|
@defining({
|
||||||
|
val statuses = getStatuses(commit.id)
|
||||||
|
val (summary, summaryText) = getSummary(statuses)
|
||||||
|
(statuses, summary, summaryText)
|
||||||
|
}){ case (statuses, summaryState, summaryText) =>
|
||||||
|
@if(statuses.nonEmpty){
|
||||||
|
@helpers.commitStateIcon(summaryState)
|
||||||
|
<strong class="text-@{summaryState.name}">@helpers.commitStateText(summaryState, commit.id)</strong>
|
||||||
|
<span class="text-@{summaryState.name}">- @summaryText checks</span>
|
||||||
|
<a href="#" class="toggle-check">Show all checks</a>
|
||||||
|
<div style="display: none;">
|
||||||
|
@statuses.map{ status =>
|
||||||
|
<div class="build-status-item">
|
||||||
|
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>
|
||||||
|
<strong>@status.context</strong>
|
||||||
|
@status.description.map { desc => <span class="muted">- @desc</span> }
|
||||||
|
<span>
|
||||||
|
@status.targetUrl.map { url => - <a href="@url">Details</a> }
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,5 +112,19 @@
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$('.toggle-check').click(function(){
|
||||||
|
var div = $(this).next('div');
|
||||||
|
if(div.is(':visible')){
|
||||||
|
$(this).text('Show all checks');
|
||||||
|
} else {
|
||||||
|
$(this).text('Hide all checks');
|
||||||
|
}
|
||||||
|
div.toggle();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,22 @@
|
|||||||
@(content: String, commentId: Int,
|
@(content: String, commentId: 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)
|
||||||
<span class="error-edit-content-@commentId error"></span>
|
<span class="error-edit-content-@commentId error"></span>
|
||||||
@gitbucket.core.helper.html.attached(repository, "issues"){
|
@gitbucket.core.helper.html.preview(
|
||||||
<textarea style="height: 100px;" id="edit-content-@commentId" class="form-control">@content</textarea>
|
repository = repository,
|
||||||
}
|
content = content,
|
||||||
<div>
|
enableWikiLink = false,
|
||||||
<input type="button" class="cancel-comment-@commentId btn btn-small btn-danger" value="Cancel"/>
|
enableRefsLink = true,
|
||||||
<input type="button" class="update-comment-@commentId btn btn-small btn-default pull-right" value="Update comment"/>
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
|
hasWritePermission = true,
|
||||||
|
completionContext = "issues",
|
||||||
|
style = "",
|
||||||
|
elastic = true,
|
||||||
|
tabIndex = 1
|
||||||
|
)
|
||||||
|
<div class="pull-right">
|
||||||
|
<input type="button" class="cancel-comment-@commentId btn btn-small btn-default" value="Cancel"/>
|
||||||
|
<input type="button" class="update-comment-@commentId btn btn-small btn-success" value="Update comment"/>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
@@ -19,13 +29,14 @@ $(function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).on('click', '.update-comment-@commentId', function(){
|
$(document).on('click', '.update-comment-@commentId', function(){
|
||||||
|
var content = $(this).parent().parent().find('textarea[name=content]').val();
|
||||||
$box = $(this).closest('.commit-comment-box');
|
$box = $(this).closest('.commit-comment-box');
|
||||||
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).attr('disabled', 'disabled');
|
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).attr('disabled', 'disabled');
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: '@context.path/@repository.owner/@repository.name/commit_comments/edit/@commentId',
|
url: '@context.path/@repository.owner/@repository.name/commit_comments/edit/@commentId',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
content : $('#edit-content-@commentId', $box).val()
|
content : content
|
||||||
}
|
}
|
||||||
}).done(
|
}).done(
|
||||||
curriedCallback($box)
|
curriedCallback($box)
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
</table>
|
</table>
|
||||||
@readme.map { case(filePath, content) =>
|
@readme.map { case(filePath, content) =>
|
||||||
<div id="readme" class="panel panel-default">
|
<div id="readme" class="panel panel-default">
|
||||||
<div class="panel-heading strong">@filePath.reverse.head</div>
|
<div class="panel-heading strong">@filePath.last</div>
|
||||||
<div class="panel-body markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div>
|
<div class="panel-body markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@repository.tags.reverse.map { tag =>
|
@repository.tags.reverseMap { tag =>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="@helpers.url(repository)/tree/@helpers.encodeRefName(tag.name)">@tag.name</a></td>
|
<td><a href="@helpers.url(repository)/tree/@helpers.encodeRefName(tag.name)">@tag.name</a></td>
|
||||||
<td>@gitbucket.core.helper.html.datetimeago(tag.time, false)</td>
|
<td>@gitbucket.core.helper.html.datetimeago(tag.time, false)</td>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
/* Common */
|
/* Common */
|
||||||
/****************************************************************************/
|
/****************************************************************************/
|
||||||
|
.wrapper { position: static; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
@@ -562,17 +564,6 @@ pre.commit-description {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
#repository-url-copy {
|
|
||||||
/*height: 18px;*/
|
|
||||||
padding-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#repository-url-copy > i {
|
|
||||||
margin-left: -4px;
|
|
||||||
margin-right: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ul#commit-file-list {
|
ul#commit-file-list {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
|
|||||||
39
src/main/webapp/assets/common/images/gitbucket.svg
Normal file
39
src/main/webapp/assets/common/images/gitbucket.svg
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="316.000000pt" height="329.000000pt" viewBox="0 0 316.000000 329.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,329.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#303030" stroke="none">
|
||||||
|
<path d="M1230 3079 c-448 -28 -840 -128 -971 -246 -50 -45 -50 -71 0 -116
|
||||||
|
158 -142 707 -256 1241 -257 l106 0 54 56 c118 121 213 124 326 12 l52 -50
|
||||||
|
133 17 c338 42 615 127 718 220 53 48 53 72 0 120 -101 91 -391 181 -704 219
|
||||||
|
-283 34 -656 44 -955 25z"/>
|
||||||
|
<path d="M232 2484 c4 -16 70 -429 148 -919 79 -490 147 -895 152 -902 17 -21
|
||||||
|
258 -114 416 -159 130 -37 351 -84 400 -84 6 0 12 6 12 13 0 6 -174 185 -386
|
||||||
|
397 -395 394 -424 428 -424 500 0 72 30 107 383 459 301 300 348 343 366 335
|
||||||
|
11 -5 87 -74 170 -152 l151 -144 0 -59 c0 -73 24 -124 77 -167 l38 -30 0 -309
|
||||||
|
0 -309 -38 -34 c-102 -89 -102 -229 -1 -307 31 -23 49 -28 108 -31 82 -4 115
|
||||||
|
11 162 73 22 30 29 51 32 100 4 65 -2 83 -53 154 l-25 34 0 266 c0 267 3 311
|
||||||
|
23 311 5 0 54 -44 109 -97 96 -95 99 -100 108 -157 17 -103 89 -166 190 -166
|
||||||
|
76 0 135 39 174 117 32 64 16 143 -43 206 -41 44 -73 57 -149 57 l-68 0 -127
|
||||||
|
121 c-107 101 -127 126 -132 157 -12 72 -16 82 -47 117 -39 45 -77 65 -122 65
|
||||||
|
-19 0 -45 4 -58 9 -13 5 -99 82 -193 170 l-170 160 -71 1 c-106 0 -360 27
|
||||||
|
-524 55 -228 40 -385 86 -579 172 -11 5 -13 0 -9 -23z"/>
|
||||||
|
<path d="M2785 2454 c-86 -36 -280 -88 -425 -114 -69 -12 -129 -26 -135 -31
|
||||||
|
-6 -6 93 -112 275 -294 156 -156 285 -283 287 -281 2 2 19 158 38 347 19 189
|
||||||
|
37 356 40 372 6 34 -2 34 -80 1z"/>
|
||||||
|
<path d="M2552 697 c-78 -78 -142 -146 -142 -150 0 -4 7 -7 16 -7 22 0 230 89
|
||||||
|
242 103 9 11 35 187 29 193 -2 2 -67 -61 -145 -139z"/>
|
||||||
|
<path d="M560 455 c0 -34 40 -95 84 -129 61 -46 197 -104 317 -135 169 -43
|
||||||
|
321 -62 534 -68 l190 -5 -70 71 c-68 69 -71 71 -120 71 -189 0 -551 79 -806
|
||||||
|
176 -64 24 -119 44 -123 44 -3 0 -6 -11 -6 -25z"/>
|
||||||
|
<path d="M2620 456 c-53 -28 -250 -97 -360 -126 l-114 -29 -78 -78 c-42 -42
|
||||||
|
-76 -79 -74 -81 9 -8 244 41 334 69 164 53 267 114 308 185 24 40 31 74 17 74
|
||||||
|
-5 -1 -19 -7 -33 -14z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -443,7 +443,7 @@ function string_score(string, word) {
|
|||||||
* @param word {String} search word
|
* @param word {String} search word
|
||||||
* @param strings {Array[String]} search targets
|
* @param strings {Array[String]} search targets
|
||||||
* @param limit {Integer} result limit
|
* @param limit {Integer} result limit
|
||||||
* @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Interger] matchng positions"}]}
|
* @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Integer] matching positions"}]}
|
||||||
*/
|
*/
|
||||||
function string_score_sort(word, strings, limit){
|
function string_score_sort(word, strings, limit){
|
||||||
var ret = [], i=0, l = (word==="")?Math.min(strings.length, limit):strings.length;
|
var ret = [], i=0, l = (word==="")?Math.min(strings.length, limit):strings.length;
|
||||||
@@ -466,7 +466,7 @@ function string_score_sort(word, strings, limit){
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* highlight by result.
|
* highlight by result.
|
||||||
* @param score {string:"string target string", matchingPositions:"Array[Interger] matchng positions"}
|
* @param score {string:"string target string", matchingPositions:"Array[Integer] matching positions"}
|
||||||
* @param highlight tag ex: '<b>'
|
* @param highlight tag ex: '<b>'
|
||||||
* @return array of highlighted html elements.
|
* @return array of highlighted html elements.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
|
|||||||
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
|
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
|
||||||
// other one can update
|
// other one can update
|
||||||
val tester2 = generateNewAccount("tester2")
|
val tester2 = generateNewAccount("tester2")
|
||||||
val time2 = new java.util.Date();
|
val time2 = new java.util.Date()
|
||||||
val id2 = createCommitStatus(
|
val id2 = createCommitStatus(
|
||||||
userName = fixture1.userName,
|
userName = fixture1.userName,
|
||||||
repositoryName = fixture1.repositoryName,
|
repositoryName = fixture1.repositoryName,
|
||||||
@@ -72,4 +72,4 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
|
|||||||
val id = generateFixture1(tester:Account)
|
val id = generateFixture1(tester:Account)
|
||||||
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
|
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class MergeServiceSpec extends FunSpec {
|
|||||||
def initRepository(owner:String, name:String): File = {
|
def initRepository(owner:String, name:String): File = {
|
||||||
val dir = createTestRepository(getRepositoryDir(owner, name))
|
val dir = createTestRepository(getRepositoryDir(owner, name))
|
||||||
using(Git.open(dir)){ git =>
|
using(Git.open(dir)){ git =>
|
||||||
createFile(git, s"refs/heads/master", "test.txt", "hoge" )
|
createFile(git, "refs/heads/master", "test.txt", "hoge" )
|
||||||
git.branchCreate().setStartPoint(s"refs/heads/master").setName(s"refs/pull/${issueId}/head").call()
|
git.branchCreate().setStartPoint(s"refs/heads/master").setName(s"refs/pull/${issueId}/head").call()
|
||||||
}
|
}
|
||||||
dir
|
dir
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ class PullRequestServiceSpec extends FunSpec with ServiceSpecBase
|
|||||||
describe("PullRequestService.getPullRequestFromBranch") {
|
describe("PullRequestService.getPullRequestFromBranch") {
|
||||||
it("""should
|
it("""should
|
||||||
|return pull request if exists pull request from `branch` to `defaultBranch` and not closed.
|
|return pull request if exists pull request from `branch` to `defaultBranch` and not closed.
|
||||||
|return pull request if exists pull request from `branch` to othre branch and not closed.
|
|return pull request if exists pull request from `branch` to other branch and not closed.
|
||||||
|return None if all pull request is closed""".stripMargin.trim) { withTestDB { implicit se =>
|
|return None if all pull request is closed""".stripMargin.trim) { withTestDB { implicit se =>
|
||||||
generateNewUserWithDBRepository("user1", "repo1")
|
generateNewUserWithDBRepository("user1", "repo1")
|
||||||
generateNewUserWithDBRepository("user1", "repo2")
|
generateNewUserWithDBRepository("user1", "repo2")
|
||||||
generateNewUserWithDBRepository("user2", "repo1")
|
generateNewUserWithDBRepository("user2", "repo1")
|
||||||
generateNewPullRequest("user1/repo1/master", "user1/repo1/head2") // not target branch
|
generateNewPullRequest("user1/repo1/master", "user1/repo1/head2") // not target branch
|
||||||
generateNewPullRequest("user1/repo1/head1", "user1/repo1/master") // not target branch ( swap from, to )
|
generateNewPullRequest("user1/repo1/head1", "user1/repo1/master") // not target branch ( swap from, to )
|
||||||
generateNewPullRequest("user1/repo1/master", "user2/repo1/head1") // othre user
|
generateNewPullRequest("user1/repo1/master", "user2/repo1/head1") // other user
|
||||||
generateNewPullRequest("user1/repo1/master", "user1/repo2/head1") // othre repository
|
generateNewPullRequest("user1/repo1/master", "user1/repo2/head1") // other repository
|
||||||
val r1 = swap(generateNewPullRequest("user1/repo1/master2", "user1/repo1/head1"))
|
val r1 = swap(generateNewPullRequest("user1/repo1/master2", "user1/repo1/head1"))
|
||||||
val r2 = swap(generateNewPullRequest("user1/repo1/master", "user1/repo1/head1"))
|
val r2 = swap(generateNewPullRequest("user1/repo1/master", "user1/repo1/head1"))
|
||||||
val r3 = swap(generateNewPullRequest("user1/repo1/master4", "user1/repo1/head1"))
|
val r3 = swap(generateNewPullRequest("user1/repo1/master4", "user1/repo1/head1"))
|
||||||
@@ -31,4 +31,4 @@ class PullRequestServiceSpec extends FunSpec with ServiceSpecBase
|
|||||||
assert(getPullRequestFromBranch("user1", "repo1", "head1", "master") == None)
|
assert(getPullRequestFromBranch("user1", "repo1", "head1", "master") == None)
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ object GitSpecUtil {
|
|||||||
throw new RuntimeException("conflict!")
|
throw new RuntimeException("conflict!")
|
||||||
}
|
}
|
||||||
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
|
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
|
||||||
val committer = mergeTipCommit.getCommitterIdent;
|
val committer = mergeTipCommit.getCommitterIdent
|
||||||
// creates merge commit
|
// creates merge commit
|
||||||
val mergeCommit = new CommitBuilder()
|
val mergeCommit = new CommitBuilder()
|
||||||
mergeCommit.setTreeId(merger.getResultTreeId)
|
mergeCommit.setTreeId(merger.getResultTreeId)
|
||||||
|
|||||||
@@ -118,7 +118,10 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
|
|||||||
useSMTP = false,
|
useSMTP = false,
|
||||||
smtp = None,
|
smtp = None,
|
||||||
ldapAuthentication = false,
|
ldapAuthentication = false,
|
||||||
ldap = None)
|
ldap = None,
|
||||||
|
skinName = "skin-blue",
|
||||||
|
debug = false
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter to test AvatarImageProviderImpl.
|
* Adapter to test AvatarImageProviderImpl.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class HelpersSpec extends FunSpec with MockitoSugar {
|
|||||||
assert(after == """Example Project. <a href="http://example.com">http://example.com</a>""")
|
assert(after == """Example Project. <a href="http://example.com">http://example.com</a>""")
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should convert a mulitple links within text") {
|
it("should convert a multiple links within text") {
|
||||||
val before = "Example Project. http://example.com. (See also https://github.com/)"
|
val before = "Example Project. http://example.com. (See also https://github.com/)"
|
||||||
val after = detectAndRenderLinks(before, repository)
|
val after = detectAndRenderLinks(before, repository)
|
||||||
assert(after == """Example Project. <a href="http://example.com">http://example.com</a>. (See also <a href="https://github.com/">https://github.com/</a>)""")
|
assert(after == """Example Project. <a href="http://example.com">http://example.com</a>. (See also <a href="https://github.com/">https://github.com/</a>)""")
|
||||||
|
|||||||
Reference in New Issue
Block a user