Compare commits

...

85 Commits

Author SHA1 Message Date
Naoki Takezoe
eafdd0fb61 Update README.md 2017-08-05 11:45:45 +09:00
Naoki Takezoe
a14394dd88 Gist, Emoji and Pages plugin are disabled in default 2017-07-31 11:59:19 +09:00
Naoki Takezoe
e28b0394ec Update README.md 2017-07-31 09:35:43 +09:00
Naoki Takezoe
11903e9728 Update README.md 2017-07-31 09:34:04 +09:00
Naoki Takezoe
0e498d1a81 Add a special rule for pages-plugin's url 2017-07-31 01:29:13 +09:00
kenji yoshida
0aafc16648 Merge pull request #1657 from uli-heller/scala-2.12.3
scala-2.12.3
2017-07-29 16:59:09 +09:00
Uli Heller
58246a91b0 scala-2.12.3 2017-07-28 11:51:42 +02:00
Naoki Takezoe
53ae59271a Add pages plugin 2017-07-27 07:34:42 +09:00
Naoki Takezoe
1a2e4e72bd Update bundle plugin information 2017-07-26 00:53:23 +09:00
Naoki Takezoe
2a83c1b9ba Add null check 2017-07-25 12:35:41 +09:00
Naoki Takezoe
5b245978d4 Update README.md 2017-07-25 02:14:00 +09:00
Naoki Takezoe
1463cee2a4 Log bundle plugins downloading 2017-07-24 20:29:20 +09:00
Naoki Takezoe
f4b910c268 Download bundle plugins from plugins.json 2017-07-24 20:26:30 +09:00
Naoki Takezoe
d39c371635 Bundling plugins is executed in packaging of the executable war file 2017-07-24 14:26:45 +09:00
Naoki Takezoe
19b3c2a265 Update bundled plugins 2017-07-23 01:05:43 +09:00
Naoki Takezoe
28efc38fc4 Fixup 2017-07-22 16:00:51 +09:00
Naoki Takezoe
6a7e948e89 Tweak copy button style 2017-07-22 15:40:35 +09:00
Naoki Takezoe
645d23b531 (refs #1635)Fix scrollbar issue 2017-07-22 15:08:26 +09:00
Naoki Takezoe
50ae5bb7cc Bump to blocking-slick 0.0.9 (Slick 3.2.1) 2017-07-22 02:36:39 +09:00
Naoki Takezoe
38728910cb Merge pull request #1652 from aruneko/support_ed25519_key
support ed25519 key
2017-07-22 01:42:04 +09:00
Aruneko
e2f695777d support ed25519 key 2017-07-21 22:36:03 +09:00
Naoki Takezoe
06a98d0f94 (refs #1644)Bump markedj to 1.0.13 2017-07-19 03:16:04 +09:00
Naoki Takezoe
944cbf04ed Enhance --plugin_dir option behavior
It became an option to specify an extra plugin jars directory, not to overwrite PLUGIN_HOME.
2017-07-18 13:04:32 +09:00
Naoki Takezoe
84891abc04 Merge pull request #1648 from gitbucket/travis-java9-build
Test for oraclejdk9 build on Travis
2017-07-18 01:37:03 +09:00
Naoki Takezoe
a848bb43b6 Scraping JDK file name 2017-07-18 01:13:49 +09:00
Naoki Takezoe
d57a2e5eae Test for oraclejdk9 build 2017-07-18 00:48:51 +09:00
Naoki Takezoe
d1adcb876d Merge pull request #1647 from gitbucket/feature/preview-comment-edit
Make preview-able comment editing forms
2017-07-17 23:15:12 +09:00
Naoki Takezoe
8505d8ae0e Change position of buttons in comment editing forms 2017-07-17 22:44:53 +09:00
Naoki Takezoe
3a567cb4a7 Make preview-able comment editing form 2017-07-17 22:35:53 +09:00
Naoki Takezoe
20dbba116a Bump gist-plugin to 4.9.1 2017-07-17 19:52:42 +09:00
Naoki Takezoe
f7d7b5bd7b Call prettyprint() after issue content editing 2017-07-17 14:47:30 +09:00
Naoki Takezoe
dd15420f2c (refs #1644)Bump markedj to 1.0.13-SNAPSHOT 2017-07-17 14:43:05 +09:00
Naoki Takezoe
31945533c2 (refs #1645)Ignore tagged object is not commit 2017-07-17 03:07:54 +09:00
shimamoto
9288e0abe0 Remove notification.md (move to plugins). 2017-07-13 20:35:09 +09:00
Naoki Takezoe
5641fee39a Fix plugin controller handling 2017-07-13 01:52:26 +09:00
Naoki Takezoe
db88458a14 Remove unnecessary semicolon 2017-07-12 18:42:14 +09:00
Naoki Takezoe
3ff89bc648 (refs #1484)Add --plugin_dir option 2017-07-12 18:33:43 +09:00
Naoki Takezoe
61f3d2d513 Fixup 2017-07-12 13:07:41 +09:00
Naoki Takezoe
788f253ad0 Merge pull request #1487 from gitbucket/feature/plugin-hotdeploy
Plugin hot deployment and bundle some plugins
2017-07-12 11:20:27 +09:00
Naoki Takezoe
947d93ddc7 Remove extra information from plugins.json 2017-07-09 02:31:13 +09:00
Naoki Takezoe
74063885b1 Don't check semver for unmanaged plugins 2017-07-09 01:55:00 +09:00
Naoki Takezoe
554fd6d700 Add emoji plugin 2017-07-09 01:49:57 +09:00
Naoki Takezoe
1fb6861565 Add notifications plugin 2017-07-08 20:01:16 +09:00
Naoki Takezoe
6c5350a51b Drop issue and pull request notification because it will be provided as plugin 2017-07-08 19:37:48 +09:00
Naoki Takezoe
00da7e9a82 Merge branch 'master' into feature/plugin-hotdeploy 2017-07-08 14:05:27 +09:00
Naoki Takezoe
e18bed12c0 Update version to 4.15.0-SNAPSHOT 2017-07-08 14:04:50 +09:00
Naoki Takezoe
d2bb7e912f Merge pull request #1638 from gitbucket/feature/repository-header-plugin
Add repositoryHeaderComponent extension point
2017-07-08 14:00:15 +09:00
Naoki Takezoe
73ed69a4ad Add repositoryHeaderComponent extension point 2017-07-08 13:28:44 +09:00
Naoki Takezoe
d8fe6a0a55 Capability of installing from the local repository 2017-07-07 11:47:43 +09:00
Naoki Takezoe
b278bfd159 (refs #1633)Bugfix for --max_file_size parameter 2017-07-07 01:54:20 +09:00
Naoki Takezoe
872beb777f Merge pull request #1634 from kazuki43zoo/gh-1629_add-repo-link
Add repo-link class to repository link at sidebar menu
2017-07-06 13:48:53 +09:00
Naoki Takezoe
aebcf5d183 Make possible to install bundled plugins in default
and use JSON for defining bundled plugin metadata
2017-07-06 03:25:06 +09:00
Kazuki Shimizu
aab9b71901 Add repo-link class to repository link at sidebar menu
Fixes #1629
2017-07-06 00:04:41 +09:00
Naoki Takezoe
9cbab137fc Make possible to install and uninstall bundled plugins 2017-07-05 18:01:03 +09:00
Naoki Takezoe
358bc23931 Fix compilation error 2017-07-05 16:58:28 +09:00
Naoki Takezoe
7396bf0675 Merge branch 'feature/bundle-plugins' into feature/plugin-hotdeploy 2017-07-05 16:42:20 +09:00
Naoki Takezoe
61166c4388 Add enabled flag to plugin info list 2017-07-05 16:40:35 +09:00
Naoki Takezoe
4b9f2c7728 Merge pull request #1628 from uli-heller/jgit-4.8.0.201706111038-r
Updated to jgit-4.8.0.201706111038-r
2017-07-05 10:57:00 +09:00
Naoki Takezoe
6b496bdef2 Merge branch 'master' into jgit-4.8.0.201706111038-r 2017-07-05 02:18:39 +09:00
Naoki Takezoe
0e795f58dd Remove unnecessary semicolon 2017-07-05 02:17:37 +09:00
Naoki Takezoe
e2ac8e29fe Adopt latest version if different versions of same plugin are found 2017-07-05 02:17:02 +09:00
Naoki Takezoe
bc80adc412 Merge branch 'master' into feature/bundle-plugins 2017-07-05 02:00:28 +09:00
Naoki Takezoe
2f634625ea Copy only LFS dir and insert default priorities in forking repository 2017-07-05 00:36:24 +09:00
Naoki Takezoe
d80774d8d0 Release 4.14.1 2017-07-05 00:36:12 +09:00
Naoki Takezoe
ecf3e97518 (refs #1631)Copy repository files directory only if it exists 2017-07-04 23:52:39 +09:00
Naoki Takezoe
3758d1f5ad Fix typo 2017-07-04 23:49:56 +09:00
Uli Heller
3e53008d35 Updated to jgit-4.8.0.201706111038-r 2017-07-04 06:45:48 +02:00
Naoki Takezoe
afde5c2685 (refs #1625)Copy bundled plugins in initialization process 2017-07-02 13:16:18 +09:00
Naoki Takezoe
224c355d44 (refs #1575)Use MaridDB JDBC driver instead of MySQL driver
to solve license issue
2017-07-02 04:53:57 +09:00
Naoki Takezoe
269718bfa6 Merge branch 'kounoike-pr-svg-logo' 2017-07-02 04:32:42 +09:00
KOUNOIKE Yuusuke
d145fdbb23 use svg icon for top-left logo. 2017-07-01 09:32:06 +09:00
KOUNOIKE Yuusuke
a60848b16c add svg icon to solve jaggy image in IE11. it also closes #747 (gray logo) 2017-07-01 09:32:06 +09:00
Naoki Takezoe
45db917ee7 Create plugins directory if it does not exist 2017-05-02 15:27:07 +09:00
Naoki Takezoe
42494ce58a Improve logging 2017-05-02 15:22:25 +09:00
Naoki Takezoe
93c75a3ffd Retry copying plugin jar file if it can't get write lock
to avoid copying during file is creating or updating.
2017-05-02 15:03:47 +09:00
Naoki Takezoe
b9283fb544 Merge branch 'master' into feature/plugin-hotdeploy 2017-05-02 14:21:53 +09:00
Naoki Takezoe
f3271846ea Merge branch 'master' into feature/plugin-hotdeploy 2017-03-23 20:45:52 +09:00
Naoki Takezoe
d39d6691e6 Added Plugin.uninstall() method to cleanup, but remove for now 2017-03-23 20:39:33 +09:00
Naoki Takezoe
832b33f949 Fix error message 2017-03-23 20:07:33 +09:00
Naoki Takezoe
1373a93c75 Check plugin duplication 2017-03-23 20:06:07 +09:00
Naoki Takezoe
b2773ff5b7 Load plugin classes from the copied jar file 2017-03-12 02:32:21 +09:00
Naoki Takezoe
8ee017c3fa Add uninstall plugin button 2017-03-11 22:59:55 +09:00
Naoki Takezoe
11fccf38a6 Automatic plugin reloading 2017-03-11 18:01:43 +09:00
Naoki Takezoe
7e7e45e794 Add the plugin reload button to the plugin list page 2017-03-11 12:13:59 +09:00
Naoki Takezoe
c760af7810 Add adapter filter for plugin controllers 2017-03-11 11:19:57 +09:00
41 changed files with 786 additions and 332 deletions

View File

@@ -29,7 +29,12 @@ matrix:
apt:
packages:
- libaio1
- oracle-java9-installer
before_install:
- cd ~
- JDK9_URL=`curl http://jdk.java.net/9/ | grep "lin64JDK" | grep "tar.gz\"" | sed -e "s/\"/ /g" | awk '{print $5}'`
- wget -O jdk-9_linux-x64_bin.tar.gz $JDK9_URL
- tar -xzf jdk-9_linux-x64_bin.tar.gz
- cd -
script:
# https://github.com/sbt/sbt/pull/2951
- git clone https://github.com/retronym/java9-rt-export
@@ -37,7 +42,9 @@ matrix:
- git checkout 1019a2873d057dd7214f4135e84283695728395d
- jdk_switcher use oraclejdk8
- sbt package
- jdk_switcher use oraclejdk9
# - jdk_switcher use oraclejdk9
- export JAVA_HOME=~/jdk-9
- PATH=$JAVA_HOME/bin:$PATH
- 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

View File

@@ -57,6 +57,7 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-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/).
@@ -71,11 +72,21 @@ Support
Release Notes
-------------
### 4.14 - 1 Jul 2017
### 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
- 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
- Add `--max_file_size` option
- Configuration by system property or environment variable

View File

@@ -1,6 +1,6 @@
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.14.0"
val GitBucketVersion = "4.15.0"
val ScalatraVersion = "2.5.0"
val JettyVersion = "9.3.19.v20170502"
@@ -10,7 +10,7 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.12.2"
scalaVersion := "2.12.3"
// dependency settings
resolvers ++= Seq(
@@ -21,25 +21,25 @@ resolvers ++= Seq(
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.7.0.201704051617-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "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.8.0.201706111038-r",
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.5.1",
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
"commons-io" % "commons-io" % "2.5",
"io.github.gitbucket" % "solidbase" % "1.0.2",
"io.github.gitbucket" % "markedj" % "1.0.12",
"io.github.gitbucket" % "markedj" % "1.0.13",
"org.apache.commons" % "commons-compress" % "1.13",
"org.apache.commons" % "commons-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
"org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"),
"org.apache.tika" % "tika-core" % "1.14",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.8",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.9",
"joda-time" % "joda-time" % "2.9.9",
"com.novell.ldap" % "jldap" % "2009-10-07",
"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",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"com.zaxxer" % "HikariCP" % "2.6.1",
@@ -50,13 +50,15 @@ libraryDependencies ++= Seq(
"org.cache2k" % "cache2k-all" % "1.0.0.CR1",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"),
"net.coobird" % "thumbnailator" % "0.4.8",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.12" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "2.7.22" % "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
@@ -143,12 +145,28 @@ executableKey := {
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
IO delete (temp / "META-INF" / "MANIFEST.MF")
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
val manifest = new JarManifest
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
val outputFile = workDir / warName
IO jar (contentMappings, outputFile, manifest)

View File

@@ -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.

View File

@@ -6,7 +6,6 @@ Developer's Guide
* [Authentication in Controller](authenticator.md)
* [About Action in Issue Comment](comment_action.md)
* [Activity Types](activity.md)
* [Notification Email](notification.md)
* [Automatic Schema Updating](auto_update.md)
* [Release Operation](release.md)
* [JRebel integration (optional)](jrebel.md)

54
plugins.json Normal file
View File

@@ -0,0 +1,54 @@
[
{
"id": "notifications",
"name": "Notifications Plugin",
"description": "Provides notifications feature on GitBucket.",
"versions": [
{
"version": "1.0.0",
"range": ">=4.15.0",
"file": "gitbucket-notifications-plugin_2.12-1.0.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
}
]

View File

@@ -1,7 +1,6 @@
import java.security.MessageDigest;
import java.security.MessageDigest
import scala.annotation._
import sbt._
import sbt.Using._
object Checksums {
private val bufferSize = 2048

17
project/PluginsJson.scala Normal file
View 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
View File

@@ -0,0 +1 @@
libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.4"

View File

@@ -40,7 +40,7 @@ public class JettyLauncher {
}
break;
case "--max_file_size":
System.setProperty("gitbucket.maxFileSize", dim[2]);
System.setProperty("gitbucket.maxFileSize", dim[1]);
break;
case "--gitbucket.home":
System.setProperty("gitbucket.home", dim[1]);
@@ -48,6 +48,9 @@ public class JettyLauncher {
case "--temp_dir":
tmpDirPath = dim[1];
break;
case "--plugin_dir":
System.setProperty("gitbucket.pluginDir", dim[1]);
break;
}
}
}

View File

@@ -31,9 +31,8 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
// Register controllers
context.mount(new AnonymousAccessController, "/*")
PluginRegistry().getControllers.foreach { case (controller, path) =>
context.mount(controller, path)
}
context.addFilter("pluginControllerFilter", new PluginControllerFilter)
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
context.mount(new IndexController, "/")
context.mount(new ApiController, "/api/v3")

View File

@@ -38,5 +38,7 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new Version("4.14.0",
new LiquibaseMigration("update/gitbucket-core_4.14.xml"),
new SqlMigration("update/gitbucket-core_4.14.sql")
)
),
new Version("4.14.1"),
new Version("4.15.0")
)

View File

@@ -232,6 +232,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} getOrElse NotFound()
})
get("/captures/(.*)".r) {
multiParams("captures").head
}
get("/:userName/_delete")(oneselfOnly {
val userName = params("userName")
@@ -594,22 +598,23 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// Insert default labels
insertDefaultLabels(accountName, repository.name)
// Insert default priorities
insertDefaultPriorities(accountName, repository.name)
// clone repository actually
JGitUtil.cloneRepository(
getRepositoryDir(repository.owner, repository.name),
getRepositoryDir(accountName, repository.name))
FileUtil.deleteIfExists(getRepositoryDir(accountName, repository.name)))
// Create Wiki repository
JGitUtil.cloneRepository(
getWikiRepositoryDir(repository.owner, repository.name),
getWikiRepositoryDir(accountName, repository.name))
JGitUtil.cloneRepository(getWikiRepositoryDir(repository.owner, repository.name),
FileUtil.deleteIfExists(getWikiRepositoryDir(accountName, repository.name)))
// Copy files
FileUtils.copyDirectory(
Directory.getRepositoryFilesDir(repository.owner, repository.name),
Directory.getRepositoryFilesDir(accountName, repository.name)
)
// Copy LFS files
val lfsDir = getLfsDir(repository.owner, repository.name)
if(lfsDir.exists){
FileUtils.copyDirectory(lfsDir, FileUtil.deleteIfExists(getLfsDir(accountName, repository.name)))
}
// Record activity
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)

View File

@@ -150,7 +150,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
get("/:owner/:repository/settings/branches")(ownerOnly { repository =>
val protecteions = getProtectedBranchList(repository.owner, repository.name)
html.branches(repository, protecteions, flash.get("info"))
});
})
/** Update default branch */
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))
}
}
// Delere parent directory
// Delete parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
// Call hooks
@@ -393,7 +393,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
LockUtil.lock(s"${repository.owner}/${repository.name}") {
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git.gc();
git.gc()
}
}
flash += "info" -> "Garbage collection has been executed."

View File

@@ -6,7 +6,7 @@ import gitbucket.core.admin.html
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
import gitbucket.core.util.{AdminAuthenticator, Mailer}
import gitbucket.core.ssh.SshServer
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository}
import SystemSettingsService._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
@@ -15,6 +15,10 @@ import gitbucket.core.util.StringUtil._
import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.{FileUtils, IOUtils}
import org.scalatra.i18n.Messages
import com.github.zafarkhaja.semver.{Version => Semver}
import gitbucket.core.GitBucketCoreModule
import scala.collection.JavaConverters._
class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with RepositoryService with AdminAuthenticator
@@ -181,7 +185,71 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
})
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")
})

View File

@@ -121,6 +121,16 @@ abstract class Plugin {
*/
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.
*/
@@ -266,6 +276,9 @@ abstract class Plugin {
(pullRequestHooks ++ pullRequestHooks(registry, context, settings)).foreach { pullRequestHook =>
registry.addPullRequestHook(pullRequestHook)
}
(repositoryHeaders ++ repositoryHeaders(registry, context, settings)).foreach { repositoryHeader =>
registry.addRepositoryHeader(repositoryHeader)
}
(globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu =>
registry.addGlobalMenu(globalMenu)
}
@@ -287,8 +300,8 @@ abstract class Plugin {
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
registry.addDashboardTab(dashboardTab)
}
(issueSidebars ++ issueSidebars(registry, context, settings)).foreach { issueSidebar =>
registry.addIssueSidebar(issueSidebar)
(issueSidebars ++ issueSidebars(registry, context, settings)).foreach { issueSidebarComponent =>
registry.addIssueSidebar(issueSidebarComponent)
}
(assetsMappings ++ assetsMappings(registry, context, settings)).foreach { assetMapping =>
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.
*/
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.
*/

View File

@@ -2,6 +2,7 @@ package gitbucket.core.plugin
import java.io.{File, FilenameFilter, InputStream}
import java.net.URLClassLoader
import java.nio.file.{Files, Paths, StandardWatchEventKinds}
import java.util.Base64
import javax.servlet.ServletContext
@@ -9,6 +10,7 @@ import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.model.{Account, Issue}
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.DatabaseConfig
@@ -16,6 +18,7 @@ import gitbucket.core.util.Directory._
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import io.github.gitbucket.solidbase.model.Module
import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
import play.twirl.api.Html
@@ -39,11 +42,10 @@ class PluginRegistry {
private val repositoryHooks = new ListBuffer[RepositoryHook]
private val issueHooks = new ListBuffer[IssueHook]
issueHooks += new gitbucket.core.util.Notifier.IssueHook()
private val pullRequestHooks = new ListBuffer[PullRequestHook]
pullRequestHooks += new gitbucket.core.util.Notifier.PullRequestHook()
private val repositoryHeaders = new ListBuffer[(RepositoryInfo, Context) => Option[Html]]
private val globalMenus = new ListBuffer[(Context) => Option[Link]]
private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
@@ -128,6 +130,10 @@ class PluginRegistry {
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.toSeq
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders += repositoryHeader
def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.toSeq
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
@@ -180,79 +186,225 @@ object 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
/**
* Returns the PluginRegistry singleton 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 {
FileUtils.copyFile(file, new File(PluginHome, file.getName))
shutdown(context, settings)
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.
*/
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 manager = new JDBCVersionManager(conn)
if(pluginDir.exists && pluginDir.isDirectory){
pluginDir.listFiles(new FilenameFilter {
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
}).sortBy(_.getName).foreach { pluginJar =>
val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
try {
val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin]
// Clean installed directory
val installedDir = new File(PluginHome, ".installed")
if(installedDir.exists){
FileUtils.deleteDirectory(installedDir)
}
installedDir.mkdir()
// Migration
val solidbase = new Solidbase()
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
val pluginJars = listPluginJars(pluginDir)
val extraJars = extraPluginDir.map { extraDir => listPluginJars(new File(extraDir)) }.getOrElse(Nil)
// Check version
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
val pluginVersion = plugin.versions.last.getVersion
if(databaseVersion != pluginVersion){
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
(extraJars ++ pluginJars).foreach { pluginJar =>
val installedJar = new File(installedDir, pluginJar.getName)
FileUtils.copyFile(pluginJar, installedJar)
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
plugin.initialize(instance, context, settings)
instance.addPlugin(PluginInfo(
pluginId = plugin.pluginId,
pluginName = plugin.pluginName,
pluginVersion = plugin.versions.last.getVersion,
description = plugin.description,
pluginClass = plugin
))
// Check database version
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
val pluginVersion = plugin.versions.last.getVersion
if (databaseVersion != pluginVersion) {
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
}
} catch {
case e: Throwable => {
logger.error(s"Error during plugin initialization: ${pluginJar.getAbsolutePath}", e)
// Initialize
plugin.initialize(instance, context, settings)
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 = {
instance.getPlugins().foreach { pluginInfo =>
def shutdown(context: ServletContext, settings: SystemSettings): Unit = synchronized {
instance.getPlugins().foreach { plugin =>
try {
pluginInfo.pluginClass.shutdown(instance, context, settings)
plugin.pluginClass.shutdown(instance, context, settings)
} catch {
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(
pluginId: String,
pluginName: String,
pluginVersion: String,
description: String,
pluginClass: Plugin
)
override val pluginId: String,
override val pluginName: String,
override val pluginVersion: String,
override val description: String,
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")
}
}

View 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
)

View File

@@ -163,7 +163,7 @@ object MergeService{
case e: NoMergeBaseException => true
}
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
val committer = mergeTipCommit.getCommitterIdent;
val committer = mergeTipCommit.getCommitterIdent
def updateBranch(treeId:ObjectId, message:String, branchName:String){
// creates merge commit
val mergeCommitId = createMergeCommit(treeId, committer, message)

View File

@@ -1,25 +1,30 @@
package gitbucket.core.servlet
import java.io.File
import java.io.{File, FileOutputStream}
import akka.event.Logging
import com.typesafe.config.ConfigFactory
import gitbucket.core.GitBucketCoreModule
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.plugin.{PluginRegistry, PluginRepository}
import gitbucket.core.service.{ActivityService, SystemSettingsService}
import gitbucket.core.util.DatabaseConfig
import gitbucket.core.util.Directory._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.JDBCUtil._
import gitbucket.core.model.Profile.profile.blockingApi._
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import javax.servlet.{ServletContextListener, ServletContextEvent}
import org.apache.commons.io.FileUtils
import javax.servlet.{ServletContextEvent, ServletContextListener}
import org.apache.commons.io.{FileUtils, IOUtils}
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.github.zafarkhaja.semver.{Version => Semver}
import scala.collection.JavaConverters._
/**
* Initialize GitBucket system.
* 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)
// 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.")
}
}
checkVersion(manager, conn)
// Run normal migration
logger.info("Start schema update")
val solidbase = new Solidbase()
solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
new Solidbase().migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
// Rescue code for users who updated from 3.14 to 4.0.0
// 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}.")
}
// Install bundled plugins
extractBundledPlugins(gitbucketVersion)
// Load plugins
logger.info("Initialize plugins")
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
@@ -117,7 +92,76 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
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 = {
// Shutdown Quartz scheduler
@@ -146,4 +190,4 @@ class DeleteOldActivityActor extends Actor with SystemSettingsService with Activ
}
}
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -92,7 +92,7 @@ object DatabaseType {
}
object MySQL extends DatabaseType {
val jdbcDriver = "com.mysql.jdbc.Driver"
val jdbcDriver = "org.mariadb.jdbc.Driver"
val slickDriver = BlockingMySQLDriver
val liquiDriver = new MySQLDatabase()
}

View File

@@ -90,4 +90,4 @@ object Directory {
def getWikiRepositoryDir(owner: String, repository: String): File =
new File(s"${RepositoryHome}/${owner}/${repository}.wiki.git")
}
}

View File

@@ -68,9 +68,24 @@ object FileUtil {
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 = {
if(dir.isDirectory() && dir.list().isEmpty) {
FileUtils.deleteDirectory(dir)
}
}
/**
* Delete file or directory forcibly.
*/
def deleteIfExists(file: java.io.File): java.io.File = {
if(file.exists){
FileUtils.forceDelete(file)
}
file
}
}

View File

@@ -14,7 +14,7 @@ import org.eclipse.jgit.revwalk.filter._
import org.eclipse.jgit.treewalk._
import org.eclipse.jgit.treewalk.filter._
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 java.util.Date
import java.util.concurrent.TimeUnit
@@ -226,9 +226,14 @@ object JGitUtil {
ref.getName.stripPrefix("refs/heads/")
}.toList,
// tags
git.tagList.call.asScala.map { ref =>
val revCommit = getRevCommitFromId(git, ref.getObjectId)
TagInfo(ref.getName.stripPrefix("refs/tags/"), revCommit.getCommitterIdent.getWhen, revCommit.getName)
git.tagList.call.asScala.flatMap { ref =>
try {
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
)
} catch {
@@ -994,13 +999,13 @@ object JGitUtil {
def getBlame(git: Git, id: String, path: String): Iterable[BlameInfo] = {
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.setFilePath(path)
val blame = blamer.call()
var blameMap = Map[String, JGitUtil.BlameInfo]()
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)
if(!blameMap.contains(c.name)){
blameMap += c.name -> JGitUtil.BlameInfo(
@@ -1010,7 +1015,7 @@ object JGitUtil {
c.getAuthorIdent.getWhen,
Option(git.log.add(c).addPath(blame.getSourcePath(i)).setSkip(1).setMaxCount(2).call.iterator.next)
.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.getShortMessage,
Set.empty)

View File

@@ -1,10 +1,9 @@
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.service.{RepositoryService, AccountService, IssuesService, SystemSettingsService}
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.servlet.Database
import gitbucket.core.view.Markdown
import scala.concurrent._
import scala.util.{Success, Failure}
@@ -31,125 +30,6 @@ object Notifier {
case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get)
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 {

View File

@@ -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.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) {
<ul>
@plugins.map { plugin =>
@plugins.map { case (plugin, enabled) =>
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.pluginVersion</a></li>
}
</ul>
@plugins.map { plugin =>
@plugins.map { case (plugin, enabled) =>
<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="row">
<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>

View File

@@ -14,7 +14,7 @@
} else {
<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) =>
<li class="menu-item-hover">
<li class="repo-link menu-item-hover">
@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>
} else {
@@ -30,7 +30,7 @@
} else {
<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) =>
<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>
</li>
}

View File

@@ -3,12 +3,12 @@
<div class="input-group" style="margin-bottom: 0px;">
@html
<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>
</span>
</div>
} 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>
}
<script>

View File

@@ -16,7 +16,7 @@
<div class="tabbable">
<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><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>
<div class="tab-content">
<div class="tab-pane active" style="margin-top: 4px;" id="tab@uid">
@@ -32,7 +32,7 @@
generateScript = !enableWikiLink
)(textarea)
</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>
</div>

View File

@@ -1,12 +1,22 @@
@(content: String, commentId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
<span id="error-edit-content-@commentId" class="error"></span>
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea id="edit-content-@commentId" class="form-control">@content</textarea>
}
<div>
<input type="button" id="cancel-comment-@commentId" class="btn btn-danger" value="Cancel"/>
<input type="button" id="update-comment-@commentId" class="btn btn-default pull-right" value="Update comment"/>
@gitbucket.core.helper.html.preview(
repository = repository,
content = content,
enableWikiLink = false,
enableRefsLink = true,
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>
<script>
$(function(){
@@ -17,13 +27,14 @@ $(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');
$.ajax({
url: '@context.path/@repository.owner/@repository.name/issue_comments/edit/@commentId',
type: 'POST',
data: {
issueId : 0, // TODO
content : $('#edit-content-@commentId').val()
content : content
}
}).done(
callback

View File

@@ -1,27 +1,37 @@
@(content: Option[String], issueId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea id="edit-content" class="form-control">@content.getOrElse("")</textarea>
}
<div>
<input type="button" id="cancel-issue" class="btn btn-danger" value="Cancel"/>
<input type="button" id="update-issue" class="btn btn-default pull-right" value="Update comment"/>
@gitbucket.core.helper.html.preview(
repository = repository,
content = content.getOrElse(""),
enableWikiLink = false,
enableRefsLink = true,
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>
<script>
$(function(){
var callback = function(data){
$('#update, #cancel').removeAttr('disabled');
$('#issueContent').empty().html(data.content);
prettyPrint();
};
$('#update-issue').click(function(){
var content = $(this).parent().parent().find('textarea[name=content]').val();
$('#update, #cancel').attr('disabled', 'disabled');
$.ajax({
url: '@context.path/@repository.owner/@repository.name/issues/edit/@issueId',
type: 'POST',
data: {
content : $('#edit-content').val()
}
data: { content : content }
}).done(
callback
).fail(function(req) {

View File

@@ -148,8 +148,8 @@
<input type="hidden" name="assignedUserName" value=""/>
}
@issue.map { issue =>
@gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebar =>
@sidebar(issue, repository, context)
@gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebarComponent =>
@sidebarComponent(issue, repository, context)
}
<hr/>
<div style="margin-bottom: 14px;">

View File

@@ -46,7 +46,7 @@
<div class="wrapper">
<header class="main-header">
<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
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
</a>

View File

@@ -74,6 +74,11 @@
@gitbucket.core.helper.html.information(info)
@gitbucket.core.helper.html.error(error)
<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)
<a href="@helpers.url(repository.owner)">@repository.owner</a> / <a href="@helpers.url(repository)" class="strong">@repository.name</a>

View File

@@ -20,7 +20,7 @@
@status.statusesAndRequired.map { case (status, required) =>
<div class="build-status-item">
<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> }
</div>
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>

View File

@@ -1,12 +1,22 @@
@(content: String, commentId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
<span class="error-edit-content-@commentId error"></span>
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea style="height: 100px;" id="edit-content-@commentId" class="form-control">@content</textarea>
}
<div>
<input type="button" class="cancel-comment-@commentId btn btn-small btn-danger" value="Cancel"/>
<input type="button" class="update-comment-@commentId btn btn-small btn-default pull-right" value="Update comment"/>
@gitbucket.core.helper.html.preview(
repository = repository,
content = content,
enableWikiLink = false,
enableRefsLink = true,
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>
<script>
$(function(){
@@ -19,13 +29,14 @@ $(function(){
}
$(document).on('click', '.update-comment-@commentId', function(){
var content = $(this).parent().parent().find('textarea[name=content]').val();
$box = $(this).closest('.commit-comment-box');
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).attr('disabled', 'disabled');
$.ajax({
url: '@context.path/@repository.owner/@repository.name/commit_comments/edit/@commentId',
type: 'POST',
data: {
content : $('#edit-content-@commentId', $box).val()
content : content
}
}).done(
curriedCallback($box)

View File

@@ -1,6 +1,8 @@
/****************************************************************************/
/* Common */
/****************************************************************************/
.wrapper { position: static; }
body {
color: #333;
}
@@ -562,17 +564,6 @@ pre.commit-description {
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 {
list-style-type: none;
padding-left: 0px;

View 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

View File

@@ -39,7 +39,7 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
// other one can update
val tester2 = generateNewAccount("tester2")
val time2 = new java.util.Date();
val time2 = new java.util.Date()
val id2 = createCommitStatus(
userName = fixture1.userName,
repositoryName = fixture1.repositoryName,
@@ -72,4 +72,4 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
}}
}
}

View File

@@ -84,7 +84,7 @@ object GitSpecUtil {
throw new RuntimeException("conflict!")
}
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
val committer = mergeTipCommit.getCommitterIdent;
val committer = mergeTipCommit.getCommitterIdent
// creates merge commit
val mergeCommit = new CommitBuilder()
mergeCommit.setTreeId(merger.getResultTreeId)