diff --git a/README.md b/README.md index c5e69f803..8c4d277f5 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,19 @@ Support 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 06e5eeb6b..c29611992 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 @@ libraryDependencies ++= Seq( "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 @@ pomExtra := ( ) + +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 86a086381..4a597d929 100644 --- a/doc/jrebel.md +++ b/doc/jrebel.md @@ -2,23 +2,15 @@ JRebel integration (optional) ============================= [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 @@ Next, unzip the downloaded file. ## 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 @@ You only need to tell jvm where to find the jrebel jar. 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 @@ $ ./sbt 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 @@ For example, you can change the title on `src/main/twirl/gitbucket/core/main.sca ```html : - - GitBucket - @defining(AutoUpdate.getCurrentVersion){ version => - @version.majorVersion.@version.minorVersion - } + : ``` @@ -128,21 +104,17 @@ If JRebel is doing is correctly installed you will see a notice for you: ``` 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 000000000..48c991289 --- /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 86cb76fd0..7ef6e3b56 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -9,3 +9,4 @@ Developer's Guide * [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 6d8d98a71..dadd98f8a 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 785559e3b..20e45f924 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -7,3 +7,4 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5") 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 6db2af3b6..94a352dc7 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -41,5 +41,7 @@ object GitBucketCoreModule extends Module("gitbucket-core", ), 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 661ce47c5..dc96a7c89 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 1f790727e..f5d74f60f 100644 --- a/src/main/scala/gitbucket/core/api/ApiRepository.scala +++ b/src/main/scala/gitbucket/core/api/ApiRepository.scala @@ -24,6 +24,7 @@ case class ApiRepository( 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 @@ object ApiRepository{ 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 7164cf643..6533272be 100644 --- a/src/main/scala/gitbucket/core/api/JsonFormat.scala +++ b/src/main/scala/gitbucket/core/api/JsonFormat.scala @@ -10,7 +10,7 @@ import scala.util.Try 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 @@ object JsonFormat { 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 41b45cd11..928e5f6f8 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -321,7 +321,7 @@ trait AccountManagementControllerBase extends ControllerBase { .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 9f27caacd..7bf840826 100644 --- a/src/main/scala/gitbucket/core/controller/FileUploadController.scala +++ b/src/main/scala/gitbucket/core/controller/FileUploadController.scala @@ -48,7 +48,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R 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 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R 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 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R } } - 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 ddcaa6cd8..a4cf49f8b 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -121,7 +121,12 @@ trait IndexControllerBase extends ControllerBase { 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 b13dde1a6..bf529cfc5 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.archive.{TgzFormat, ZipFormat} 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 @@ trait RepositoryViewerControllerBase extends ControllerBase { 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 @@ trait RepositoryViewerControllerBase extends ControllerBase { 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 3aafa11d3..e7d49df5c 100644 --- a/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala +++ b/src/main/scala/gitbucket/core/plugin/SuggestionProvider.scala @@ -3,15 +3,92 @@ package gitbucket.core.plugin 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 @@ class UserNameSuggestionProvider extends SuggestionProvider { 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 5cbb77c86..65d0aaa6a 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 @@ trait ProtectedBranchService { 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 @@ object ProtectedBranchService { * 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 c73ed3591..bb50df4e2 100644 --- a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala +++ b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala @@ -140,17 +140,16 @@ object SystemSettingsService { 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 6d14d2c06..2fa3859b2 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -156,9 +156,13 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] 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 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] 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 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest] 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 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: 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 CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: } -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 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: 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 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl: 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 9d7345dee..3be9f8746 100644 --- a/src/main/scala/gitbucket/core/ssh/GitCommand.scala +++ b/src/main/scala/gitbucket/core/ssh/GitCommand.scala @@ -154,7 +154,7 @@ class DefaultGitUploadPack(owner: String, repoName: String) extends DefaultGitCo } } -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 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) ex 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 PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting) exte } -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 @@ class GitCommandFactory(baseUrl: String) extends CommandFactory { 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 6ff1d0e40..857db45cb 100644 --- a/src/main/scala/gitbucket/core/ssh/SshServerListener.scala +++ b/src/main/scala/gitbucket/core/ssh/SshServerListener.scala @@ -22,7 +22,7 @@ object SshServer { 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 e31dd6bb3..6b082d164 100644 --- a/src/main/scala/gitbucket/core/util/FileUtil.scala +++ b/src/main/scala/gitbucket/core/util/FileUtil.scala @@ -28,8 +28,6 @@ object FileUtil { 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 @@ object FileUtil { } } - 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 77767a3bc..393dbbf88 100644 --- a/src/main/scala/gitbucket/core/util/Implicits.scala +++ b/src/main/scala/gitbucket/core/util/Implicits.scala @@ -22,7 +22,8 @@ object Implicits { // 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 @@ object Implicits { 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 b8ac1330c..c68c2bf23 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 @@ $(function(){ 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 @@ $(function(){ 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 ddb997c36..2cc6f9f7d 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 @@ $(function(){ } 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 @@ $(function(){ } $(this).closest('table').find('.not-diff').toggle(); }); + $('.ignore-whitespace').change(function() { renderOneDiff($(this).closest("table").find(".diffText"), viewType); }); @@ -188,22 +190,56 @@ $(function(){ } 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 @@ $(function(){ 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 @@ $(function(){ tmp.hide(); } } + function renderStatBar(add, del){ if(add + del > 5){ if(add){ @@ -282,6 +316,7 @@ $(function(){ } return ret; } + function renderOneDiff(diffText, viewType){ var table = diffText.closest("table[data-diff-id]"); var i = table.data("diff-id"); @@ -305,12 +340,59 @@ $(function(){ } }); } + 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 976969dd5..0211a2d70 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 @@ $(function(){ $.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(){ 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 612491a83..24c7f25fc 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 => } - +