diff --git a/README.md b/README.md index c5e69f8..8c4d277 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,19 @@ Release Notes ------------- +### 4.18.0 - 14 Oct 2017 +- Form to reply to review comment +- Display fullname in username suggestion +- Commit hook plugins are applied to online editing +- Improve gitbucket-ci-plugin + +### 4.17.0 - 30 Sep 2017 +- [gitbucket-ci-plugin](https://github.com/takezoe/gitbucket-ci-plugin) is available +- Transferring to URL with commit ID +- Drop uploadable file type limitation +- Improve Mailer API +- Web API and webhook enhancement + ### 4.16.0 - 2 Sep 2017 - Support AdminLTE color skin - Improve unexpected error handling diff --git a/build.sbt b/build.sbt index 06e5eeb..c296119 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,8 @@ +import com.typesafe.sbt.license.{LicenseInfo, DepModuleInfo} + val Organization = "io.github.gitbucket" val Name = "gitbucket" -val GitBucketVersion = "4.17.0-SNAPSHOT" +val GitBucketVersion = "4.18.0" val ScalatraVersion = "2.5.0" val JettyVersion = "9.3.19.v20170502" @@ -29,7 +31,7 @@ "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.14", + "io.github.gitbucket" % "markedj" % "1.0.15", "org.apache.commons" % "commons-compress" % "1.13", "org.apache.commons" % "commons-email" % "1.4", "org.apache.httpcomponents" % "httpclient" % "4.5.3", @@ -237,3 +239,8 @@ ) + +licenseOverrides := { + case DepModuleInfo("com.github.bkromhout", "java-diff-utils", _) => + LicenseInfo(LicenseCategory.Apache, "Apache-2.0", "http://www.apache.org/licenses/LICENSE-2.0") +} diff --git a/doc/jrebel.md b/doc/jrebel.md index 86a0863..4a597d9 100644 --- a/doc/jrebel.md +++ b/doc/jrebel.md @@ -2,23 +2,15 @@ ============================= [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 slow "app restart" per modification of codes. Alsp it's only used during development, and doesn't change your deployed app in any way. -``` -> jetty:start -``` - -While JRebel is not open source, it does reload your code faster than the `~;copy-resources;aux-compile` way of doing things using `sbt`. - -It's only used during development, and doesn't change your deployed app in any way. - -JRebel used to be free for Scala developers, but that changed recently, and now there's a cost associated with usage for Scala. There are trial plans and free non-commercial licenses available if you just want to try it out. +JRebel is not open source, but we can use it free for non-commercial use. ---- ## 1. Get a JRebel license -Sign up for a [usage plan](https://my.jrebel.com/). You will need to create an account. +Sign up for a [myJRebel](https://my.jrebel.com/register). You will need to create an account. ## 2. Download JRebel @@ -27,9 +19,7 @@ ## 3. Activate -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. +Follow `readme.txt` in the extracted directory to activate your downloaded JRebel. You don't need to integrate with your IDE, since we're using sbt to do the servlet deployment. @@ -41,13 +31,13 @@ To do so, edit your shell resource file (usually `~/.bash_profile` on Mac, and `~/.bashrc` on Linux), and add the following line: ```bash -export JREBEL=/path/to/jrebel/jrebel.jar +export JREBEL=/path/to/jrebel/legacy/jrebel.jar ``` For example, if you unzipped your JRebel download in your home directory, you whould use: ```bash -export JREBEL=~/jrebel/jrebel.jar +export JREBEL=~/jrebel/legacy/jrebel.jar ``` Now reload your shell: @@ -73,39 +63,26 @@ You will start the servlet container slightly differently now that you're using sbt. ``` -> jetty:start +> jetty:quickstart : -[info] starting server ... -[success] Total time: 3 s, completed Jan 3, 2016 9:47:55 PM -2016-01-03 21:47:57 JRebel: -2016-01-03 21:47:57 JRebel: A newer version '6.3.1' is available for download -2016-01-03 21:47:57 JRebel: from http://zeroturnaround.com/software/jrebel/download/ -2016-01-03 21:47:57 JRebel: -2016-01-03 21:47:58 JRebel: Contacting myJRebel server .. -2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/classes' will be monitored for changes. -2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/scala-2.11/test-classes' will be monitored for changes. -2016-01-03 21:47:59 JRebel: Directory '/git/gitbucket/target/webapp' will be monitored for changes. -2016-01-03 21:48:00 JRebel: -2016-01-03 21:48:00 JRebel: ############################################################# -2016-01-03 21:48:00 JRebel: -2016-01-03 21:48:00 JRebel: JRebel Legacy Agent 6.2.5 (201509291538) -2016-01-03 21:48:00 JRebel: (c) Copyright ZeroTurnaround AS, Estonia, Tartu. -2016-01-03 21:48:00 JRebel: -2016-01-03 21:48:00 JRebel: Over the last 30 days JRebel prevented -2016-01-03 21:48:00 JRebel: at least 182 redeploys/restarts saving you about 7.4 hours. -2016-01-03 21:48:00 JRebel: -2016-01-03 21:48:00 JRebel: Over the last 324 days JRebel prevented -2016-01-03 21:48:00 JRebel: at least 1538 redeploys/restarts saving you about 62.4 hours. -2016-01-03 21:48:00 JRebel: -2016-01-03 21:48:00 JRebel: Licensed to nazo king (using myJRebel). -2016-01-03 21:48:00 JRebel: -2016-01-03 21:48:00 JRebel: -2016-01-03 21:48:00 JRebel: ############################################################# -2016-01-03 21:48:00 JRebel: +2017-09-21 15:46:35 JRebel: +2017-09-21 15:46:35 JRebel: ############################################################# +2017-09-21 15:46:35 JRebel: +2017-09-21 15:46:35 JRebel: Legacy Agent 7.0.15 (201709080836) +2017-09-21 15:46:35 JRebel: (c) Copyright ZeroTurnaround AS, Estonia, Tartu. +2017-09-21 15:46:35 JRebel: +2017-09-21 15:46:35 JRebel: Over the last 2 days JRebel prevented +2017-09-21 15:46:35 JRebel: at least 8 redeploys/restarts saving you about 0.3 hours. +2017-09-21 15:46:35 JRebel: +2017-09-21 15:46:35 JRebel: Licensed to Naoki Takezoe (using myJRebel). +2017-09-21 15:46:35 JRebel: +2017-09-21 15:46:35 JRebel: +2017-09-21 15:46:35 JRebel: ############################################################# +2017-09-21 15:46:35 JRebel: : -> ~ copy-resources -[success] Total time: 0 s, completed Jan 3, 2016 9:13:54 PM +> ~compile +[success] Total time: 2 s, completed 2017/09/21 15:50:06 1. Waiting for source changes... (press enter to interrupt) ``` @@ -114,12 +91,11 @@ ```html : - - GitBucket - @defining(AutoUpdate.getCurrentVersion){ version => - @version.majorVersion.@version.minorVersion - } + : ``` @@ -128,21 +104,17 @@ ``` 1. Waiting for source changes... (press enter to interrupt) -2016-01-03 21:48:42 JRebel: Reloading class 'gitbucket.core.html.main$'. -[info] Wrote rebel.xml to /git/gitbucket/target/scala-2.11/resource_managed/main/rebel.xml -[info] Compiling 1 Scala source to /git/gitbucket/target/scala-2.11/classes... -[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM -2. Waiting for source changes... (press enter to interrupt) +[info] Compiling 1 Scala source to /Users/naoki.takezoe/gitbucket/target/scala-2.12/classes... +[success] Total time: 1 s, completed 2017/09/21 15:55:40 ``` And you reload browser, JRebel give notice of that it has reloaded classes: ``` -[success] Total time: 3 s, completed Jan 3, 2016 9:48:55 PM 2. Waiting for source changes... (press enter to interrupt) -2016-01-03 21:49:13 JRebel: Reloading class 'gitbucket.core.html.main$'. +2017-09-21 15:55:40 JRebel: Reloading class 'gitbucket.core.html.main$'. ``` ## 6. Limitations -JRebel is nearly always able to eliminate the need to explicitly reload your container after a code change. However, if you change any of your routes patterns, there is nothing JRebel can do, you will have to run `jetty:start`. +JRebel is nearly always able to eliminate the need to explicitly reload your container after a code change. However, if you change any of your routing patterns, there is nothing JRebel can do, you will have to restart by `jetty:quickstart`. diff --git a/doc/licenses.md b/doc/licenses.md new file mode 100644 index 0000000..48c9912 --- /dev/null +++ b/doc/licenses.md @@ -0,0 +1,102 @@ +# gitbucket-licenses + +Category | License | Dependency | Notes +--- | --- | --- | --- +Apache | [ Apache License, Version 2.0 ]( http://opensource.org/licenses/apache2.0.php ) | org.osgi # org.osgi.core # 4.3.1 | +Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.googlecode.javaewah # JavaEWAH # 1.1.6 | +Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | joda-time # joda-time # 2.9.9 | +Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-all # 1.0.0.CR1 | +Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.objenesis # objenesis # 2.5 | +Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # apache-sshd # 1.4.0 | +Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-core # 1.4.0 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe # config # 1.3.1 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe.akka # akka-actor_2.12 # 2.5.0 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-io # commons-io # 2.5 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | fr.brouillard.oss.security.xhub # xhub4j-core # 1.0.0 | +Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-compress # 1.13 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-email # 1.4 | +Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-lang3 # 3.5 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpclient # 4.5.3 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpcore # 4.4.6 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpmime # 4.5.2 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.tika # tika-core # 1.14 | +Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.liquibase # liquibase-core # 3.4.1 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-http # 9.2.19.v20160908 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-io # 9.2.19.v20160908 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-security # 9.2.19.v20160908 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-server # 9.2.19.v20160908 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-servlet # 9.2.19.v20160908 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-util # 9.2.19.v20160908 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-webapp # 9.2.19.v20160908 | +Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-xml # 9.2.19.v20160908 | +Apache | [Apache Software License, Version 1.1](http://www.apache.org/licenses/LICENSE-1.1) | org.bouncycastle # bcpg-jdk15on # 1.56 | +Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.github.bkromhout # java-diff-utils # 2.1.1 | +Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.typesafe.play # twirl-api_2.12 # 1.3.7 | +Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-ast_2.12 # 3.5.1 | +Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-core_2.12 # 3.5.1 | +Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-jackson_2.12 # 3.5.1 | +Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-scalap_2.12 # 3.5.1 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.enragedginger # akka-quartz-scheduler_2.12 # 1.6.0-akka-2.4.x | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-annotations # 2.8.0 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-core # 2.8.4 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-databind # 2.8.4 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.takezoe # blocking-slick-32_2.12 # 0.0.10 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.google.code.findbugs # jsr305 # 3.0.0 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.zaxxer # HikariCP # 2.6.1 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-codec # commons-codec # 1.9 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-logging # commons-logging # 1.2 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | de.flapdoodle.embed # de.flapdoodle.embed.process # 2.0.1 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | eu.medsea.mimeutil # mime-util # 2.1.3 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # markedj # 1.0.15 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # scalatra-forms_2.12 # 1.1.0 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # solidbase # 1.0.2 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy # 1.6.11 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy-agent # 1.6.11 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.quartz-scheduler # quartz # 2.2.3 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | ru.yandex.qatools.embed # postgresql-embedded # 2.0 | +Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | tomcat # tomcat-apr # 5.5.23 | +Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalactic # scalactic_2.12 # 3.0.0 | +Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalatest # scalatest_2.12 # 3.0.0 | +BSD | [BSD](LICENSE.txt) | com.thoughtworks.paranamer # paranamer # 2.8 | +BSD | [BSD](http://software.clapper.org/grizzled-slf4j/license.html) | org.clapper # grizzled-slf4j_2.12 # 1.3.0 | +BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-common_2.12 # 2.5.0 | +BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-json_2.12 # 2.5.0 | +BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-scalatest_2.12 # 2.5.0 | +BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-test_2.12 # 2.5.0 | +BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra_2.12 # 2.5.0 | +BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-library # 2.12.3 | +BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-reflect # 2.12.3 | +BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-java8-compat_2.12 # 0.8.0 | +BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-parser-combinators_2.12 # 1.0.4 | +BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-xml_2.12 # 1.0.6 | +BSD | [BSD License](http://www.opensource.org/licenses/bsd-license.php) | com.wix # wix-embedded-mysql # 2.1.4 | +BSD | [BSD-2-Clause](https://jdbc.postgresql.org/about/license.html) | org.postgresql # postgresql # 42.0.0 | +BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit # 4.8.0.201706111038-r | +BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.archive # 4.8.0.201706111038-r | +BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.http.server # 4.8.0.201706111038-r | +BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 | +BSD | [Revised BSD](http://www.jcraft.com/jsch/LICENSE.txt) | com.jcraft # jsch # 0.1.54 | +BSD | [Two-clause BSD-style license](http://github.com/slick/slick/blob/master/LICENSE.txt) | com.typesafe.slick # slick_2.12 # 3.2.1 | +CC0 | [CC0](http://creativecommons.org/publicdomain/zero/1.0/) | org.reactivestreams # reactive-streams # 1.0.0 | +CDDL | [COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0](https://glassfish.dev.java.net/public/CDDLv1.0.html) | javax.activation # activation # 1.1.1 | +GPL | [CDDL/GPLv2+CE](https://glassfish.java.net/public/CDDL+GPL_1_1.html) | com.sun.mail # javax.mail # 1.5.2 | +GPL with Classpath Extension | [CDDL + GPLv2 with classpath exception](https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html) | javax.servlet # javax.servlet-api # 3.1.0 | +LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-classic # 1.2.3 | +LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-core # 1.2.3 | +LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna # 4.0.0 | +LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna-platform # 4.0.0 | +LGPL | [LGPL-2.1](null) | org.mariadb.jdbc # mariadb-java-client # 2.0.3 | +MIT | [MIT License](http://www.opensource.org/licenses/mit-license.php) | org.slf4j # slf4j-api # 1.7.25 | +MIT | [The MIT License](http://www.opensource.org/licenses/mit-license.php) | com.github.zafarkhaja # java-semver # 0.9.0 | +MIT | [The MIT License](https://jsoup.org/license) | org.jsoup # jsoup # 1.10.2 | +MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-all # 1.10.19 | +MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-core # 2.7.22 | +MIT | [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) | net.coobird # thumbnailator # 0.4.8 | +Mozilla | [MPL 2.0 or EPL 1.0](http://h2database.com/html/license.html) | com.h2database # h2 # 1.4.195 | +Mozilla | [Mozilla Public License 1.1 (MPL 1.1)](http://www.mozilla.org/MPL/MPL-1.1.html) | com.googlecode.juniversalchardet # juniversalchardet # 1.0.3 | +Public Domain | [Public Domain](http://en.wikipedia.org/wiki/Public_domain) | net.i2p.crypto # eddsa # 0.1.0 | +unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcpkix-jdk15on # 1.56 | +unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcprov-jdk15on # 1.56 | +unrecognized | [Eclipse Public License 1.0](http://www.eclipse.org/legal/epl-v10.html) | junit # junit # 4.12 | +unrecognized | [The OpenLDAP Public License](http://www.openldap.org/software/release/license.html) | com.novell.ldap # jldap # 2009-10-07 | + diff --git a/doc/readme.md b/doc/readme.md index 86cb76f..7ef6e3b 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -9,3 +9,4 @@ * [Automatic Schema Updating](auto_update.md) * [Release Operation](release.md) * [JRebel integration (optional)](jrebel.md) + * [Licenses](licenses.md) diff --git a/plugins.json b/plugins.json index 6d8d98a..dadd98f 100644 --- a/plugins.json +++ b/plugins.json @@ -5,9 +5,9 @@ "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" + "version": "1.2.0", + "range": ">=4.17.0", + "file": "gitbucket-notifications-plugin_2.12-1.2.0.jar" } ], "default": true @@ -18,9 +18,9 @@ "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" + "version": "4.5.0", + "range": ">=4.18.0", + "file": "gitbucket-emoji-plugin_2.12-4.5.0.jar" } ], "default": false diff --git a/project/plugins.sbt b/project/plugins.sbt index 785559e..20e45f9 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,3 +7,4 @@ addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.1") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC11") +addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0") diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index 6db2af3..94a352d 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -41,5 +41,7 @@ ), new Version("4.14.1"), new Version("4.15.0"), - new Version("4.16.0") + new Version("4.16.0"), + new Version("4.17.0"), + new Version("4.18.0") ) diff --git a/src/main/scala/gitbucket/core/api/ApiPath.scala b/src/main/scala/gitbucket/core/api/ApiPath.scala index 661ce47..dc96a7c 100644 --- a/src/main/scala/gitbucket/core/api/ApiPath.scala +++ b/src/main/scala/gitbucket/core/api/ApiPath.scala @@ -1,6 +1,13 @@ package gitbucket.core.api /** - * path for api url. if set path '/repos/aa/bb' then, expand 'http://server:post/repos/aa/bb' when converted to json. + * Path for API url. + * If set path '/repos/aa/bb' then, expand 'http://server:port/repos/aa/bb' when converted to json. */ case class ApiPath(path: String) + +/** + * Path for git repository via SSH. + * If set path '/aa/bb.git' then, expand 'git@server:port/aa/bb.git' when converted to json. + */ +case class SshPath(path: String) diff --git a/src/main/scala/gitbucket/core/api/ApiRepository.scala b/src/main/scala/gitbucket/core/api/ApiRepository.scala index 1f79072..f5d74f6 100644 --- a/src/main/scala/gitbucket/core/api/ApiRepository.scala +++ b/src/main/scala/gitbucket/core/api/ApiRepository.scala @@ -24,6 +24,7 @@ val http_url = ApiPath(s"/git/${full_name}.git") val clone_url = ApiPath(s"/git/${full_name}.git") val html_url = ApiPath(s"/${full_name}") + val ssh_url = Some(SshPath(s":${full_name}.git")) } object ApiRepository{ @@ -55,12 +56,13 @@ def forDummyPayload(owner: ApiUser): ApiRepository = ApiRepository( - name="dummy", - full_name=s"${owner.login}/dummy", - description="", - watchers=0, - forks=0, - `private`=false, - default_branch="master", - owner=owner)(true) + name = "dummy", + full_name = s"${owner.login}/dummy", + description = "", + watchers = 0, + forks = 0, + `private` = false, + default_branch = "master", + owner = owner + )(true) } diff --git a/src/main/scala/gitbucket/core/api/JsonFormat.scala b/src/main/scala/gitbucket/core/api/JsonFormat.scala index 7164cf6..6533272 100644 --- a/src/main/scala/gitbucket/core/api/JsonFormat.scala +++ b/src/main/scala/gitbucket/core/api/JsonFormat.scala @@ -10,7 +10,7 @@ object JsonFormat { - case class Context(baseUrl: String) + case class Context(baseUrl: String, sshUrl: Option[String]) val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") @@ -40,21 +40,24 @@ FieldSerializer[ApiCommits.File]() + ApiBranchProtection.enforcementLevelSerializer - def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format => - ( - { - case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length)) - case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") - }, - { - case ApiPath(path) => JString(c.baseUrl + path) - } - ) - ) + def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](_ => ({ + case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length)) + case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") + }, { + case ApiPath(path) => JString(c.baseUrl + path) + })) + + def sshPathSerializer(c: Context) = new CustomSerializer[SshPath](_ => ({ + case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) => SshPath(s.substring(c.sshUrl.get.length)) + case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath") + }, { + case SshPath(path) => c.sshUrl.map { sshUrl => JString(sshUrl + path) } getOrElse JNothing + })) /** * convert object to json string */ - def apply(obj: AnyRef)(implicit c: Context): String = Serialization.write(obj)(jsonFormats + apiPathSerializer(c)) + def apply(obj: AnyRef)(implicit c: Context): String = + Serialization.write(obj)(jsonFormats + apiPathSerializer(c) + sshPathSerializer(c)) } diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index 41b45cd..928e5f6 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -321,7 +321,7 @@ .map { _ => "Mail address is already registered." } } - val allReservedNames = Set("git", "admin", "upload", "api") + val allReservedNames = Set("git", "admin", "upload", "api", "assets", "plugin-assets", "signin", "signout", "register", "activities.atom", "sidebar-collapse", "groups", "new") protected def reservedNames(): Constraint = new Constraint(){ override def validate(name: String, value: String, messages: Messages): Option[String] = if(allReservedNames.contains(value)){ Some(s"${value} is reserved") diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala index 9f27caa..7bf8408 100644 --- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala +++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala @@ -48,7 +48,7 @@ FileUtils.writeByteArrayToFile(new java.io.File( getAttachedDir(params("owner"), params("repository")), fileId + "." + FileUtil.getExtension(file.getName)), file.get) - }, FileUtil.isUploadableType) + }, _ => true) } post("/wiki/:owner/:repository"){ @@ -80,12 +80,12 @@ builder.finish() val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), - Constants.HEAD, loginAccount.userName, loginAccount.mailAddress, s"Uploaded ${fileName}") + Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, s"Uploaded ${fileName}") fileName } } - }, FileUtil.isUploadableType) + }, _ => true) } } getOrElse BadRequest() } @@ -113,7 +113,7 @@ } } - private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match { + private def execute(f: (FileItem, String) => Unit , mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match { case Some(file) if(mimeTypeChcker(file.name)) => defining(FileUtil.generateFileId){ fileId => f(file, fileId) diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index ddcaa6c..a4cf49f 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -121,7 +121,12 @@ case (true, false) => !t.isGroupAccount case (false, true) => t.isGroupAccount case (false, false) => false - }}.map { t => t.userName } + }}.map { t => + Map( + "label" -> s"@${t.userName} ${t.fullName}", + "value" -> t.userName + ) + } )) ) }) diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index b13dde1..bf529cf 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -24,6 +24,7 @@ import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder} import org.eclipse.jgit.errors.MissingObjectException import org.eclipse.jgit.lib._ +import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack} import org.scalatra._ import org.scalatra.i18n.Messages @@ -431,7 +432,7 @@ try { using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git => defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) { revCommit => - JGitUtil.getDiffs(git, id, false) match { + JGitUtil.getDiffs(git, id, true) match { case (diffs, oldCommitId) => html.commit(id, new JGitUtil.CommitInfo(revCommit), JGitUtil.getBranchesOfCommit(git, revCommit.getName), @@ -717,39 +718,63 @@ f(git, headTip, builder, inserter) val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter), - headName, loginAccount.userName, loginAccount.mailAddress, message) + headName, loginAccount.fullName, loginAccount.mailAddress, message) inserter.flush() inserter.close() - // update refs - val refUpdate = git.getRepository.updateRef(headName) - refUpdate.setNewObjectId(commitId) - refUpdate.setForceUpdate(false) - refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) - refUpdate.update() + val receivePack = new ReceivePack(git.getRepository) + val receiveCommand = new ReceiveCommand(headTip, commitId, headName) - // update pull request - updatePullRequests(repository.owner, repository.name, branch) + // call post commit hook + val error = PluginRegistry().getReceiveHooks.flatMap { hook => + hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName) + }.headOption - // record activity - val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) - recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo)) + error match { + case Some(error) => + // commit is rejected + // TODO Notify commit failure to edited user + val refUpdate = git.getRepository.updateRef(headName) + refUpdate.setNewObjectId(headTip) + refUpdate.setForceUpdate(true) + refUpdate.update() - // create issue comment by commit message - createIssueComment(repository.owner, repository.name, commitInfo) + case None => + // update refs + val refUpdate = git.getRepository.updateRef(headName) + refUpdate.setNewObjectId(commitId) + refUpdate.setForceUpdate(false) + refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)) + refUpdate.update() - // close issue by commit message - closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name) + // update pull request + updatePullRequests(repository.owner, repository.name, branch) - //call web hook - callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount) - val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) - callWebHookOf(repository.owner, repository.name, WebHook.Push) { - getAccountByUserName(repository.owner).map{ ownerAccount => - WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount, - oldId = headTip, newId = commitId) - } + // record activity + val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) + recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo)) + + // create issue comment by commit message + createIssueComment(repository.owner, repository.name, commitInfo) + + // close issue by commit message + closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name) + + // call post commit hook + PluginRegistry().getReceiveHooks.foreach { hook => + hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName) + } + + //call web hook + callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount) + val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)) + callWebHookOf(repository.owner, repository.name, WebHook.Push) { + getAccountByUserName(repository.owner).map{ ownerAccount => + WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount, + oldId = headTip, newId = commitId) + } + } } } } diff --git a/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala b/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala index 3aafa11..e7d49df 100644 --- a/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala +++ b/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala @@ -3,15 +3,92 @@ import gitbucket.core.controller.Context import gitbucket.core.service.RepositoryService.RepositoryInfo +/** + * The base trait of suggestion providers which supplies completion proposals in some text areas. + */ trait SuggestionProvider { + /** + * The identifier of this suggestion provider. + * You must specify the unique identifier in the all suggestion providers. + */ val id: String + + /** + * The trigger of this suggestion provider. When user types this character, the proposal list would be displayed. + * Also this is used as the prefix of the replaced string. + */ val prefix: String + + /** + * The suffix of the replaced string. The default is `" "`. + */ val suffix: String = " " + + /** + * Which contexts is this suggestion provider enabled. Currently, available contexts are `"issues"` and `"wiki"`. + */ val context: Seq[String] - def values(repository: RepositoryInfo): Seq[String] - def template(implicit context: Context): String = "value" + /** + * If this suggestion provider has static proposal list, override this method to return it. + * + * The returned sequence is rendered as follows: + *
+   * [
+   *   {
+   *     "label" -> "value1",
+   *     "value" -> "value1"
+   *   },
+   *   {
+   *     "label" -> "value2",
+   *     "value" -> "value2"
+   *   },
+   * ]
+   * 
+ * + * Each element can be accessed as `option` in `template()` or `replace()` method. + */ + def values(repository: RepositoryInfo): Seq[String] = Nil + + /** + * If this suggestion provider has static proposal list, override this method to return it. + * + * If your proposals have label and value, use this method instead of `values()`. + * The first element of tuple is used as a value, and the second element is used as a label. + * + * The returned sequence is rendered as follows: + *
+   * [
+   *   {
+   *     "label" -> "label1",
+   *     "value" -> "value1"
+   *   },
+   *   {
+   *     "label" -> "label2",
+   *     "value" -> "value2"
+   *   },
+   * ]
+   * 
+ * + * Each element can be accessed as `option` in `template()` or `replace()` method. + */ + def options(repository: RepositoryInfo): Seq[(String, String)] = values(repository).map { value => (value, value) } + + /** + * JavaScript fragment to generate a label of completion proposal. The default is: `option.label`. + */ + def template(implicit context: Context): String = "option.label" + + /** + * JavaScript fragment to generate a replaced value of completion proposal. The default is: `option.value` + */ + def replace(implicit context: Context): String = "option.value" + + /** + * If this suggestion provider needs some additional process to assemble the proposal list (e.g. It need to use Ajax + * to get a proposal list from the server), then override this method and return any JavaScript code. + */ def additionalScript(implicit context: Context): String = "" } @@ -20,8 +97,6 @@ override val id: String = "user" override val prefix: String = "@" override val context: Seq[String] = Seq("issues") - override def values(repository: RepositoryInfo): Seq[String] = Nil - override def template(implicit context: Context): String = "'@' + value" override def additionalScript(implicit context: Context): String = s"""$$.get('${context.path}/_user/proposals', { query: '', user: true, group: false }, function (data) { user = data.options; });""" -} \ No newline at end of file +} diff --git a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala index 5cbb77c..65d0aaa 100644 --- a/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala +++ b/src/main/scala/gitbucket/core/service/ProtectedBranchService.scala @@ -1,11 +1,10 @@ package gitbucket.core.service -import gitbucket.core.model.{ProtectedBranch, ProtectedBranchContext, CommitState} +import gitbucket.core.model.{Session => _, _} import gitbucket.core.plugin.ReceiveHook import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile.profile.blockingApi._ - -import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand} +import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack} trait ProtectedBranchService { @@ -46,12 +45,17 @@ object ProtectedBranchService { - class ProtectedBranchReceiveHook extends ReceiveHook with ProtectedBranchService { + class ProtectedBranchReceiveHook extends ReceiveHook with ProtectedBranchService with RepositoryService with AccountService { override def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String) (implicit session: Session): Option[String] = { val branch = command.getRefName.stripPrefix("refs/heads/") if(branch != command.getRefName){ - getProtectedBranchInfo(owner, repository, branch).getStopReason(receivePack.isAllowNonFastForwards, command, pusher) + val repositoryInfo = getRepository(owner, repository) + if(command.getType == ReceiveCommand.Type.DELETE && repositoryInfo.exists(_.repository.defaultBranch == branch)){ + Some(s"refusing to delete the branch: ${command.getRefName}.") + } else { + getProtectedBranchInfo(owner, repository, branch).getStopReason(receivePack.isAllowNonFastForwards, command, pusher) + } } else { None } @@ -74,10 +78,19 @@ * Include administrators * Enforce required status checks for repository administrators. */ - includeAdministrators: Boolean) extends AccountService with CommitStatusService { + includeAdministrators: Boolean) extends AccountService with RepositoryService with CommitStatusService { def isAdministrator(pusher: String)(implicit session: Session): Boolean = - pusher == owner || getGroupMembers(owner).exists(gm => gm.userName == pusher && gm.isManager) + pusher == owner || getGroupMembers(owner).exists(gm => gm.userName == pusher && gm.isManager) || + getCollaborators(owner, repository).exists { case (collaborator, isGroup) => + if(collaborator.role == Role.ADMIN.name){ + if(isGroup){ + getGroupMembers(collaborator.collaboratorName).exists(gm => gm.userName == pusher) + } else { + collaborator.collaboratorName == pusher + } + } else false + } /** * Can't be force pushed diff --git a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala index c73ed35..bb50df4 100644 --- a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala +++ b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala @@ -140,17 +140,16 @@ ldapAuthentication: Boolean, ldap: Option[Ldap], skinName: String){ - def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/")) - def sshAddress:Option[SshAddress] = - for { - host <- sshHost if ssh - } - yield SshAddress( - host, - sshPort.getOrElse(DefaultSshPort), - "git" - ) + def baseUrl(request: HttpServletRequest): String = baseUrl.fold { + val url = request.getRequestURL.toString + val len = url.length - (request.getRequestURI.length - request.getContextPath.length) + url.substring(0, len).stripSuffix("/") + } (_.stripSuffix("/")) + + def sshAddress:Option[SshAddress] = sshHost.collect { case host if ssh => + SshAddress(host, sshPort.getOrElse(DefaultSshPort), "git") + } } case class Ldap( diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 6d14d2c..2fa3859 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -156,9 +156,13 @@ logger.debug("repository:" + owner + "/" + repository) + val settings = loadSystemSettings() + val baseUrl = settings.baseUrl(request) + val sshUrl = settings.sshAddress.map { x => s"${x.genericUser}@${x.host}:${x.port}" } + if(!repository.endsWith(".wiki")){ defining(request) { implicit r => - val hook = new CommitLogHook(owner, repository, pusher, baseUrl) + val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl) receivePack.setPreReceiveHook(hook) receivePack.setPostReceiveHook(hook) } @@ -166,7 +170,7 @@ if(repository.endsWith(".wiki")){ defining(request) { implicit r => - receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl)) + receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)) } } } @@ -178,7 +182,7 @@ import scala.collection.JavaConverters._ -class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String) +class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String]) extends PostReceiveHook with PreReceiveHook with RepositoryService with AccountService with IssuesService with ActivityService with PullRequestService with WebHookService with WebHookPullRequestService with CommitsService { @@ -219,7 +223,7 @@ val pushedIds = scala.collection.mutable.Set[String]() commands.asScala.foreach { command => logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}") - implicit val apiContext = api.JsonFormat.Context(baseUrl) + implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl) val refName = command.getRefName.split("/") val branchName = refName.drop(2).mkString("/") val commits = if (refName(1) == "tags") { @@ -320,7 +324,7 @@ } -class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: String) +class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String]) extends PostReceiveHook with WebHookService with AccountService with RepositoryService { private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook]) @@ -329,7 +333,7 @@ Database() withTransaction { implicit session => try { commands.asScala.headOption.foreach { command => - implicit val apiContext = api.JsonFormat.Context(baseUrl) + implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl) val refName = command.getRefName.split("/") val commitIds = if (refName(1) == "tags") { None @@ -347,7 +351,6 @@ diffs._1.collect { case diff if diff.newPath.toLowerCase.endsWith(".md") => val action = if(diff.changeType == ChangeType.ADD) "created" else "edited" val fileName = diff.newPath - //println(action + " - " + fileName + " - " + commit.id) (action, fileName, commit.id) } } diff --git a/src/main/scala/gitbucket/core/ssh/GitCommand.scala b/src/main/scala/gitbucket/core/ssh/GitCommand.scala index 9d7345d..3be9f87 100644 --- a/src/main/scala/gitbucket/core/ssh/GitCommand.scala +++ b/src/main/scala/gitbucket/core/ssh/GitCommand.scala @@ -154,7 +154,7 @@ } } -class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName) +class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String]) extends DefaultGitCommand(owner, repoName) with RepositoryService with AccountService with DeployKeyService { override protected def runTask(authType: AuthType): Unit = { @@ -169,7 +169,7 @@ val repository = git.getRepository val receive = new ReceivePack(repository) if (!repoName.endsWith(".wiki")) { - val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl) + val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl, sshUrl) receive.setPreReceiveHook(hook) receive.setPostReceiveHook(hook) } @@ -216,7 +216,7 @@ } -class GitCommandFactory(baseUrl: String) extends CommandFactory { +class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends CommandFactory { private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory]) override def createCommand(command: String): Command = { @@ -227,7 +227,7 @@ case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, routing(repoName)) case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, routing(repoName)) case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName) - case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl) + case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl) case _ => new UnknownCommand(command) } } diff --git a/src/main/scala/gitbucket/core/ssh/SshServerListener.scala b/src/main/scala/gitbucket/core/ssh/SshServerListener.scala index 6ff1d0e..857db45 100644 --- a/src/main/scala/gitbucket/core/ssh/SshServerListener.scala +++ b/src/main/scala/gitbucket/core/ssh/SshServerListener.scala @@ -22,7 +22,7 @@ provider.setOverwriteAllowed(false) server.setKeyPairProvider(provider) server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser)) - server.setCommandFactory(new GitCommandFactory(baseUrl)) + server.setCommandFactory(new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}"))) server.setShellFactory(new NoShell(sshAddress)) } diff --git a/src/main/scala/gitbucket/core/util/FileUtil.scala b/src/main/scala/gitbucket/core/util/FileUtil.scala index e31dd6b..6b082d1 100644 --- a/src/main/scala/gitbucket/core/util/FileUtil.scala +++ b/src/main/scala/gitbucket/core/util/FileUtil.scala @@ -28,8 +28,6 @@ def isImage(name: String): Boolean = getMimeType(name).startsWith("image/") - def isUploadableType(name: String): Boolean = mimeTypeWhiteList contains getMimeType(name) - def isLarge(size: Long): Boolean = (size > 1024 * 1000) def isText(content: Array[Byte]): Boolean = !content.contains(0) @@ -53,16 +51,6 @@ } } - val mimeTypeWhiteList: Array[String] = Array( - "application/pdf", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "image/gif", - "image/jpeg", - "image/png", - "text/plain") - def getLfsFilePath(owner: String, repository: String, oid: String): String = Directory.getLfsDir(owner, repository) + "/" + oid diff --git a/src/main/scala/gitbucket/core/util/Implicits.scala b/src/main/scala/gitbucket/core/util/Implicits.scala index 77767a3..393dbbf 100644 --- a/src/main/scala/gitbucket/core/util/Implicits.scala +++ b/src/main/scala/gitbucket/core/util/Implicits.scala @@ -22,7 +22,8 @@ // Convert to slick session. implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request) - implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context = JsonFormat.Context(context.baseUrl) + implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context = + JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x => s"${x.genericUser}@${x.host}:${x.port}" }) implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal { @@ -77,11 +78,6 @@ def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^" + quote(request.getContextPath) + "/git/", "/") - def baseUrl:String = { - val url = request.getRequestURL.toString - val len = url.length - (request.getRequestURI.length - request.getContextPath.length) - url.substring(0, len).stripSuffix("/") - } } implicit class RichSession(private val session: HttpSession) extends AnyVal { diff --git a/src/main/twirl/gitbucket/core/helper/attached.scala.html b/src/main/twirl/gitbucket/core/helper/attached.scala.html index b8ac133..c68c2bf 100644 --- a/src/main/twirl/gitbucket/core/helper/attached.scala.html +++ b/src/main/twirl/gitbucket/core/helper/attached.scala.html @@ -11,7 +11,9 @@ $(function(){ @gitbucket.core.plugin.PluginRegistry().getSuggestionProviders.map { provider => @if(provider.context.contains(completionContext)){ - var @provider.id = @Html(helpers.json(provider.values(repository))); + var @provider.id = @Html(helpers.json(provider.options(repository).map { case (value, label) => + Map("value" -> value, "label" -> label) + })); @Html(provider.additionalScript) } } @@ -23,14 +25,14 @@ match: /\B@{provider.prefix}([\-+\w]*)$/, search: function (term, callback) { callback($.map(@{provider.id}, function (proposal) { - return proposal.indexOf(term) === 0 ? proposal : null; + return proposal.value.indexOf(term) === 0 ? proposal : null; })); }, - template: function (value) { + template: function (option) { return @{Html(provider.template)}; }, - replace: function (value) { - return '@{provider.prefix}' + value + '@{provider.suffix}'; + replace: function (option) { + return '@{provider.prefix}' + @{Html(provider.replace)} + '@{provider.suffix}'; }, index: 1 }, @@ -65,8 +67,6 @@ url: '@context.path/upload/file/@repository.owner/@repository.name', maxFilesize: 10, clickable: @clickable, - acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")), - dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.', previewTemplate: "
\n
Uploading your files...
\n
\n
", success: function(file, id) { var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) + diff --git a/src/main/twirl/gitbucket/core/helper/diff.scala.html b/src/main/twirl/gitbucket/core/helper/diff.scala.html index ddb997c..2cc6f9f 100644 --- a/src/main/twirl/gitbucket/core/helper/diff.scala.html +++ b/src/main/twirl/gitbucket/core/helper/diff.scala.html @@ -10,7 +10,7 @@ @import org.eclipse.jgit.diff.DiffEntry.ChangeType @if(showIndex){
-
+
@@ -151,20 +151,21 @@ } window.viewType = 1; if(("&" + location.search.substring(1)).indexOf("&diff=split") != -1){ - $('.container').removeClass('container').addClass('container-wide'); window.viewType = 0; } renderDiffs(); $('#btn-unified').click(function(){ window.viewType = 1; - $('.container-wide').removeClass('container-wide').addClass('container'); + $('#btn-unified').addClass('active'); + $('#btn-split').removeClass('active'); renderDiffs(); }); $('#btn-split').click(function(){ window.viewType = 0; - $('.container').removeClass('container').addClass('container-wide'); + $('#btn-unified').removeClass('active'); + $('#btn-split').addClass('active'); renderDiffs(); }); @@ -174,6 +175,7 @@ } $(this).closest('table').find('.not-diff').toggle(); }); + $('.ignore-whitespace').change(function() { renderOneDiff($(this).closest("table").find(".diffText"), viewType); }); @@ -188,22 +190,56 @@ } return $(''); } + if (typeof $('#show-notes')[0] !== 'undefined' && !$('#show-notes')[0].checked) { $('#comment-list').children('.inline-comment').hide(); } + + function showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr){ + // assemble Ajax url + var url = '@helpers.url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' }; + if (!isNaN(oldLineNumber) && oldLineNumber) { + url += ('&oldLineNumber=' + oldLineNumber) + } + if (!isNaN(newLineNumber) && newLineNumber) { + url += ('&newLineNumber=' + newLineNumber) + } + // send Ajax request + $.get(url, { dataType : 'html' }, function(responseContent) { + // create container + var tmp; + if (!isNaN(oldLineNumber) && oldLineNumber) { + if (!isNaN(newLineNumber) && newLineNumber) { + tmp = getInlineContainer(); + } else { + tmp = getInlineContainer('old'); + } + } else { + tmp = getInlineContainer('new'); + } + // add comment textarea + tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent); + $tr.nextAll(':not(.not-diff):first').before(tmp); + // hide reply comment field + $(tmp).closest('.not-diff').prev().find('.reply-comment').closest('.not-diff').hide(); + // focus textarea + tmp.find('textarea').focus(); + }); + } + + // Add comment button $('.diff-outside').on('click','table.diff .add-comment',function() { var $this = $(this); var $tr = $this.closest('tr'); var $check = $this.closest('table:not(.diff)').find('.toggle-notes'); - var url = ''; + //var url = ''; if (!$check.prop('checked')) { $check.prop('checked', true).trigger('change'); } if (!$tr.nextAll(':not(.not-diff):first').prev().hasClass('inline-comment-form')) { var commitId = $this.closest('.table-bordered').attr('commitId'), fileName = $this.closest('.table-bordered').attr('fileName'), - oldLineNumber, newLineNumber, - url = '@helpers.url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' }; + oldLineNumber, newLineNumber; if (viewType == 0) { oldLineNumber = $this.parent().prev('.oldline').attr('line-number'); newLineNumber = $this.parent().prev('.newline').attr('line-number'); @@ -211,30 +247,27 @@ oldLineNumber = $this.parent().prevAll('.oldline').attr('line-number'); newLineNumber = $this.parent().prevAll('.newline').attr('line-number'); } - if (!isNaN(oldLineNumber) && oldLineNumber) { - url += ('&oldLineNumber=' + oldLineNumber) - } - if (!isNaN(newLineNumber) && newLineNumber) { - url += ('&newLineNumber=' + newLineNumber) - } - $.get(url, { dataType : 'html' }, function(responseContent) { - var tmp; - if (!isNaN(oldLineNumber) && oldLineNumber) { - if (!isNaN(newLineNumber) && newLineNumber) { - tmp = getInlineContainer(); - } else { - tmp = getInlineContainer('old'); - } - } else { - tmp = getInlineContainer('new'); - } - tmp.addClass('inline-comment-form').children('.comment-box-container').html(responseContent); - $tr.nextAll(':not(.not-diff):first').before(tmp); - }); + + showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr); } }).on('click', 'table.diff .btn-default', function() { + // Cancel comment form + $(this).closest('.not-diff').prev().find('.reply-comment').closest('.not-diff').show(); $(this).closest('.inline-comment-form').remove(); }); + + // Reply comment + $('.diff-outside').on('click', '.reply-comment',function(){ + var $this = $(this); + var $tr = $this.closest('tr'); + var commitId = $this.closest('.table-bordered').attr('commitId'); + var fileName = $this.data('filename'); + var oldLineNumber = $this.data('oldline'); + var newLineNumber = $this.data('newline'); + + showCommentForm(commitId, fileName, oldLineNumber, newLineNumber, $tr); + }); + function renderOneCommitCommentIntoDiff($v, diff){ var filename = $v.attr('filename'); var oldline = $v.attr('oldline'); @@ -257,6 +290,7 @@ tmp.hide(); } } + function renderStatBar(add, del){ if(add + del > 5){ if(add){ @@ -282,6 +316,7 @@ } return ret; } + function renderOneDiff(diffText, viewType){ var table = diffText.closest("table[data-diff-id]"); var i = table.data("diff-id"); @@ -305,12 +340,59 @@ } }); } + return table; } + + function renderReplyComment($table){ + var elements = {}; + var filename, newline, oldline; + $table.find('.comment-box-container .inline-comment').each(function(i, e){ + filename = $(e).attr('filename'); + newline = $(e).attr('newline'); + oldline = $(e).attr('oldline'); + var key = filename + '-' + newline + '-' + oldline; + elements[key] = { + element: $(e), + filename: filename, + newline: newline, + oldline: oldline + }; + }); + for(var key in elements){ + filename = elements[key]['filename']; + oldline = elements[key]['oldline']; + newline = elements[key]['newline']; + + var $v = $('
') + .append($('') + .data('filename', filename) + .data('newline', newline) + .data('oldline', oldline)); + + var tmp; + if (typeof oldline !== 'undefined') { + if (typeof newline !== 'undefined') { + tmp = getInlineContainer(); + } else { + tmp = getInlineContainer('old'); + } + tmp.children('td:first').html($v); + } else { + tmp = getInlineContainer('new'); + tmp.children('td:last').html($v); + } + elements[key]['element'].closest('.not-diff').after(tmp); + } + } + function renderDiffs(){ var i = 0, diffs = $('.diffText'); function render(){ if(diffs[i]){ - renderOneDiff($(diffs[i]), viewType); + var $table = renderOneDiff($(diffs[i]), viewType); + @if(hasWritePermission) { + renderReplyComment($table); + } i++; setTimeout(render); } diff --git a/src/main/twirl/gitbucket/core/issues/commentlist.scala.html b/src/main/twirl/gitbucket/core/issues/commentlist.scala.html index 976969d..0211a2d 100644 --- a/src/main/twirl/gitbucket/core/issues/commentlist.scala.html +++ b/src/main/twirl/gitbucket/core/issues/commentlist.scala.html @@ -201,7 +201,6 @@ $.post('@helpers.url(repository)/issue_comments/delete/' + id, function(data){ if(data > 0) { - $('#comment-' + id).prev('div.issue-avatar-image').remove(); $('#comment-' + id).remove(); } }); @@ -230,7 +229,6 @@ function(data){ if(data > 0) { $('.commit-comment-' + id).closest('.not-diff').remove(); - $('.commit-comment-' + id).closest('.inline-comment').remove(); } }); } diff --git a/src/main/twirl/gitbucket/core/main.scala.html b/src/main/twirl/gitbucket/core/main.scala.html index 612491a..24c7f25 100644 --- a/src/main/twirl/gitbucket/core/main.scala.html +++ b/src/main/twirl/gitbucket/core/main.scala.html @@ -9,51 +9,53 @@ - - + + + - + - - - + + + - + - + - - + + - + - + @repository.map { repository => } - +