From db679967afeab134deddaad360d506fd5158dce8 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 12 Sep 2016 14:59:43 +0900 Subject: [PATCH 01/59] (refs #1291)Add http-only attribute to JSESSIONID cookie --- src/main/webapp/WEB-INF/web.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index e8ee9e90b..9634066c2 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -88,6 +88,9 @@ 1440 + + true + From 7a282fb67e171ef5fa27e9ec24f8bc06cbb62f33 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 12 Sep 2016 15:06:59 +0900 Subject: [PATCH 02/59] (refs #1291)Add secure attribute to JSESSIONID cookie when baseUrl starts with "https://" --- src/main/scala/ScalatraBootstrap.scala | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index fbb488a55..623fded9b 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,17 +1,23 @@ import gitbucket.core.controller._ import gitbucket.core.plugin.PluginRegistry -import gitbucket.core.servlet.{ApiAuthenticationFilter, GitAuthenticationFilter, Database, TransactionFilter} +import gitbucket.core.servlet.{ApiAuthenticationFilter, Database, GitAuthenticationFilter, TransactionFilter} import gitbucket.core.util.Directory - import java.util.EnumSet import javax.servlet._ +import gitbucket.core.service.SystemSettingsService import org.scalatra._ -class ScalatraBootstrap extends LifeCycle { +class ScalatraBootstrap extends LifeCycle with SystemSettingsService { override def init(context: ServletContext) { + + val settings = loadSystemSettings() + if(settings.baseUrl.exists(_.startsWith("https://"))) { + context.getSessionCookieConfig.setSecure(true) + } + // Register TransactionFilter and BasicAuthenticationFilter at first context.addFilter("transactionFilter", new TransactionFilter) context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") From 78df2accfc63c4db4a582a2df663aa3e23f73614 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 18 Sep 2016 21:14:00 +0900 Subject: [PATCH 03/59] =?UTF-8?q?(refs=20#1290)Always=20show=20=E2=80=9CDo?= =?UTF-8?q?wnload=20ZIP=E2=80=9D=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/twirl/gitbucket/core/repo/files.scala.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/twirl/gitbucket/core/repo/files.scala.html b/src/main/twirl/gitbucket/core/repo/files.scala.html index 6df6409f9..b89ae016d 100644 --- a/src/main/twirl/gitbucket/core/repo/files.scala.html +++ b/src/main/twirl/gitbucket/core/repo/files.scala.html @@ -29,14 +29,14 @@ @if(pathList.isEmpty){ - @if(context.platform != "linux" && context.platform != null){ -
-
+
+
+ @if(context.platform != "linux" && context.platform != null){ - Download ZIP -
+ } + Download ZIP
- } +
@gitbucket.core.helper.html.copy("repository-url-copy", repository.httpUrl){ From 2ca20af502691915fe13e923dd00f78eeaf527d5 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 19 Sep 2016 23:13:52 +0900 Subject: [PATCH 04/59] (refs #1297)Allow to configure HikariCP in database.conf --- .../core/servlet/TransactionFilter.scala | 7 +++++ .../gitbucket/core/util/DatabaseConfig.scala | 26 ++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala b/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala index bad06e1a9..c25a2feaa 100644 --- a/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala +++ b/src/main/scala/gitbucket/core/servlet/TransactionFilter.scala @@ -52,6 +52,13 @@ object Database { config.setJdbcUrl(DatabaseConfig.url) config.setUsername(DatabaseConfig.user) config.setPassword(DatabaseConfig.password) + config.setAutoCommit(false) + DatabaseConfig.connectionTimeout.foreach(config.setConnectionTimeout) + DatabaseConfig.idleTimeout.foreach(config.setIdleTimeout) + DatabaseConfig.maxLifetime.foreach(config.setMaxLifetime) + DatabaseConfig.minimumIdle.foreach(config.setMinimumIdle) + DatabaseConfig.maximumPoolSize.foreach(config.setMaximumPoolSize) + logger.debug("load database connection pool") new HikariDataSource(config) } diff --git a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala index ec68d0bb6..98892b7d4 100644 --- a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala +++ b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala @@ -17,6 +17,11 @@ object DatabaseConfig { | url = "jdbc:h2:${DatabaseHome};MVCC=true" | user = "sa" | password = "sa" + |# connectionTimeout = 30000 + |# idleTimeout = 600000 + |# maxLifetime = 1800000 + |# minimumIdle = 10 + |# maximumPoolSize = 10 |} |""".stripMargin, "UTF-8") } @@ -28,12 +33,21 @@ object DatabaseConfig { def url(directory: Option[String]): String = dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome)) - lazy val url: String = url(None) - lazy val user: String = config.getString("db.user") - lazy val password: String = config.getString("db.password") - lazy val jdbcDriver: String = DatabaseType(url).jdbcDriver - lazy val slickDriver: slick.driver.JdbcProfile = DatabaseType(url).slickDriver - lazy val liquiDriver: AbstractJdbcDatabase = DatabaseType(url).liquiDriver + lazy val url : String = url(None) + lazy val user : String = config.getString("db.user") + lazy val password : String = config.getString("db.password") + lazy val jdbcDriver : String = DatabaseType(url).jdbcDriver + lazy val slickDriver : slick.driver.JdbcProfile = DatabaseType(url).slickDriver + lazy val liquiDriver : AbstractJdbcDatabase = DatabaseType(url).liquiDriver + lazy val connectionTimeout : Option[Long] = getOptionValue("db.connectionTimeout", config.getLong) + lazy val idleTimeout : Option[Long] = getOptionValue("db.idleTimeout" , config.getLong) + lazy val maxLifetime : Option[Long] = getOptionValue("db.maxLifetime" , config.getLong) + lazy val minimumIdle : Option[Int] = getOptionValue("db.minimumIdle" , config.getInt) + lazy val maximumPoolSize : Option[Int] = getOptionValue("db.maximumPoolSize" , config.getInt) + + private def getOptionValue[T](path: String, f: String => T): Option[T] = { + if(config.hasPath(path)) None else Some(f(path)) + } } From 7b84f25c562fa54da52c70010fc4a5dbfe66b335 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 20 Sep 2016 15:45:21 +0900 Subject: [PATCH 05/59] (refs #1297)Bugfix --- src/main/scala/gitbucket/core/util/DatabaseConfig.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala index 98892b7d4..fb8d1842d 100644 --- a/src/main/scala/gitbucket/core/util/DatabaseConfig.scala +++ b/src/main/scala/gitbucket/core/util/DatabaseConfig.scala @@ -46,7 +46,7 @@ object DatabaseConfig { lazy val maximumPoolSize : Option[Int] = getOptionValue("db.maximumPoolSize" , config.getInt) private def getOptionValue[T](path: String, f: String => T): Option[T] = { - if(config.hasPath(path)) None else Some(f(path)) + if(config.hasPath(path)) Some(f(path)) else None } } From d5e455336b8709a8ed44ed3bc2e8d27bb9094306 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 20 Sep 2016 16:41:20 +0900 Subject: [PATCH 06/59] (refs #1293)Restore dashboard issues / pull requests switcher --- .../core/dashboard/issues.scala.html | 2 +- .../core/dashboard/issuesnavi.scala.html | 28 +++++++++++-------- .../gitbucket/core/dashboard/pulls.scala.html | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/twirl/gitbucket/core/dashboard/issues.scala.html b/src/main/twirl/gitbucket/core/dashboard/issues.scala.html index d051d76f4..f23e57652 100644 --- a/src/main/twirl/gitbucket/core/dashboard/issues.scala.html +++ b/src/main/twirl/gitbucket/core/dashboard/issues.scala.html @@ -11,7 +11,7 @@ @gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){ @gitbucket.core.dashboard.html.tab("issues")
- @gitbucket.core.dashboard.html.issuesnavi(filter, openCount, closedCount, condition) + @gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition) @gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
} diff --git a/src/main/twirl/gitbucket/core/dashboard/issuesnavi.scala.html b/src/main/twirl/gitbucket/core/dashboard/issuesnavi.scala.html index a328c5c8e..7504d1aec 100644 --- a/src/main/twirl/gitbucket/core/dashboard/issuesnavi.scala.html +++ b/src/main/twirl/gitbucket/core/dashboard/issuesnavi.scala.html @@ -1,4 +1,5 @@ -@(filter: String, +@(active: String, + filter: String, openCount: Int, closedCount: Int, condition: gitbucket.core.service.IssuesService.IssueSearchCondition)(implicit context: gitbucket.core.controller.Context) @@ -9,15 +10,18 @@
  • Closed @closedCount
  • - @* -
  • - Created -
  • -
  • - Assigned -
  • -
  • - Mentioned -
  • - *@ + + + + diff --git a/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html b/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html index 33bb057d6..6d1686789 100644 --- a/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html +++ b/src/main/twirl/gitbucket/core/dashboard/pulls.scala.html @@ -11,7 +11,7 @@ @gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){ @gitbucket.core.dashboard.html.tab("pulls")
    - @gitbucket.core.dashboard.html.issuesnavi(filter, openCount, closedCount, condition) + @gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition) @gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
    } From 98914269b71957099bd93ae36b711a87a0c08319 Mon Sep 17 00:00:00 2001 From: conradlink Date: Wed, 21 Sep 2016 08:43:07 -0400 Subject: [PATCH 07/59] Fix to make the --host argument work again. --- src/main/java/JettyLauncher.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/JettyLauncher.java b/src/main/java/JettyLauncher.java index 443954c7b..1ac4187c2 100644 --- a/src/main/java/JettyLauncher.java +++ b/src/main/java/JettyLauncher.java @@ -3,12 +3,14 @@ import org.eclipse.jetty.webapp.WebAppContext; import java.io.File; import java.net.URL; +import java.net.InetSocketAddress; import java.security.ProtectionDomain; public class JettyLauncher { public static void main(String[] args) throws Exception { String host = null; int port = 8080; + InetSocketAddress address = null; String contextPath = "/"; boolean forceHttps = false; @@ -29,7 +31,13 @@ public class JettyLauncher { } } - Server server = new Server(port); + if(host != null) { + address = new InetSocketAddress(host, port); + } else { + address = new InetSocketAddress(port); + } + + Server server = new Server(address); // SelectChannelConnector connector = new SelectChannelConnector(); // if(host != null) { From a1372034eda13bb7957ea647bd3429140e39af97 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 22 Sep 2016 11:37:37 +0900 Subject: [PATCH 08/59] Ready for GitBucket 4.5 release --- README.md | 7 +++++++ build.sbt | 2 +- src/main/scala/gitbucket/core/GitBucketCoreModule.scala | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2b658683..f3e42e7c2 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,13 @@ Support Release Notes ------------- +### 4.5 - 1 Oct 2016 +- Attach files by dropping into textarea +- Issues / Pull requests switcher in dashboard +- HikariCP could be configured in `GITBUCKET_HOME/database.conf` +- Improve Cookie security +- Improve mobile view + ### 4.4 - 28 Aug 2016 - Import a SQL dump file to the database - `go get` support in private repositories diff --git a/build.sbt b/build.sbt index e49451b45..760aab1d9 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ val Organization = "io.github.gitbucket" val Name = "gitbucket" -val GitBucketVersion = "4.4.0" +val GitBucketVersion = "4.5.0" val ScalatraVersion = "2.4.1" val JettyVersion = "9.3.9.v20160517" diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index d1685bae4..a404be3a3 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -14,5 +14,6 @@ object GitBucketCoreModule extends Module("gitbucket-core", ), new Version("4.2.1"), new Version("4.3.0"), - new Version("4.4.0") + new Version("4.4.0"), + new Version("4.5.0") ) From c14a732e2a5d570c24c1df6133804129dd712a1e Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 22 Sep 2016 11:41:26 +0900 Subject: [PATCH 09/59] Update publishing jar operation --- doc/release.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/release.md b/doc/release.md index 504778cd9..682dc8842 100644 --- a/doc/release.md +++ b/doc/release.md @@ -46,9 +46,10 @@ $ sbt executable ### Deploy assembly jar file -For plug-in development, we have to publish the assembly jar file to the public Maven repository by `release/deploy-assembly-jar.sh`. +For plug-in development, we have to publish the GitBucket jar file to the Maven central repository as well. At first, hit following command to publish artifacts to the sonatype OSS repository: ```bash -$ cd release/ -$ ./deploy-assembly-jar.sh +$ sbt publish-signed ``` + +Then operate release sequence at https://oss.sonatype.org/. From 1532fd71d0d913328174f057a0e10e1217b04fa1 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 22 Sep 2016 11:43:21 +0900 Subject: [PATCH 10/59] Remove files for publishing jars to the maven repository because already it's possible by sbt. --- release/deploy-assembly-jar.sh | 24 ------------------------ release/env.sh | 3 --- release/pom.xml | 17 ----------------- 3 files changed, 44 deletions(-) delete mode 100755 release/deploy-assembly-jar.sh delete mode 100644 release/env.sh delete mode 100644 release/pom.xml diff --git a/release/deploy-assembly-jar.sh b/release/deploy-assembly-jar.sh deleted file mode 100755 index 43778d7e8..000000000 --- a/release/deploy-assembly-jar.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/sh -. ./env.sh - -cd ../ -./sbt.sh clean assembly - -cd release - -if [[ "$GITBUCKET_VERSION" =~ -SNAPSHOT$ ]]; then - MVN_DEPLOY_PATH=mvn-snapshot -else - MVN_DEPLOY_PATH=mvn -fi - -echo $MVN_DEPLOY_PATH - -mvn deploy:deploy-file \ - -DgroupId=gitbucket\ - -DartifactId=gitbucket-assembly\ - -Dversion=$GITBUCKET_VERSION\ - -Dpackaging=jar\ - -Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\ - -DrepositoryId=sourceforge.jp\ - -Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/$MVN_DEPLOY_PATH/ diff --git a/release/env.sh b/release/env.sh deleted file mode 100644 index 58921719d..000000000 --- a/release/env.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -export GITBUCKET_VERSION=`cat ../build.sbt | grep 'val GitBucketVersion' | cut -d \" -f 2` -echo "GITBUCKET_VERSION: $GITBUCKET_VERSION" diff --git a/release/pom.xml b/release/pom.xml deleted file mode 100644 index 0d84d21fa..000000000 --- a/release/pom.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - 4.0.0 - jp.sf.amateras - gitbucket-assembly - 0.0.1 - - - - org.apache.maven.wagon - wagon-ssh - 2.10 - - - - \ No newline at end of file From 9b33655bd49db2bbfddf8a0ffb15096f6fc84398 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 25 Sep 2016 17:09:29 +0900 Subject: [PATCH 11/59] Bump to sbt 0.13.12. --- project/build.properties | 2 +- ...aunch-0.13.9.jar => sbt-launch-0.13.12.jar | Bin 1210221 -> 1210231 bytes sbt.bat | 2 +- sbt.sh | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename sbt-launch-0.13.9.jar => sbt-launch-0.13.12.jar (94%) diff --git a/project/build.properties b/project/build.properties index 43b8278c6..35c88bab7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.11 +sbt.version=0.13.12 diff --git a/sbt-launch-0.13.9.jar b/sbt-launch-0.13.12.jar similarity index 94% rename from sbt-launch-0.13.9.jar rename to sbt-launch-0.13.12.jar index c065b47b8666d8ff7553caa5265438afbfacac70..871dedda6bc11601f8d39f5bcd0dc809678936d7 100644 GIT binary patch delta 14863 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

    ;e<{?x0jxqNsJy;a zea9HzIZ5SVl*&H9sn-yw;#3tOdmCaQ$H!C+k@-kPJEjPzoYu`X|OqcF62=I<)< zrmKSx?U@V(Zc~vL`@obJ zX{r%{$i^QsQraP-JEccpZpW+52qhBd?9U>Qi9Tm&d?fB{&ohXQ`rTQdNc3d{rAI2! z;_CBU8*<6$Na}oI-x%re`Jx5m-3$BVqA#!zM&lc!5Z*GYF7G!+UJkp*h4b0yN{c2a z+~_gMQCQSdAEQR~gRuDwCp_hN`BO|C>Mec8u$HFj~MKuKpf5}ODQ@G#!iVLS8 z2#zE)Q#y+`-*L7p6kEEiW@uPGZEB`;7m`Veg8t=at`8JVM#_r9Uad$lQP6V)2*{b< zM2Z&@@;@UVO1R#!x_*iag0P zT(IM7zDFzYGEpW9i8587Zmlq(dPdXfR@hxGZiQ+TI@Jmj@eG`$$ATS>F?sSKoDhT1 zZj9ylVXVpg=4k`NC)#j)Jw^!;i`rQP9Hq8aSB{~rv5URf(V}o5_jnWD{IQn;&at=w zt`r!nG!av~GLusDb1X($OWGBybkf}EXClY`CRoI&`F?t%O^bsqj zP*EH*Cbi(+@Eep~EC0x7*MO0TAqZIy1K%OpD07OV5l z7!0g#?GTvxQ%qDa#pF)nwjrN$_ z*e<*b4d{gaIqy-CH~SC{YY#&e^G(z;-{e8hJ0VVCf!$ zvyQ09hb}Rx%SjUrIBoK#N?mY+BPqTMHmU`biyv|QIZEh;n|SXWykh$I7bjleWN=rd zju?BH3HTcOGH#BV0r$J2gZQ|y1mk?H8@!&Pp4~881m9u`S31}YGu4s%CVGA!_5S<< zr-PNdqXR9Tm}uw|WRtq59SJ0c6w&qWiVNj-hnqj?T6gShNBnD|MgN+7DYggt=}BXI zU^L-8y$8~01KIS1WJAq+Vnf-3=J!-$G&UNEs%RwqbE^b&Ay7-R6Y%WYJpnGa(5wWd zgV-4#e=w%+r9_C8^%g;!uIB z5MlmKe2>kfhPm+j9xgt)Sm7VOho2lb3kek$;wutX3L{EK&paeQ{%Pj$J}8>!$#m|N z+XoB$OMFDngtvV#N{`lLLMd9(7srRDT9Q5W=!^EWt!=?kV*h#;K}${3F-Tkz5%5l7 z5~YVp9;EMtL6QI@?4lnNvAywbLWKhm_Zx}WgSz6a028Y8gW%MH3nQ5@Hku3jnXt4K z6Fm4{*uOs--J>-p>f$z<5wAE-94jLxhZ(umMkQGT5RL2Yxz@Xbq@|;k*j^ynHVjr8`V(p#*w_unPakSA9KLgC_HY#KL1zKQPkYE^gwhO$COt=>WwZB7 zw0*ziLjg@O4_z3cxS9I>RpPu&pI%A$;)J;`^Mm51n{c2+LtagBqSXaZQ|=%>COC-3 k7R12ADj;DHzGX(mo5_Bp5-R*cQ6rVgcAbt&_-B~^2b$?jt^fc4 diff --git a/sbt.bat b/sbt.bat index 726d347bb..bfc44ca25 100644 --- a/sbt.bat +++ b/sbt.bat @@ -1,2 +1,2 @@ set SCRIPT_DIR=%~dp0 -java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.9.jar" %* +java %JAVA_OPTS% -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.12.jar" %* diff --git a/sbt.sh b/sbt.sh index e0f14b25c..55d0be1e4 100755 --- a/sbt.sh +++ b/sbt.sh @@ -1,2 +1,2 @@ #!/bin/sh -java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.9.jar "$@" +java $JAVA_OPTS -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.12.jar "$@" From fe959aecff9d98a4dd98c0be1d5f242e423cebe4 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 25 Sep 2016 17:42:50 +0900 Subject: [PATCH 12/59] (refs #1298)Append raw=true only if the given url does not have it. --- src/main/scala/gitbucket/core/view/Markdown.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/gitbucket/core/view/Markdown.scala b/src/main/scala/gitbucket/core/view/Markdown.scala index 49ceeedba..6eb3641c5 100644 --- a/src/main/scala/gitbucket/core/view/Markdown.scala +++ b/src/main/scala/gitbucket/core/view/Markdown.scala @@ -147,21 +147,23 @@ object Markdown { } private def fixUrl(url: String, isImage: Boolean = false): String = { + lazy val urlWithRawParam: String = url + (if(isImage && !url.endsWith("?raw=true")) "?raw=true" else "") + if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){ url } else if(url.startsWith("#")){ ("#" + generateAnchorName(url.substring(1))) } else if(!enableWikiLink){ if(context.currentPath.contains("/blob/")){ - url + (if(isImage) "?raw=true" else "") + urlWithRawParam } else if(context.currentPath.contains("/tree/")){ val paths = context.currentPath.split("/") val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") + repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam } else { val paths = context.currentPath.split("/") val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch - repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "") + repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam } } else { repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url From 11fb0a7edfb36795aae8a7d05e8604274c3d0983 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Wed, 28 Sep 2016 09:33:16 +0900 Subject: [PATCH 13/59] (refs #1214)Gravater is disable in default --- .../scala/gitbucket/core/service/SystemSettingsService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala index 668eb9a7b..c2aa28ded 100644 --- a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala +++ b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala @@ -73,7 +73,7 @@ trait SystemSettingsService { getValue(props, AllowAccountRegistration, false), getValue(props, AllowAnonymousAccess, true), getValue(props, IsCreateRepoOptionPublic, true), - getValue(props, Gravatar, true), + getValue(props, Gravatar, false), getValue(props, Notification, false), getOptionValue[Int](props, ActivityLogLimit, None), getValue(props, Ssh, false), From aefbee2093bd545519f0f1617b7633656cda2e4a Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Wed, 28 Sep 2016 09:58:32 +0900 Subject: [PATCH 14/59] (refs #1206)Display find and history icon in mobile view --- src/main/twirl/gitbucket/core/repo/files.scala.html | 4 ++-- src/main/webapp/assets/common/css/gitbucket.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/twirl/gitbucket/core/repo/files.scala.html b/src/main/twirl/gitbucket/core/repo/files.scala.html index b89ae016d..98202e71f 100644 --- a/src/main/twirl/gitbucket/core/repo/files.scala.html +++ b/src/main/twirl/gitbucket/core/repo/files.scala.html @@ -22,9 +22,9 @@ }, Some(repository)) { @gitbucket.core.html.menu("files", repository, Some(branch), info, error){

    -
    +
    - +
    diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index ffd5adc65..58122f688 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -1588,7 +1588,7 @@ a.markdown-anchor-link span.octicon { vertical-align: baseline; font-size: 100%; height: inherit; - width: 780px; + width: 300px; } .find-input{ font-size: 18px; From d99e382dfee99e01ef2805e3bfb9865d769e8a26 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Wed, 28 Sep 2016 10:11:55 +0900 Subject: [PATCH 15/59] (refs #1206)Display commit count on the history button --- src/main/twirl/gitbucket/core/repo/files.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/twirl/gitbucket/core/repo/files.scala.html b/src/main/twirl/gitbucket/core/repo/files.scala.html index 98202e71f..7d44d74a1 100644 --- a/src/main/twirl/gitbucket/core/repo/files.scala.html +++ b/src/main/twirl/gitbucket/core/repo/files.scala.html @@ -25,7 +25,7 @@ @if(pathList.isEmpty){ From f73daaef4455ebb437b7cc7d3db58616583fec30 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Wed, 28 Sep 2016 13:26:59 +0900 Subject: [PATCH 16/59] (refs #954)Cut commit id in Markdown with 7 letters --- src/main/scala/gitbucket/core/view/LinkConverter.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/scala/gitbucket/core/view/LinkConverter.scala b/src/main/scala/gitbucket/core/view/LinkConverter.scala index 3f2abf8e8..996fac25e 100644 --- a/src/main/scala/gitbucket/core/view/LinkConverter.scala +++ b/src/main/scala/gitbucket/core/view/LinkConverter.scala @@ -37,7 +37,7 @@ trait LinkConverter { self: RequestCache => // convert username/project@SHA to link .replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r){ m => getAccountByUserName(m.group(2)).map { _ => - s"""${m.group(2)}/${m.group(3)}@${m.group(4).substring(0, 7)}""" + s"""${m.group(2)}/${m.group(3)}@${m.group(4).substring(0, 7)}""" } } @@ -56,7 +56,7 @@ trait LinkConverter { self: RequestCache => // convert username@SHA to link .replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r ) { m => getAccountByUserName(m.group(2)).map { _ => - s"""${m.group(2)}@${m.group(3).substring(0, 7)}""" + s"""${m.group(2)}@${m.group(3).substring(0, 7)}""" } } @@ -93,6 +93,8 @@ trait LinkConverter { self: RequestCache => } // convert commit id to link - .replaceAll("(?<=(^|[^\\w/@]))([a-f0-9]{40})(?=(\\W|$))", s"""$$2""") + .replaceBy("(?<=(^|[^\\w/@]))([a-f0-9]{40})(?=(\\W|$))".r){ m => + Some(s"""${m.group(2).substring(0, 7)}""") + } } } From 53f6190267640b72503552067accd65b142215d4 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Wed, 28 Sep 2016 14:05:40 +0900 Subject: [PATCH 17/59] Scalaz's <| is deprecated --- .../core/service/ProtectedBranchServiceSpec.scala | 12 ++++++++---- .../gitbucket/core/service/ServiceSpecBase.scala | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala b/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala index b839b60cd..ce91e28bf 100644 --- a/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala @@ -53,7 +53,8 @@ class ProtectedBranchServiceSpec extends FunSpec with ServiceSpecBase with Prote it("getBranchProtectedReason on force push from admin") { withTestDB { implicit session => withTestRepository { git => - val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(true) } + val rp = new ReceivePack(git.getRepository) + rp.setAllowNonFastForwards(true) val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE_NONFASTFORWARD) generateNewUserWithDBRepository("user1", "repo1") assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == None) @@ -65,7 +66,8 @@ class ProtectedBranchServiceSpec extends FunSpec with ServiceSpecBase with Prote it("getBranchProtectedReason on force push from other") { withTestDB { implicit session => withTestRepository { git => - val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(true) } + val rp = new ReceivePack(git.getRepository) + rp.setAllowNonFastForwards(true) val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE_NONFASTFORWARD) generateNewUserWithDBRepository("user1", "repo1") assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == None) @@ -77,7 +79,8 @@ class ProtectedBranchServiceSpec extends FunSpec with ServiceSpecBase with Prote it("getBranchProtectedReason check status on push from other") { withTestDB { implicit session => withTestRepository { git => - val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(false) } + val rp = new ReceivePack(git.getRepository) + rp.setAllowNonFastForwards(false) val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE) val user1 = generateNewUserWithDBRepository("user1", "repo1") assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2") == None) @@ -97,7 +100,8 @@ class ProtectedBranchServiceSpec extends FunSpec with ServiceSpecBase with Prote it("getBranchProtectedReason check status on push from admin") { withTestDB { implicit session => withTestRepository { git => - val rp = new ReceivePack(git.getRepository) <| { _.setAllowNonFastForwards(false) } + val rp = new ReceivePack(git.getRepository) + rp.setAllowNonFastForwards(false) val rc = new ReceiveCommand(ObjectId.fromString(sha), ObjectId.fromString(sha2), "refs/heads/branch", ReceiveCommand.Type.UPDATE) val user1 = generateNewUserWithDBRepository("user1", "repo1") assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1") == None) diff --git a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala index 87eb29a7c..d5ec292c9 100644 --- a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala +++ b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala @@ -28,7 +28,8 @@ trait ServiceSpecBase { org.h2.Driver.load() using(DriverManager.getConnection(url, user, pass)){ conn => val solidbase = new Solidbase() - val db = new H2Database() <| { _.setConnection(new JdbcConnection(conn)) } // TODO Remove setConnection in the future + val db = new H2Database() + db.setConnection(new JdbcConnection(conn)) // TODO Remove setConnection in the future solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, db, GitBucketCoreModule) } Database.forURL(url, user, pass).withSession { session => From 22ae1df4b1189c404561aa1567fcc4fea453cf59 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 29 Sep 2016 10:30:34 +0900 Subject: [PATCH 18/59] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3e42e7c2..5f2f3e617 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Support Release Notes ------------- -### 4.5 - 1 Oct 2016 +### 4.5 - 29 Sep 2016 - Attach files by dropping into textarea - Issues / Pull requests switcher in dashboard - HikariCP could be configured in `GITBUCKET_HOME/database.conf` From c65599d99574954ca36a8e62ef850ad14e40a06f Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 29 Sep 2016 10:40:21 +0900 Subject: [PATCH 19/59] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5f2f3e617..262072dca 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Release Notes - Issues / Pull requests switcher in dashboard - HikariCP could be configured in `GITBUCKET_HOME/database.conf` - Improve Cookie security +- Display commit count on the history button - Improve mobile view ### 4.4 - 28 Aug 2016 From 02330a2050646b4dd7097428eb8e72128ccdf645 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sat, 1 Oct 2016 03:24:21 +0900 Subject: [PATCH 20/59] (refs #1304)Remove package artifact overriding --- build.sbt | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sbt b/build.sbt index 760aab1d9..910ff7ae1 100644 --- a/build.sbt +++ b/build.sbt @@ -177,7 +177,6 @@ publishTo <<= version { (v: String) => } publishMavenStyle := true pomIncludeRepository := { _ => false } -artifact in Keys.`package` := Artifact(moduleName.value) pomExtra := ( https://github.com/gitbucket/gitbucket From 23fa937fd1e6bfbb480b995ece0888e839928f4e Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 2 Oct 2016 02:27:48 +0900 Subject: [PATCH 21/59] Remove unnecessary lines --- build.sbt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/build.sbt b/build.sbt index 910ff7ae1..4dece9472 100644 --- a/build.sbt +++ b/build.sbt @@ -106,7 +106,6 @@ libraryDependencies ++= Seq( val executableKey = TaskKey[File]("executable") executableKey := { - import org.apache.ivy.util.ChecksumHelper import java.util.jar.{ Manifest => JarManifest } import java.util.jar.Attributes.{ Name => AttrName } @@ -164,12 +163,6 @@ executableKey := { log info s"built executable webapp ${outputFile}" outputFile } -/* -Keys.artifact in (Compile, executableKey) ~= { - _ copy (`type` = "war", extension = "war")) -} -addArtifact(Keys.artifact in (Compile, executableKey), executableKey) -*/ publishTo <<= version { (v: String) => val nexus = "https://oss.sonatype.org/" if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots") From 28c9f8b89a0466228faa2e9e907f5ef9c3ac3993 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 3 Oct 2016 01:42:56 +0900 Subject: [PATCH 22/59] (refs #1308)Fix sorting in issue query --- src/main/scala/gitbucket/core/service/IssuesService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala index 37f7deb8e..ad6546b76 100644 --- a/src/main/scala/gitbucket/core/service/IssuesService.scala +++ b/src/main/scala/gitbucket/core/service/IssuesService.scala @@ -221,7 +221,7 @@ trait IssuesService { case "desc" => sort desc } } - } + }.sortBy { case (t1, t2) => t1.issueId desc } .drop(offset).take(limit) From 82b102845fc4ce34270617af7358a9d4c6aed9b2 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 3 Oct 2016 15:26:23 +0900 Subject: [PATCH 23/59] (refs #1292)Add new option to disable repository forking --- .../resources/update/gitbucket-core_4.6.xml | 6 + .../gitbucket/core/GitBucketCoreModule.scala | 5 +- .../core/controller/AccountController.scala | 127 +++++++++--------- .../RepositorySettingsController.scala | 9 +- .../RepositoryViewerController.scala | 26 ++-- .../core/controller/WikiController.scala | 2 +- .../gitbucket/core/model/Repository.scala | 77 ++++++++--- .../core/service/RepositoryService.scala | 22 +-- src/main/twirl/gitbucket/core/menu.scala.html | 12 +- .../core/settings/options.scala.html | 21 ++- 10 files changed, 192 insertions(+), 115 deletions(-) create mode 100644 src/main/resources/update/gitbucket-core_4.6.xml diff --git a/src/main/resources/update/gitbucket-core_4.6.xml b/src/main/resources/update/gitbucket-core_4.6.xml new file mode 100644 index 000000000..6bf4acfd0 --- /dev/null +++ b/src/main/resources/update/gitbucket-core_4.6.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index a404be3a3..6830d2d67 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -15,5 +15,8 @@ object GitBucketCoreModule extends Module("gitbucket-core", new Version("4.2.1"), new Version("4.3.0"), new Version("4.4.0"), - new Version("4.5.0") + new Version("4.5.0"), + new Version("4.6.0", + new LiquibaseMigration("update/gitbucket-core_4.6.xml") + ) ) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index c0f0212d2..0308a4abd 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -14,6 +14,7 @@ import gitbucket.core.util._ import io.github.gitbucket.scalatra.forms._ import org.apache.commons.io.FileUtils import org.scalatra.i18n.Messages +import org.scalatra.BadRequest class AccountController extends AccountControllerBase @@ -355,76 +356,80 @@ trait AccountControllerBase extends AccountManagementControllerBase { }) get("/:owner/:repository/fork")(readableUsersOnly { repository => - val loginAccount = context.loginAccount.get - val loginUserName = loginAccount.userName - val groups = getGroupsByUserName(loginUserName) - groups match { - case _: List[String] => - val managerPermissions = groups.map { group => - val members = getGroupMembers(group) - context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }) - } - helper.html.forkrepository( - repository, - (groups zip managerPermissions).toMap - ) - case _ => redirect(s"/${loginUserName}") - } + if(repository.repository.options.allowFork){ + val loginAccount = context.loginAccount.get + val loginUserName = loginAccount.userName + val groups = getGroupsByUserName(loginUserName) + groups match { + case _: List[String] => + val managerPermissions = groups.map { group => + val members = getGroupMembers(group) + context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }) + } + helper.html.forkrepository( + repository, + (groups zip managerPermissions).toMap + ) + case _ => redirect(s"/${loginUserName}") + } + } else BadRequest }) post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) => - val loginAccount = context.loginAccount.get - val loginUserName = loginAccount.userName - val accountName = form.accountName + if(repository.repository.options.allowFork){ + val loginAccount = context.loginAccount.get + val loginUserName = loginAccount.userName + val accountName = form.accountName - LockUtil.lock(s"${accountName}/${repository.name}"){ - if(getRepository(accountName, repository.name).isDefined || - (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){ - // redirect to the repository if repository already exists - redirect(s"/${accountName}/${repository.name}") - } else { - // Insert to the database at first - val originUserName = repository.repository.originUserName.getOrElse(repository.owner) - val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) + LockUtil.lock(s"${accountName}/${repository.name}"){ + if(getRepository(accountName, repository.name).isDefined || + (accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){ + // redirect to the repository if repository already exists + redirect(s"/${accountName}/${repository.name}") + } else { + // Insert to the database at first + val originUserName = repository.repository.originUserName.getOrElse(repository.owner) + val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name) - insertRepository( - repositoryName = repository.name, - userName = accountName, - description = repository.repository.description, - isPrivate = repository.repository.isPrivate, - originRepositoryName = Some(originRepositoryName), - originUserName = Some(originUserName), - parentRepositoryName = Some(repository.name), - parentUserName = Some(repository.owner) - ) + insertRepository( + repositoryName = repository.name, + userName = accountName, + description = repository.repository.description, + isPrivate = repository.repository.isPrivate, + originRepositoryName = Some(originRepositoryName), + originUserName = Some(originUserName), + parentRepositoryName = Some(repository.name), + parentUserName = Some(repository.owner) + ) - // Add collaborators for group repository - val ownerAccount = getAccountByUserName(accountName).get - if(ownerAccount.isGroupAccount){ - getGroupMembers(accountName).foreach { member => - addCollaborator(accountName, repository.name, member.userName) + // Add collaborators for group repository + val ownerAccount = getAccountByUserName(accountName).get + if(ownerAccount.isGroupAccount){ + getGroupMembers(accountName).foreach { member => + addCollaborator(accountName, repository.name, member.userName) + } } + + // Insert default labels + insertDefaultLabels(accountName, repository.name) + + // clone repository actually + JGitUtil.cloneRepository( + getRepositoryDir(repository.owner, repository.name), + getRepositoryDir(accountName, repository.name)) + + // Create Wiki repository + JGitUtil.cloneRepository( + getWikiRepositoryDir(repository.owner, repository.name), + getWikiRepositoryDir(accountName, repository.name)) + + // Record activity + recordForkActivity(repository.owner, repository.name, loginUserName, accountName) + // redirect to the repository + redirect(s"/${accountName}/${repository.name}") } - - // Insert default labels - insertDefaultLabels(accountName, repository.name) - - // clone repository actually - JGitUtil.cloneRepository( - getRepositoryDir(repository.owner, repository.name), - getRepositoryDir(accountName, repository.name)) - - // Create Wiki repository - JGitUtil.cloneRepository( - getWikiRepositoryDir(repository.owner, repository.name), - getWikiRepositoryDir(accountName, repository.name)) - - // Record activity - recordForkActivity(repository.owner, repository.name, loginUserName, accountName) - // redirect to the repository - redirect(s"/${accountName}/${repository.name}") } - } + } else BadRequest }) private def existsAccount: Constraint = new Constraint(){ diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index b60ae5d94..6d7c9814e 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -35,7 +35,8 @@ trait RepositorySettingsControllerBase extends ControllerBase { externalIssuesUrl: Option[String], enableWiki: Boolean, allowWikiEditing: Boolean, - externalWikiUrl: Option[String] + externalWikiUrl: Option[String], + allowFork: Boolean ) val optionsForm = mapping( @@ -46,7 +47,8 @@ trait RepositorySettingsControllerBase extends ControllerBase { "externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))), "enableWiki" -> trim(label("Enable Wiki" , boolean())), "allowWikiEditing" -> trim(label("Allow Wiki Editing" , boolean())), - "externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))) + "externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))), + "allowFork" -> trim(label("Allow Forking" , boolean())) )(OptionsForm.apply) // for default branch @@ -111,7 +113,8 @@ trait RepositorySettingsControllerBase extends ControllerBase { form.externalIssuesUrl, form.enableWiki, form.allowWikiEditing, - form.externalWikiUrl + form.externalWikiUrl, + form.allowFork ) // Change repository name if(repository.name != form.repositoryName){ diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index cee8f4fc7..4307fbbfd 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -494,18 +494,20 @@ trait RepositoryViewerControllerBase extends ControllerBase { }) get("/:owner/:repository/network/members")(referrersOnly { repository => - html.forked( - getRepository( - repository.repository.originUserName.getOrElse(repository.owner), - repository.repository.originRepositoryName.getOrElse(repository.name)), - getForkedRepositories( - repository.repository.originUserName.getOrElse(repository.owner), - repository.repository.originRepositoryName.getOrElse(repository.name)), - context.loginAccount match { - case None => List() - case account: Option[Account] => getGroupsByUserName(account.get.userName) - }, // groups of current user - repository) + if(repository.repository.options.allowFork) { + html.forked( + getRepository( + repository.repository.originUserName.getOrElse(repository.owner), + repository.repository.originRepositoryName.getOrElse(repository.name)), + getForkedRepositories( + repository.repository.originUserName.getOrElse(repository.owner), + repository.repository.originRepositoryName.getOrElse(repository.name)), + context.loginAccount match { + case None => List() + case account: Option[Account] => getGroupsByUserName(account.get.userName) + }, // groups of current user + repository) + } else BadRequest() }) /** diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index ad98cee33..847c42173 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -241,7 +241,7 @@ trait WikiControllerBase extends ControllerBase { private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName")) private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = - repository.repository.allowWikiEditing || ( + repository.repository.options.allowWikiEditing || ( hasWritePermission(repository.owner, repository.name, context.loginAccount) ) diff --git a/src/main/scala/gitbucket/core/model/Repository.scala b/src/main/scala/gitbucket/core/model/Repository.scala index ebdfb3ac8..c5c61bb39 100644 --- a/src/main/scala/gitbucket/core/model/Repository.scala +++ b/src/main/scala/gitbucket/core/model/Repository.scala @@ -7,24 +7,62 @@ trait RepositoryComponent extends TemplateComponent { self: Profile => lazy val Repositories = TableQuery[Repositories] class Repositories(tag: Tag) extends Table[Repository](tag, "REPOSITORY") with BasicTemplate { - val isPrivate = column[Boolean]("PRIVATE") - val description = column[String]("DESCRIPTION") - val defaultBranch = column[String]("DEFAULT_BRANCH") - val registeredDate = column[java.util.Date]("REGISTERED_DATE") - val updatedDate = column[java.util.Date]("UPDATED_DATE") - val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") - val originUserName = column[String]("ORIGIN_USER_NAME") + val isPrivate = column[Boolean]("PRIVATE") + val description = column[String]("DESCRIPTION") + val defaultBranch = column[String]("DEFAULT_BRANCH") + val registeredDate = column[java.util.Date]("REGISTERED_DATE") + val updatedDate = column[java.util.Date]("UPDATED_DATE") + val lastActivityDate = column[java.util.Date]("LAST_ACTIVITY_DATE") + val originUserName = column[String]("ORIGIN_USER_NAME") val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME") - val parentUserName = column[String]("PARENT_USER_NAME") + val parentUserName = column[String]("PARENT_USER_NAME") val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME") - val enableIssues = column[Boolean]("ENABLE_ISSUES") - val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL") - val enableWiki = column[Boolean]("ENABLE_WIKI") - val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING") - val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL") - def * = (userName, repositoryName, isPrivate, description.?, defaultBranch, - registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?, - enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?) <> (Repository.tupled, Repository.unapply) + val enableIssues = column[Boolean]("ENABLE_ISSUES") + val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL") + val enableWiki = column[Boolean]("ENABLE_WIKI") + val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING") + val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL") + val allowFork = column[Boolean]("ALLOW_FORK") + + def * = ( + (userName, repositoryName, isPrivate, description.?, defaultBranch, + registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?), + (enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?, allowFork) + ).shaped <> ( + { case (repository, options) => + Repository( + repository._1, + repository._2, + repository._3, + repository._4, + repository._5, + repository._6, + repository._7, + repository._8, + repository._9, + repository._10, + repository._11, + repository._12, + RepositoryOptions.tupled.apply(options) + ) + }, { (r: Repository) => + Some((( + r.userName, + r.repositoryName, + r.isPrivate, + r.description, + r.defaultBranch, + r.registeredDate, + r.updatedDate, + r.lastActivityDate, + r.originUserName, + r.originRepositoryName, + r.parentUserName, + r.parentRepositoryName + ),( + RepositoryOptions.unapply(r.options).get + ))) + }) def byPrimaryKey(owner: String, repository: String) = byRepository(owner, repository) } @@ -43,9 +81,14 @@ case class Repository( originRepositoryName: Option[String], parentUserName: Option[String], parentRepositoryName: Option[String], + options: RepositoryOptions +) + +case class RepositoryOptions( enableIssues: Boolean, externalIssuesUrl: Option[String], enableWiki: Boolean, allowWikiEditing: Boolean, - externalWikiUrl: Option[String] + externalWikiUrl: Option[String], + allowFork: Boolean ) diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 959e735f9..3541c0786 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -1,7 +1,7 @@ package gitbucket.core.service import gitbucket.core.controller.Context -import gitbucket.core.model.{Collaborator, Repository, Account} +import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account} import gitbucket.core.model.Profile._ import gitbucket.core.util.JGitUtil import profile.simple._ @@ -37,11 +37,14 @@ trait RepositoryService { self: AccountService => originRepositoryName = originRepositoryName, parentUserName = parentUserName, parentRepositoryName = parentRepositoryName, - enableIssues = true, - externalIssuesUrl = None, - enableWiki = true, - allowWikiEditing = true, - externalWikiUrl = None + options = RepositoryOptions( + enableIssues = true, + externalIssuesUrl = None, + enableWiki = true, + allowWikiEditing = true, + externalWikiUrl = None, + allowFork = true + ) ) IssueId insert (userName, repositoryName, 0) @@ -321,10 +324,11 @@ trait RepositoryService { self: AccountService => def saveRepositoryOptions(userName: String, repositoryName: String, description: Option[String], isPrivate: Boolean, enableIssues: Boolean, externalIssuesUrl: Option[String], - enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String])(implicit s: Session): Unit = + enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String], + allowFork: Boolean)(implicit s: Session): Unit = Repositories.filter(_.byRepository(userName, repositoryName)) - .map { r => (r.description.?, r.isPrivate, r.enableIssues, r.externalIssuesUrl.?, r.enableWiki, r.allowWikiEditing, r.externalWikiUrl.?, r.updatedDate) } - .update (description, isPrivate, enableIssues, externalIssuesUrl, enableWiki, allowWikiEditing, externalWikiUrl, currentDate) + .map { r => (r.description.?, r.isPrivate, r.enableIssues, r.externalIssuesUrl.?, r.enableWiki, r.allowWikiEditing, r.externalWikiUrl.?, r.allowFork, r.updatedDate) } + .update (description, isPrivate, enableIssues, externalIssuesUrl, enableWiki, allowWikiEditing, externalWikiUrl, allowFork, currentDate) def saveRepositoryDefaultBranch(userName: String, repositoryName: String, defaultBranch: String)(implicit s: Session): Unit = diff --git a/src/main/twirl/gitbucket/core/menu.scala.html b/src/main/twirl/gitbucket/core/menu.scala.html index 061fa0207..cefd82c70 100644 --- a/src/main/twirl/gitbucket/core/menu.scala.html +++ b/src/main/twirl/gitbucket/core/menu.scala.html @@ -27,24 +27,26 @@ @menuitem("/branches", "branches", "Branches", "git-branch", repository.branchList.length) @menuitem("/tags", "tags", "Tags", "tag", repository.tags.length) } - @if(repository.repository.enableIssues) { + @if(repository.repository.options.enableIssues) { @menuitem("/issues", "issues", "Issues", "issue-opened", repository.issueCount) @menuitem("/pulls", "pulls", "Pull Requests", "git-pull-request", repository.pullCount) @menuitem("/issues/labels", "labels", "Labels", "tag") @menuitem("/issues/milestones", "milestones", "Milestones", "milestone") } else { - @repository.repository.externalIssuesUrl.map { externalIssuesUrl => + @repository.repository.options.externalIssuesUrl.map { externalIssuesUrl => @menuitem(externalIssuesUrl, "issues", "Issues", "issue-opened") } } - @if(repository.repository.enableWiki) { + @if(repository.repository.options.enableWiki) { @menuitem("/wiki", "wiki", "Wiki", "book") } else { - @repository.repository.externalWikiUrl.map { externalWikiUrl => + @repository.repository.options.externalWikiUrl.map { externalWikiUrl => @menuitem(externalWikiUrl, "wiki", "Wiki", "book") } } - @menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount) + @if(repository.repository.options.allowFork) { + @menuitem("/network/members", "fork", "Forks", "repo-forked", repository.forkedCount) + } @if(context.loginAccount.isDefined && (context.loginAccount.get.isAdmin || repository.managers.contains(context.loginAccount.get.userName))){ @menuitem("/settings", "settings", "Settings", "tools") } diff --git a/src/main/twirl/gitbucket/core/settings/options.scala.html b/src/main/twirl/gitbucket/core/settings/options.scala.html index 490da7002..7b263102b 100644 --- a/src/main/twirl/gitbucket/core/settings/options.scala.html +++ b/src/main/twirl/gitbucket/core/settings/options.scala.html @@ -46,7 +46,7 @@
    -
    +
    - + +
    +
    +
    From 2621de2cde8a3d16740232d99f5819b52847a156 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 3 Oct 2016 16:01:57 +0900 Subject: [PATCH 24/59] Fix error responses --- .../core/controller/AccountController.scala | 22 ++++++------ .../core/controller/ApiController.scala | 26 +++++++------- .../core/controller/ControllerBase.scala | 2 +- .../controller/FileUploadController.scala | 9 +++-- .../core/controller/IssuesController.scala | 36 +++++++++---------- .../controller/MilestonesController.scala | 10 +++--- .../controller/PullRequestsController.scala | 16 ++++----- .../RepositorySettingsController.scala | 2 +- .../RepositoryViewerController.scala | 32 ++++++++--------- .../controller/SystemSettingsController.scala | 4 +-- .../core/controller/WikiController.scala | 2 +- 11 files changed, 80 insertions(+), 81 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index 0308a4abd..7d3d1eb43 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -134,7 +134,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })) } } - } getOrElse NotFound + } getOrElse NotFound() } get("/:userName.atom") { @@ -157,7 +157,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { val userName = params("userName") getAccountByUserName(userName).map { x => html.edit(x, flash.get("info"), flash.get("error")) - } getOrElse NotFound + } getOrElse NotFound() }) post("/:userName/_edit", editForm)(oneselfOnly { form => @@ -173,7 +173,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { flash += "info" -> "Account information has been updated." redirect(s"/${userName}/_edit") - } getOrElse NotFound + } getOrElse NotFound() }) get("/:userName/_delete")(oneselfOnly { @@ -197,14 +197,14 @@ trait AccountControllerBase extends AccountManagementControllerBase { session.invalidate redirect("/") } - } getOrElse NotFound + } getOrElse NotFound() }) get("/:userName/_ssh")(oneselfOnly { val userName = params("userName") getAccountByUserName(userName).map { x => html.ssh(x, getPublicKeys(x.userName)) - } getOrElse NotFound + } getOrElse NotFound() }) post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form => @@ -235,7 +235,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { case _ => None } html.application(x, tokens, generatedToken) - } getOrElse NotFound + } getOrElse NotFound() }) post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form => @@ -261,7 +261,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { } else { html.register() } - } else NotFound + } else NotFound() } post("/register", newForm){ form => @@ -269,7 +269,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.url) updateImage(form.userName, form.fileId, false) redirect("/signin") - } else NotFound + } else NotFound() } get("/groups/new")(usersOnly { @@ -330,7 +330,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { updateImage(form.groupName, form.fileId, form.clearImage) redirect(s"/${form.groupName}") - } getOrElse NotFound + } getOrElse NotFound() } }) @@ -372,7 +372,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { ) case _ => redirect(s"/${loginUserName}") } - } else BadRequest + } else BadRequest() }) post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) => @@ -429,7 +429,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { redirect(s"/${accountName}/${repository.name}") } } - } else BadRequest + } else BadRequest() }) private def existsAccount: Constraint = new Constraint(){ diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 9a2f2a4e5..d535a5855 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -66,7 +66,7 @@ trait ApiControllerBase extends ControllerBase { get("/api/v3/orgs/:groupName") { getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account => JsonFormat(ApiUser(account)) - } getOrElse NotFound + } getOrElse NotFound() } /** @@ -75,7 +75,7 @@ trait ApiControllerBase extends ControllerBase { get("/api/v3/users/:userName") { getAccountByUserName(params("userName")).filterNot(account => account.isGroupAccount).map { account => JsonFormat(ApiUser(account)) - } getOrElse NotFound + } getOrElse NotFound() } /** @@ -145,7 +145,7 @@ trait ApiControllerBase extends ControllerBase { get("/api/v3/user") { context.loginAccount.map { account => JsonFormat(ApiUser(account)) - } getOrElse Unauthorized + } getOrElse Unauthorized() } /** @@ -179,7 +179,7 @@ trait ApiControllerBase extends ControllerBase { ) } } - }) getOrElse NotFound + }) getOrElse NotFound() }) /** @@ -203,7 +203,7 @@ trait ApiControllerBase extends ControllerBase { ) } } - }) getOrElse NotFound + }) getOrElse NotFound() }) /** @@ -221,7 +221,7 @@ trait ApiControllerBase extends ControllerBase { disableBranchProtection(repository.owner, repository.name, branch) } JsonFormat(ApiBranch(branch, protection)(RepositoryName(repository))) - }) getOrElse NotFound + }) getOrElse NotFound() }) /** @@ -243,7 +243,7 @@ trait ApiControllerBase extends ControllerBase { comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt) } yield { JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) }) - }).getOrElse(NotFound) + }) getOrElse NotFound() }) /** @@ -259,7 +259,7 @@ trait ApiControllerBase extends ControllerBase { issueComment <- getComment(repository.owner, repository.name, id.toString()) } yield { JsonFormat(ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest)) - }) getOrElse NotFound + }) getOrElse NotFound() }) /** @@ -393,7 +393,7 @@ trait ApiControllerBase extends ControllerBase { ApiRepository(headRepo, ApiUser(headOwner)), ApiRepository(repository, ApiUser(baseOwner)), ApiUser(issueUser))) - }).getOrElse(NotFound) + }) getOrElse NotFound() }) /** @@ -412,7 +412,7 @@ trait ApiControllerBase extends ControllerBase { JsonFormat(commits) } } - } getOrElse NotFound + } getOrElse NotFound() }) /** @@ -437,7 +437,7 @@ trait ApiControllerBase extends ControllerBase { status <- getCommitStatus(repository.owner, repository.name, statusId) } yield { JsonFormat(ApiCommitStatus(status, ApiUser(creator))) - }) getOrElse NotFound + }) getOrElse NotFound() }) /** @@ -453,7 +453,7 @@ trait ApiControllerBase extends ControllerBase { JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) => ApiCommitStatus(status, ApiUser(creator)) }) - }) getOrElse NotFound + }) getOrElse NotFound() }) /** @@ -478,7 +478,7 @@ trait ApiControllerBase extends ControllerBase { } yield { val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha) JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner))) - }) getOrElse NotFound + }) getOrElse NotFound() }) private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean = diff --git a/src/main/scala/gitbucket/core/controller/ControllerBase.scala b/src/main/scala/gitbucket/core/controller/ControllerBase.scala index db795dda2..ef1a634b0 100644 --- a/src/main/scala/gitbucket/core/controller/ControllerBase.scala +++ b/src/main/scala/gitbucket/core/controller/ControllerBase.scala @@ -248,7 +248,7 @@ trait AccountManagementControllerBase extends ControllerBase { 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") - }else{ + } else { None } } diff --git a/src/main/scala/gitbucket/core/controller/FileUploadController.scala b/src/main/scala/gitbucket/core/controller/FileUploadController.scala index ac90bbc33..d26434113 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 // Check whether logged-in user is collaborator collaboratorsOnly(owner, repository, loginAccount){ execute({ (file, fileId) => - val fileName = file.getName + val fileName = file.getName LockUtil.lock(s"${owner}/${repository}/wiki") { using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git => val builder = DirCache.newInCore.builder() @@ -75,7 +75,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R } }, FileUtil.isUploadableType) } - } getOrElse BadRequest + } getOrElse BadRequest() } post("/import") { @@ -93,7 +93,7 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R loginAccount match { case x if(x.isAdmin) => action case x if(getCollaborators(owner, repository).contains(x.userName)) => action - case _ => BadRequest + case _ => BadRequest() } } @@ -101,10 +101,9 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R case Some(file) if(mimeTypeChcker(file.name)) => defining(FileUtil.generateFileId){ fileId => f(file, fileId) - Ok(fileId) } - case _ => BadRequest + case _ => BadRequest() } } diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index f425b6028..954e92f08 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -72,7 +72,7 @@ trait IssuesControllerBase extends ControllerBase { getLabels(owner, name), hasWritePermission(owner, name, context.loginAccount), repository) - } getOrElse NotFound + } getOrElse NotFound() } }) @@ -139,8 +139,8 @@ trait IssuesControllerBase extends ControllerBase { createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get) redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() } }) @@ -154,8 +154,8 @@ trait IssuesControllerBase extends ControllerBase { createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get) redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}") - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() } }) @@ -166,7 +166,7 @@ trait IssuesControllerBase extends ControllerBase { redirect(s"/${repository.owner}/${repository.name}/${ if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") } - } getOrElse NotFound + } getOrElse NotFound() }) post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) => @@ -176,7 +176,7 @@ trait IssuesControllerBase extends ControllerBase { redirect(s"/${repository.owner}/${repository.name}/${ if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") } - } getOrElse NotFound + } getOrElse NotFound() }) ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => @@ -185,8 +185,8 @@ trait IssuesControllerBase extends ControllerBase { if(isEditable(owner, name, comment.commentedUserName)){ updateComment(comment.commentId, form.content) redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}") - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() } }) @@ -195,8 +195,8 @@ trait IssuesControllerBase extends ControllerBase { getComment(owner, name, params("id")).map { comment => if(isEditable(owner, name, comment.commentedUserName)){ Ok(deleteComment(comment.commentId)) - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() } }) @@ -223,8 +223,8 @@ trait IssuesControllerBase extends ControllerBase { ) ) } - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() }) ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository => @@ -249,8 +249,8 @@ trait IssuesControllerBase extends ControllerBase { ) ) } - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() }) ajaxPost("/:owner/:repository/issues/new/label")(collaboratorsOnly { repository => @@ -284,7 +284,7 @@ trait IssuesControllerBase extends ControllerBase { getMilestonesWithIssueCount(repository.owner, repository.name) .find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) => gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount) - } getOrElse NotFound + } getOrElse NotFound() } getOrElse Ok() }) @@ -313,7 +313,7 @@ trait IssuesControllerBase extends ControllerBase { registerIssueLabel(repository.owner, repository.name, issueId, labelId) } } - } getOrElse NotFound + } getOrElse NotFound() }) post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository => @@ -340,7 +340,7 @@ trait IssuesControllerBase extends ControllerBase { RawData(FileUtil.getMimeType(file.getName), file) } case _ => None - }) getOrElse NotFound + }) getOrElse NotFound() }) val assignedUserName = (key: String) => params.get(key) filter (_.trim != "") diff --git a/src/main/scala/gitbucket/core/controller/MilestonesController.scala b/src/main/scala/gitbucket/core/controller/MilestonesController.scala index 50cd0683e..323b11445 100644 --- a/src/main/scala/gitbucket/core/controller/MilestonesController.scala +++ b/src/main/scala/gitbucket/core/controller/MilestonesController.scala @@ -42,7 +42,7 @@ trait MilestonesControllerBase extends ControllerBase { get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository => params("milestoneId").toIntOpt.map{ milestoneId => html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository) - } getOrElse NotFound + } getOrElse NotFound() }) post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(collaboratorsOnly { (form, repository) => @@ -51,7 +51,7 @@ trait MilestonesControllerBase extends ControllerBase { updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate)) redirect(s"/${repository.owner}/${repository.name}/issues/milestones") } - } getOrElse NotFound + } getOrElse NotFound() }) get("/:owner/:repository/issues/milestones/:milestoneId/close")(collaboratorsOnly { repository => @@ -60,7 +60,7 @@ trait MilestonesControllerBase extends ControllerBase { closeMilestone(milestone) redirect(s"/${repository.owner}/${repository.name}/issues/milestones") } - } getOrElse NotFound + } getOrElse NotFound() }) get("/:owner/:repository/issues/milestones/:milestoneId/open")(collaboratorsOnly { repository => @@ -69,7 +69,7 @@ trait MilestonesControllerBase extends ControllerBase { openMilestone(milestone) redirect(s"/${repository.owner}/${repository.name}/issues/milestones") } - } getOrElse NotFound + } getOrElse NotFound() }) get("/:owner/:repository/issues/milestones/:milestoneId/delete")(collaboratorsOnly { repository => @@ -78,7 +78,7 @@ trait MilestonesControllerBase extends ControllerBase { deleteMilestone(repository.owner, repository.name, milestone.milestoneId) redirect(s"/${repository.owner}/${repository.name}/issues/milestones") } - } getOrElse NotFound + } getOrElse NotFound() }) } diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 803ab5100..f3bd241ab 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -104,7 +104,7 @@ trait PullRequestsControllerBase extends ControllerBase { flash.toMap.map(f => f._1 -> f._2.toString)) } } - } getOrElse NotFound + } getOrElse NotFound() }) ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository => @@ -138,7 +138,7 @@ trait PullRequestsControllerBase extends ControllerBase { repository, getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get) } - } getOrElse NotFound + } getOrElse NotFound() }) get("/:owner/:repository/pull/:id/delete/*")(collaboratorsOnly { repository => @@ -153,7 +153,7 @@ trait PullRequestsControllerBase extends ControllerBase { } createComment(repository.owner, repository.name, userName, issueId, branchName, "delete_branch") redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") - } getOrElse NotFound + } getOrElse NotFound() }) post("/:owner/:repository/pull/:id/update_branch")(referrersOnly { baseRepository => @@ -222,7 +222,7 @@ trait PullRequestsControllerBase extends ControllerBase { } redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}") } - }) getOrElse NotFound + }) getOrElse NotFound() }) post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) => @@ -273,7 +273,7 @@ trait PullRequestsControllerBase extends ControllerBase { } } } - } getOrElse NotFound + } getOrElse NotFound() }) get("/:owner/:repository/compare")(referrersOnly { forkedRepository => @@ -290,7 +290,7 @@ trait PullRequestsControllerBase extends ControllerBase { redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}") } - } getOrElse NotFound + } getOrElse NotFound() } case _ => { using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git => @@ -386,7 +386,7 @@ trait PullRequestsControllerBase extends ControllerBase { s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}") } } - }) getOrElse NotFound + }) getOrElse NotFound() }) ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { forkedRepository => @@ -416,7 +416,7 @@ trait PullRequestsControllerBase extends ControllerBase { } html.mergecheck(conflict) } - }) getOrElse NotFound + }) getOrElse NotFound() }) post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) => diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index 6d7c9814e..41455cc08 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -300,7 +300,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository => getWebHook(repository.owner, repository.name, params("url")).map{ case (webhook, events) => html.edithooks(webhook, events, repository, flash.get("info"), false) - } getOrElse NotFound + } getOrElse NotFound() }) /** diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index 4307fbbfd..f61b4062c 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -152,7 +152,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { logs.splitWith{ (commit1, commit2) => view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) }, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount)) - case Left(_) => NotFound + case Left(_) => NotFound() } } }) @@ -177,7 +177,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last), JGitUtil.getContentInfo(git, path, objectId), protectedBranch) - } getOrElse NotFound + } getOrElse NotFound() } }) @@ -190,7 +190,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { val paths = path.split("/") html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last, JGitUtil.getContentInfo(git, path, objectId)) - } getOrElse NotFound + } getOrElse NotFound() } }) @@ -250,7 +250,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { loader.copyTo(response.outputStream) () } - } getOrElse NotFound + } getOrElse NotFound() } }) @@ -270,7 +270,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { response.setContentLength(loader.getSize.toInt) loader.copyTo(response.outputStream) () - } getOrElse NotFound + } getOrElse NotFound() } else { html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId), @@ -278,7 +278,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { hasWritePermission(repository.owner, repository.name, context.loginAccount), request.paths(2) == "blame") } - } getOrElse NotFound + } getOrElse NotFound() } }) @@ -334,7 +334,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { } } } catch { - case e:MissingObjectException => NotFound + case e:MissingObjectException => NotFound() } }) @@ -397,8 +397,8 @@ trait RepositoryViewerControllerBase extends ControllerBase { ) )) } - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() }) ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => @@ -407,8 +407,8 @@ trait RepositoryViewerControllerBase extends ControllerBase { if(isEditable(owner, name, comment.commentedUserName)){ updateCommitComment(comment.commentId, form.content) redirect(s"/${owner}/${name}/commit_comments/_data/${comment.commentId}") - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() } }) @@ -417,8 +417,8 @@ trait RepositoryViewerControllerBase extends ControllerBase { getCommitComment(owner, name, params("id")).map { comment => if(isEditable(owner, name, comment.commentedUserName)){ Ok(deleteCommitComment(comment.commentId)) - } else Unauthorized - } getOrElse NotFound + } else Unauthorized() + } getOrElse NotFound() } }) @@ -489,7 +489,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { archiveRepository(name, ".zip", repository) case name if name.endsWith(".tar.gz") => archiveRepository(name, ".tar.gz", repository) - case _ => BadRequest + case _ => BadRequest() } }) @@ -518,7 +518,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { val ref = multiParams("splat").head JGitUtil.getTreeId(git, ref).map{ treeId => html.find(ref, treeId, repository) - } getOrElse NotFound + } getOrElse NotFound() } }) @@ -573,7 +573,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch), flash.get("info"), flash.get("error")) } - } getOrElse NotFound + } getOrElse NotFound() } } } diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index deaf4d0fb..7a6874612 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -233,7 +233,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { updateImage(userName, form.fileId, form.clearImage) redirect("/admin/users") } - } getOrElse NotFound + } getOrElse NotFound() }) get("/admin/users/_newgroup")(adminOnly { @@ -291,7 +291,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { updateImage(form.groupName, form.fileId, form.clearImage) redirect("/admin/users") - } getOrElse NotFound + } getOrElse NotFound() } }) diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index 847c42173..29f0569ee 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -201,7 +201,7 @@ trait WikiControllerBase extends ControllerBase { getFileContent(repository.owner, repository.name, path).map { bytes => RawData(FileUtil.getContentType(path, bytes), bytes) - } getOrElse NotFound + } getOrElse NotFound() }) private def unique: Constraint = new Constraint(){ From 28ee80b727b39cfc73131ddf39afe3671d5dad12 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 3 Oct 2016 16:55:20 +0900 Subject: [PATCH 25/59] (refs #1129)Not delete from REPOSITORY table when user is disabled --- src/main/scala/gitbucket/core/service/AccountService.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scala/gitbucket/core/service/AccountService.scala b/src/main/scala/gitbucket/core/service/AccountService.scala index 06133fce0..d583255d3 100644 --- a/src/main/scala/gitbucket/core/service/AccountService.scala +++ b/src/main/scala/gitbucket/core/service/AccountService.scala @@ -181,7 +181,6 @@ trait AccountService { def removeUserRelatedData(userName: String)(implicit s: Session): Unit = { GroupMembers.filter(_.userName === userName.bind).delete Collaborators.filter(_.collaboratorName === userName.bind).delete - Repositories.filter(_.userName === userName.bind).delete } def getGroupNames(userName: String)(implicit s: Session): List[String] = { From 872320ccab03efab0802959908cec8c7b3127cc7 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 4 Oct 2016 10:28:47 +0900 Subject: [PATCH 26/59] (refs #1129)Not use Option.get for non-able value --- .../scala/gitbucket/core/controller/IssuesController.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 954e92f08..6ddcd40e8 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -67,7 +67,7 @@ trait IssuesControllerBase extends ControllerBase { _, getComments(owner, name, issueId.toInt), getIssueLabels(owner, name, issueId.toInt), - (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, + (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted, getMilestonesWithIssueCount(owner, name), getLabels(owner, name), hasWritePermission(owner, name, context.loginAccount), @@ -79,7 +79,7 @@ trait IssuesControllerBase extends ControllerBase { get("/:owner/:repository/issues/new")(readableUsersOnly { repository => defining(repository.owner, repository.name){ case (owner, name) => html.create( - (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, + (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted, getMilestones(owner, name), getLabels(owner, name), hasWritePermission(owner, name, context.loginAccount), From 4d13282915b77686d152508b720d012a920b9d3a Mon Sep 17 00:00:00 2001 From: xuwei-k <6b656e6a69@gmail.com> Date: Wed, 5 Oct 2016 11:12:58 +0900 Subject: [PATCH 27/59] remove unused scalaz --- build.sbt | 1 - .../gitbucket/core/service/ProtectedBranchServiceSpec.scala | 3 +-- src/test/scala/gitbucket/core/service/ServiceSpecBase.scala | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 4dece9472..cd54ac692 100644 --- a/build.sbt +++ b/build.sbt @@ -50,7 +50,6 @@ libraryDependencies ++= Seq( "javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided", "junit" % "junit" % "4.12" % "test", "org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test", - "org.scalaz" %% "scalaz-core" % "7.2.4" % "test", "com.wix" % "wix-embedded-mysql" % "1.0.3" % "test", "ru.yandex.qatools.embed" % "postgresql-embedded" % "1.14" % "test" ) diff --git a/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala b/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala index ce91e28bf..a2c2edf5d 100644 --- a/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/ProtectedBranchServiceSpec.scala @@ -5,7 +5,6 @@ import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand} import org.eclipse.jgit.lib.ObjectId import gitbucket.core.model.CommitState import gitbucket.core.service.ProtectedBranchService.{ProtectedBranchReceiveHook, ProtectedBranchInfo} -import scalaz._, Scalaz._ import org.scalatest.FunSpec class ProtectedBranchServiceSpec extends FunSpec with ServiceSpecBase with ProtectedBranchService with CommitStatusService { @@ -187,4 +186,4 @@ class ProtectedBranchServiceSpec extends FunSpec with ServiceSpecBase with Prote } } } -} \ No newline at end of file +} diff --git a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala index d5ec292c9..0bf208ba1 100644 --- a/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala +++ b/src/test/scala/gitbucket/core/service/ServiceSpecBase.scala @@ -9,8 +9,6 @@ import io.github.gitbucket.solidbase.Solidbase import liquibase.database.core.H2Database import liquibase.database.jvm.JdbcConnection import profile.simple._ -import scalaz._ -import Scalaz._ import org.apache.commons.io.FileUtils From 9c5d3edc722f22d26fa70d4efe98f69439f8d99f Mon Sep 17 00:00:00 2001 From: Keiichi Watanabe Date: Mon, 10 Oct 2016 00:50:38 +0900 Subject: [PATCH 28/59] Add API to get a file content --- .../gitbucket/core/api/ApiContents.scala | 17 ++++++++++----- .../core/controller/ApiController.scala | 21 +++++++++++++++++-- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/main/scala/gitbucket/core/api/ApiContents.scala b/src/main/scala/gitbucket/core/api/ApiContents.scala index db7032015..1582370c1 100644 --- a/src/main/scala/gitbucket/core/api/ApiContents.scala +++ b/src/main/scala/gitbucket/core/api/ApiContents.scala @@ -1,11 +1,18 @@ package gitbucket.core.api import gitbucket.core.util.JGitUtil.FileInfo +import org.apache.commons.codec.binary.Base64 -case class ApiContents(`type`: String, name: String) +case class ApiContents(`type`: String, name: String, content: Option[String], encoding: Option[String]) object ApiContents{ - def apply(fileInfo: FileInfo): ApiContents = - if(fileInfo.isDirectory) ApiContents("dir", fileInfo.name) - else ApiContents("file", fileInfo.name) -} \ No newline at end of file + def apply(fileInfo: FileInfo, content: Option[Array[Byte]]): ApiContents = { + if(fileInfo.isDirectory) { + ApiContents("dir", fileInfo.name, None, None) + } else { + content.map(arr => + ApiContents("file", fileInfo.name, Some(Base64.encodeBase64String(arr)), Some("base64")) + ).getOrElse(ApiContents("file", fileInfo.name, None, None)) + } + } +} diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index d535a5855..1a2114e75 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -7,7 +7,7 @@ import gitbucket.core.service.PullRequestService._ import gitbucket.core.service._ import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.Directory._ -import gitbucket.core.util.JGitUtil.{CommitInfo, getFileList, getBranches, getDefaultBranch} +import gitbucket.core.util.JGitUtil._ import gitbucket.core.util._ import gitbucket.core.util.Implicits._ import org.eclipse.jgit.api.Git @@ -109,13 +109,30 @@ trait ApiControllerBase extends ControllerBase { * https://developer.github.com/v3/repos/contents/#get-contents */ get("/api/v3/repos/:owner/:repo/contents/*")(referrersOnly { repository => + def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = { + val path = new java.io.File(pathStr) + val dirName = path.getParent match { + case null => "." + case s => s + } + getFileList(git, revision, dirName).find(f => f.name.equals(path.getName)) + } + val path = multiParams("splat").head match { case s if s.isEmpty => "." case s => s } val refStr = params.getOrElse("ref", repository.repository.defaultBranch) + using(Git.open(getRepositoryDir(params("owner"), params("repo")))){ git => - JsonFormat(getFileList(git, refStr, path).map{f => ApiContents(f)}) + val fileList = getFileList(git, refStr, path) + if (fileList.isEmpty) { // file or NotFound + getFileInfo(git, refStr, path).map(f => { + JsonFormat(ApiContents(f, getContentFromId(git, f.id, true))) + }).getOrElse(NotFound()) + } else { // directory + JsonFormat(fileList.map{f => ApiContents(f, None)}) + } } }) From b52981a845bb1beed05f3998483ee50d3dbb3cde Mon Sep 17 00:00:00 2001 From: Hidetake Iwata Date: Sat, 8 Oct 2016 23:21:08 +0900 Subject: [PATCH 29/59] Provide GitHub compatible URL for Git clients --- src/main/scala/ScalatraBootstrap.scala | 11 +++--- .../GHCompatRepositoryAccessFilter.scala | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/main/scala/gitbucket/core/servlet/GHCompatRepositoryAccessFilter.scala diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index 623fded9b..949bf80d9 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,12 +1,12 @@ -import gitbucket.core.controller._ -import gitbucket.core.plugin.PluginRegistry -import gitbucket.core.servlet.{ApiAuthenticationFilter, Database, GitAuthenticationFilter, TransactionFilter} -import gitbucket.core.util.Directory import java.util.EnumSet import javax.servlet._ +import gitbucket.core.controller._ +import gitbucket.core.plugin.PluginRegistry import gitbucket.core.service.SystemSettingsService +import gitbucket.core.servlet._ +import gitbucket.core.util.Directory import org.scalatra._ @@ -25,6 +25,9 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService { context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter) context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*") + context.addFilter("ghCompatRepositoryAccessFilter", new GHCompatRepositoryAccessFilter) + context.getFilterRegistration("ghCompatRepositoryAccessFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") + // Register controllers context.mount(new AnonymousAccessController, "/*") diff --git a/src/main/scala/gitbucket/core/servlet/GHCompatRepositoryAccessFilter.scala b/src/main/scala/gitbucket/core/servlet/GHCompatRepositoryAccessFilter.scala new file mode 100644 index 000000000..0f1322d68 --- /dev/null +++ b/src/main/scala/gitbucket/core/servlet/GHCompatRepositoryAccessFilter.scala @@ -0,0 +1,36 @@ +package gitbucket.core.servlet + +import javax.servlet._ +import javax.servlet.http.{HttpServletRequest, HttpServletResponse} + +import gitbucket.core.service.SystemSettingsService + +/** + * A controller to provide GitHub compatible URL for Git clients. + */ +class GHCompatRepositoryAccessFilter extends Filter with SystemSettingsService { + + /** + * Pattern of GitHub compatible repository URL. + * /:user/:repo.git/ + */ + private val githubRepositoryPattern = """^/[^/]+/[^/]+\.git/.*""".r + + override def init(filterConfig: FilterConfig) = {} + + override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) = { + implicit val request = req.asInstanceOf[HttpServletRequest] + val response = res.asInstanceOf[HttpServletResponse] + val requestPath = request.getRequestURI.substring(request.getContextPath.length) + requestPath match { + case githubRepositoryPattern() => + response.sendRedirect(baseUrl + "/git" + requestPath) + + case _ => + chain.doFilter(req, res) + } + } + + override def destroy() = {} + +} From e415f9d24e45554544ec1cc035b82f36fdc111ed Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 13 Oct 2016 00:59:05 +0900 Subject: [PATCH 30/59] (refs #1316)Add "Page History" button to the wiki page view --- src/main/twirl/gitbucket/core/wiki/page.scala.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/twirl/gitbucket/core/wiki/page.scala.html b/src/main/twirl/gitbucket/core/wiki/page.scala.html index df38be16c..611714051 100644 --- a/src/main/twirl/gitbucket/core/wiki/page.scala.html +++ b/src/main/twirl/gitbucket/core/wiki/page.scala.html @@ -10,12 +10,13 @@ @gitbucket.core.html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){ @gitbucket.core.html.menu("wiki", repository){
    - @if(hasWritePermission){ -
    +
    + Page History + @if(hasWritePermission){ Edit Page New Page -
    - } + } +

    @pageName

    @page.committer edited this page @gitbucket.core.helper.html.datetimeago(page.time) From 6afd51bb8d888859cb941520a99b0bb6ba4dd0c1 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 13 Oct 2016 06:42:58 +0900 Subject: [PATCH 31/59] (refs #1312)Fix badge position on the side menu --- src/main/twirl/gitbucket/core/menu.scala.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/twirl/gitbucket/core/menu.scala.html b/src/main/twirl/gitbucket/core/menu.scala.html index cefd82c70..a46bff8d5 100644 --- a/src/main/twirl/gitbucket/core/menu.scala.html +++ b/src/main/twirl/gitbucket/core/menu.scala.html @@ -9,11 +9,11 @@
  • @if(path.startsWith("http")){ - @label @if(count > 0) { @count } + @label @if(count > 0) { @count } } else { - @label @if(count > 0) { @count } + @label @if(count > 0) { @count } }
  • From 0f189ca71095b6be289e5f16d01820bac8657109 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Thu, 13 Oct 2016 20:24:01 +0900 Subject: [PATCH 32/59] (refs #1319)Get rid of the duplication of issue id extracted from commit message --- src/main/scala/gitbucket/core/util/StringUtil.scala | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/scala/gitbucket/core/util/StringUtil.scala b/src/main/scala/gitbucket/core/util/StringUtil.scala index f6e4bc040..76cc38969 100644 --- a/src/main/scala/gitbucket/core/util/StringUtil.scala +++ b/src/main/scala/gitbucket/core/util/StringUtil.scala @@ -86,8 +86,9 @@ object StringUtil { *@param message the message which may contains issue id * @return the iterator of issue id */ - def extractIssueId(message: String): Iterator[String] = - "(^|\\W)#(\\d+)(\\W|$)".r.findAllIn(message).matchData.map(_.group(2)) + def extractIssueId(message: String): Seq[String] = + "(^|\\W)#(\\d+)(\\W|$)".r + .findAllIn(message).matchData.map(_.group(2)).toSeq.distinct /** * Extract close issue id like ```close #issueId ``` from the given message. @@ -95,7 +96,8 @@ object StringUtil { * @param message the message which may contains close command * @return the iterator of issue id */ - def extractCloseId(message: String): Iterator[String] = - "(?i)(? Date: Thu, 13 Oct 2016 20:36:11 +0900 Subject: [PATCH 33/59] Create issue comment by online file editing as well --- .../core/controller/RepositoryViewerController.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala index f61b4062c..63b904efd 100644 --- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala @@ -623,8 +623,11 @@ trait RepositoryViewerControllerBase extends ControllerBase { updatePullRequests(repository.owner, repository.name, branch) // record activity - recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, - List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId)))) + 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) From ec793535e76494ab2b3929d03095db834ba865dc Mon Sep 17 00:00:00 2001 From: KOUNOIKE Yuusuke Date: Sun, 16 Oct 2016 12:44:43 +0900 Subject: [PATCH 34/59] Suppress noisy transition animation on load in IE11 http://stackoverflow.com/a/25674229 --- src/main/twirl/gitbucket/core/main.scala.html | 2 +- src/main/webapp/assets/common/css/gitbucket.css | 11 +++++++++++ src/main/webapp/assets/common/js/gitbucket.js | 3 +++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/twirl/gitbucket/core/main.scala.html b/src/main/twirl/gitbucket/core/main.scala.html index 779ae4c29..323eff462 100644 --- a/src/main/twirl/gitbucket/core/main.scala.html +++ b/src/main/twirl/gitbucket/core/main.scala.html @@ -39,7 +39,7 @@ } - +
    @userName + @helpers.avatar(member.userName, 20) @member.userName + @if(member.isManager){ (Manager) }
    } From de726d8d96bcddf2527f7c0ce0adceba49f64866 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Wed, 26 Oct 2016 12:21:21 +0900 Subject: [PATCH 39/59] (refs #1325) Prepend one more empty line when the first line is an empty line. --- src/main/twirl/gitbucket/core/repo/blob.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/twirl/gitbucket/core/repo/blob.scala.html b/src/main/twirl/gitbucket/core/repo/blob.scala.html index 78f1e7608..9ae999d06 100644 --- a/src/main/twirl/gitbucket/core/repo/blob.scala.html +++ b/src/main/twirl/gitbucket/core/repo/blob.scala.html @@ -73,7 +73,7 @@
    } else {
    -
    @content.content.get
    +
    @content.content.map(_.replaceAll("^(\r?\n)", "$1$1"))
    } } From 83a39f1e39ab8165e3a6f1463fa0a41bb2ded9b5 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sat, 29 Oct 2016 15:00:01 +0900 Subject: [PATCH 40/59] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 262072dca..eb3b2601e 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,13 @@ Support Release Notes ------------- +### 4.6 - 29 Oct 2016 +- Add disable option for forking +- Add History button to wiki page +- Git repository URL redirection for GitHub compatibility +- Get-Content API improvement +- Indicate who is group master in Members tab in group view + ### 4.5 - 29 Sep 2016 - Attach files by dropping into textarea - Issues / Pull requests switcher in dashboard From 7d3bda42e21d4638fea349673c15ee7c3099e853 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sat, 29 Oct 2016 15:00:20 +0900 Subject: [PATCH 41/59] Update version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index cd54ac692..ab09c788b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ val Organization = "io.github.gitbucket" val Name = "gitbucket" -val GitBucketVersion = "4.5.0" +val GitBucketVersion = "4.6.0" val ScalatraVersion = "2.4.1" val JettyVersion = "9.3.9.v20160517" From 60ff046823996736698afc3c84634854ee7d3747 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Mon, 31 Oct 2016 18:18:24 +0900 Subject: [PATCH 42/59] (refs #1286) Prototyping of new permission system --- .../resources/update/gitbucket-core_4.7.sql | 2 + .../resources/update/gitbucket-core_4.7.xml | 9 ++++ .../gitbucket/core/GitBucketCoreModule.scala | 4 ++ .../core/controller/IndexController.scala | 6 ++- .../RepositorySettingsController.scala | 12 +++--- .../core/service/RepositoryService.scala | 42 ++++++++++--------- .../gitbucket/core/util/Authenticator.scala | 29 +++++++------ .../gitbucket/core/helper/account.scala.html | 4 +- .../core/settings/collaborators.scala.html | 12 +----- 9 files changed, 69 insertions(+), 51 deletions(-) create mode 100644 src/main/resources/update/gitbucket-core_4.7.sql create mode 100644 src/main/resources/update/gitbucket-core_4.7.xml diff --git a/src/main/resources/update/gitbucket-core_4.7.sql b/src/main/resources/update/gitbucket-core_4.7.sql new file mode 100644 index 000000000..ef13c706b --- /dev/null +++ b/src/main/resources/update/gitbucket-core_4.7.sql @@ -0,0 +1,2 @@ +-- DELETE COLLABORATORS IN GROUP REPOSITORIES +DELETE FROM COLLABORATOR WHERE USER_NAME IN (SELECT USER_NAME FROM ACCOUNT WHERE GROUP_ACCOUNT = TRUE) diff --git a/src/main/resources/update/gitbucket-core_4.7.xml b/src/main/resources/update/gitbucket-core_4.7.xml new file mode 100644 index 000000000..c46a28ef9 --- /dev/null +++ b/src/main/resources/update/gitbucket-core_4.7.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala index 6830d2d67..c04a68787 100644 --- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala +++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala @@ -18,5 +18,9 @@ object GitBucketCoreModule extends Module("gitbucket-core", new Version("4.5.0"), new Version("4.6.0", new LiquibaseMigration("update/gitbucket-core_4.6.xml") + ), + new Version("4.7.0", + new LiquibaseMigration("update/gitbucket-core_4.7.xml"), + new SqlMigration("update/gitbucket-core_4.7.sql") ) ) diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index 6bc27515a..c5a17bc2a 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -109,7 +109,11 @@ trait IndexControllerBase extends ControllerBase { get("/_user/proposals")(usersOnly { contentType = formats("json") org.json4s.jackson.Serialization.write( - Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray) + Map("options" -> (if(params.get("userOnly").isDefined) { + getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray + } else { + getAllUsers(false).map(_.userName).toArray + })) ) }) diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index 41455cc08..cceb9a603 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -182,7 +182,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { * Add the collaborator. */ post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => - if(!getAccountByUserName(repository.owner).get.isGroupAccount){ + getAccountByUserName(repository.owner).foreach { _ => addCollaborator(repository.owner, repository.name, form.userName) } redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") @@ -192,9 +192,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { * Add the collaborator. */ get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository => - if(!getAccountByUserName(repository.owner).get.isGroupAccount){ - removeCollaborator(repository.owner, repository.name, params("name")) - } + removeCollaborator(repository.owner, repository.name, params("name")) redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") }) @@ -404,10 +402,10 @@ trait RepositorySettingsControllerBase extends ControllerBase { override def validate(name: String, value: String, messages: Messages): Option[String] = getAccountByUserName(value) match { case None => Some("User does not exist.") - case Some(x) if(x.isGroupAccount) - => Some("User does not exist.") +// case Some(x) if(x.isGroupAccount) +// => Some("User does not exist.") case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName)) - => Some("User can access this repository already.") + => Some(value + " is repository owner.") // TODO also group members? case _ => None } } diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 3541c0786..fef989767 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -337,49 +337,53 @@ trait RepositoryService { self: AccountService => .update (defaultBranch) /** - * Add collaborator to the repository. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - * @param collaboratorName the collaborator name + * Add collaborator (user or group) to the repository. */ def addCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = Collaborators insert Collaborator(userName, repositoryName, collaboratorName) /** - * Remove collaborator from the repository. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - * @param collaboratorName the collaborator name + * Remove collaborator (user or group) from the repository. */ def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete /** * Remove all collaborators from the repository. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name */ def removeCollaborators(userName: String, repositoryName: String)(implicit s: Session): Unit = Collaborators.filter(_.byRepository(userName, repositoryName)).delete /** - * Returns the list of collaborators name which is sorted with ascending order. - * - * @param userName the user name of the repository owner - * @param repositoryName the repository name - * @return the list of collaborators name + * Returns the list of collaborators name (user name or group name) which is sorted with ascending order. */ def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[String] = Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list + /** + * Returns the list of all collaborator name and permission which is sorted with ascending order. + * If a group is added as a collaborator, this method returns users who are belong to that group. + */ + def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[String] = Nil)(implicit s: Session): List[(String, String)] = { + val q1 = Collaborators.filter(_.byRepository(userName, repositoryName)) + .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) } + .map { case (t1, t2) => (t1.collaboratorName, "ADMIN") } + + val q2 = Collaborators.filter(_.byRepository(userName, repositoryName)) + .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) } + .innerJoin(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName } + .map { case ((t1, t2), t3) => (t3.userName, "ADMIN") } + + q1.union(q2).list + } + + def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = { loginAccount match { case Some(a) if(a.isAdmin) => true case Some(a) if(a.userName == owner) => true - case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true + case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => true + case Some(a) if(getCollaboratorUserNames(owner, repository).contains((a.userName, "ADMIN"))) => true // TODO ADMIN|WRITE case _ => false } } diff --git a/src/main/scala/gitbucket/core/util/Authenticator.scala b/src/main/scala/gitbucket/core/util/Authenticator.scala index 00354941b..ca1c39492 100644 --- a/src/main/scala/gitbucket/core/util/Authenticator.scala +++ b/src/main/scala/gitbucket/core/util/Authenticator.scala @@ -1,11 +1,13 @@ package gitbucket.core.util import gitbucket.core.controller.ControllerBase -import gitbucket.core.service.{RepositoryService, AccountService} +import gitbucket.core.service.{AccountService, RepositoryService} import RepositoryService.RepositoryInfo import Implicits._ import ControlUtil._ +import scala.collection.Searching.search + /** * Allows only oneself and administrators. */ @@ -40,9 +42,9 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco context.loginAccount match { case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(repository.owner == x.userName) => action(repository) - case Some(x) if(getGroupMembers(repository.owner).exists { member => - member.userName == x.userName && member.isManager == true - }) => action(repository) + // TODO Repository management is allowed for only group managers? + case Some(x) if(getGroupMembers(repository.owner).exists { m => m.userName == x.userName && m.isManager == true }) => action(repository) + case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq("ADMIN")).exists(_._1 == x.userName)) => action(repository) case _ => Unauthorized() } } getOrElse NotFound() @@ -88,7 +90,7 @@ trait AdminAuthenticator { self: ControllerBase => /** * Allows only collaborators and administrators. */ -trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService => +trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService with AccountService => protected def collaboratorsOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } protected def collaboratorsOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } @@ -99,7 +101,8 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService = context.loginAccount match { case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) + case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository) + case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq("ADMIN", "WRITE")).exists(_._1 == x.userName)) => action(repository) case _ => Unauthorized() } } getOrElse NotFound() @@ -109,9 +112,9 @@ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService = } /** - * Allows only the repository owner (or manager for group repository) and administrators. + * Allows only guests and signed in users who can access the repository. */ -trait ReferrerAuthenticator { self: ControllerBase with RepositoryService => +trait ReferrerAuthenticator { self: ControllerBase with RepositoryService with AccountService => protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } @@ -125,7 +128,8 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService => context.loginAccount match { case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) + case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository) + case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).exists(_._1 == x.userName)) => action(repository) case _ => Unauthorized() } } @@ -136,9 +140,9 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService => } /** - * Allows only signed in users which can access the repository. + * Allows only signed in users who can access the repository. */ -trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService => +trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService => protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) } @@ -150,7 +154,8 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService = case Some(x) if(x.isAdmin) => action(repository) case Some(x) if(!repository.repository.isPrivate) => action(repository) case Some(x) if(paths(0) == x.userName) => action(repository) - case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository) + case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository) + case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).exists(_._1 == x.userName)) => action(repository) case _ => Unauthorized() } } getOrElse NotFound() diff --git a/src/main/twirl/gitbucket/core/helper/account.scala.html b/src/main/twirl/gitbucket/core/helper/account.scala.html index e3dc42c31..8fa8907ad 100644 --- a/src/main/twirl/gitbucket/core/helper/account.scala.html +++ b/src/main/twirl/gitbucket/core/helper/account.scala.html @@ -1,4 +1,4 @@ -@(id: String, width: Int)(implicit context: gitbucket.core.controller.Context) +@(id: String, width: Int, userOnly: Boolean = true)(implicit context: gitbucket.core.controller.Context) @@ -6,7 +6,7 @@ $(function(){ $('#@id').typeahead({ source: function (query, process) { - return $.get('@context.path/_user/proposals', { query: query }, + return $.get('@context.path/_user/proposals@if(userOnly){?userOnly}', { query: query }, function (data) { return process(data.options); }); diff --git a/src/main/twirl/gitbucket/core/settings/collaborators.scala.html b/src/main/twirl/gitbucket/core/settings/collaborators.scala.html index 21c84238f..83116e805 100644 --- a/src/main/twirl/gitbucket/core/settings/collaborators.scala.html +++ b/src/main/twirl/gitbucket/core/settings/collaborators.scala.html @@ -10,25 +10,17 @@ @collaborators.map { collaboratorName =>
  • @collaboratorName - @if(!isGroupRepository){ - (remove) - } else { - @if(repository.managers.contains(collaboratorName)){ - (Manager) - } - } + (remove)
  • } - @if(!isGroupRepository){
    - @gitbucket.core.helper.html.account("userName", 300) + @gitbucket.core.helper.html.account("userName", 300, false)
    - } } } } From ce916a7d4b4b178fea007e28b9c218692edffde4 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 1 Nov 2016 06:51:30 +0900 Subject: [PATCH 43/59] (refs #1286) Fix models --- .../scala/gitbucket/core/model/Collaborator.scala | 6 ++++-- .../scala/gitbucket/core/model/Repository.scala | 6 ++++-- .../gitbucket/core/service/RepositoryService.scala | 14 ++++++-------- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/scala/gitbucket/core/model/Collaborator.scala b/src/main/scala/gitbucket/core/model/Collaborator.scala index 55ae80f14..f25e4b293 100644 --- a/src/main/scala/gitbucket/core/model/Collaborator.scala +++ b/src/main/scala/gitbucket/core/model/Collaborator.scala @@ -7,7 +7,8 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile => class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate { val collaboratorName = column[String]("COLLABORATOR_NAME") - def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply) + val permission = column[String]("PERMISSION") + def * = (userName, repositoryName, collaboratorName, permission) <> (Collaborator.tupled, Collaborator.unapply) def byPrimaryKey(owner: String, repository: String, collaborator: String) = byRepository(owner, repository) && (collaboratorName === collaborator.bind) @@ -17,5 +18,6 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile => case class Collaborator( userName: String, repositoryName: String, - collaboratorName: String + collaboratorName: String, + permission: String ) diff --git a/src/main/scala/gitbucket/core/model/Repository.scala b/src/main/scala/gitbucket/core/model/Repository.scala index c5c61bb39..feca31d81 100644 --- a/src/main/scala/gitbucket/core/model/Repository.scala +++ b/src/main/scala/gitbucket/core/model/Repository.scala @@ -23,11 +23,12 @@ trait RepositoryComponent extends TemplateComponent { self: Profile => val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING") val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL") val allowFork = column[Boolean]("ALLOW_FORK") + val allowCreateIssue = column[Boolean]("ALLOW_CREATE_ISSUE") def * = ( (userName, repositoryName, isPrivate, description.?, defaultBranch, registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?), - (enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?, allowFork) + (enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?, allowFork, allowCreateIssue) ).shaped <> ( { case (repository, options) => Repository( @@ -90,5 +91,6 @@ case class RepositoryOptions( enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String], - allowFork: Boolean + allowFork: Boolean, + allowCreateIssue: Boolean ) diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index fef989767..093e87621 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -43,7 +43,8 @@ trait RepositoryService { self: AccountService => enableWiki = true, allowWikiEditing = true, externalWikiUrl = None, - allowFork = true + allowFork = true, + allowCreateIssue = false ) ) @@ -124,11 +125,8 @@ trait RepositoryService { self: AccountService => repositoryName = newRepositoryName )) :_*) - if(account.isGroupAccount){ - Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*) - } else { - Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) - } + // TODO Drop transfered owner from collaborators? + Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) // Update activity messages Activities.filter { t => @@ -340,7 +338,7 @@ trait RepositoryService { self: AccountService => * Add collaborator (user or group) to the repository. */ def addCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = - Collaborators insert Collaborator(userName, repositoryName, collaboratorName) + Collaborators insert Collaborator(userName, repositoryName, collaboratorName, "ADMIN") // TODO /** * Remove collaborator (user or group) from the repository. @@ -358,7 +356,7 @@ trait RepositoryService { self: AccountService => * Returns the list of collaborators name (user name or group name) which is sorted with ascending order. */ def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[String] = - Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list + Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list // TODO return with permission /** * Returns the list of all collaborator name and permission which is sorted with ascending order. From 368052bd8f9ca330c5348f305c8f9db302df1290 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 1 Nov 2016 07:24:51 +0900 Subject: [PATCH 44/59] (refs #1286) Fix services and beat compilation errors --- .../scala/gitbucket/core/api/ApiUser.scala | 2 +- .../core/controller/AccountController.scala | 28 +++++++++---------- .../core/controller/ApiController.scala | 3 +- .../core/controller/IssuesController.scala | 24 ++++++++-------- .../controller/PullRequestsController.scala | 18 ++++++------ .../RepositorySettingsController.scala | 2 +- .../controller/SystemSettingsController.scala | 14 +++++----- .../service/RepositoryCreationService.scala | 12 ++++---- .../core/service/RepositoryService.scala | 8 +++--- .../scala/gitbucket/core/util/Notifier.scala | 4 ++- .../core/settings/collaborators.scala.html | 8 +++--- 11 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/main/scala/gitbucket/core/api/ApiUser.scala b/src/main/scala/gitbucket/core/api/ApiUser.scala index 7259c12e4..9b3dc9dcc 100644 --- a/src/main/scala/gitbucket/core/api/ApiUser.scala +++ b/src/main/scala/gitbucket/core/api/ApiUser.scala @@ -30,7 +30,7 @@ object ApiUser{ def apply(user: Account): ApiUser = ApiUser( login = user.userName, email = user.mailAddress, - `type` = if(user.isGroupAccount){ "Organization" }else{ "User" }, + `type` = if(user.isGroupAccount){ "Organization" } else { "User" }, site_admin = user.isAdmin, created_at = user.registeredDate ) diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala index fa46b7810..bfb9ccd08 100644 --- a/src/main/scala/gitbucket/core/controller/AccountController.scala +++ b/src/main/scala/gitbucket/core/controller/AccountController.scala @@ -319,13 +319,13 @@ trait AccountControllerBase extends AccountManagementControllerBase { // Update GROUP_MEMBER updateGroupMembers(form.groupName, members) - // Update COLLABORATOR for group repositories - getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => - removeCollaborators(form.groupName, repositoryName) - members.foreach { case (userName, isManager) => - addCollaborator(form.groupName, repositoryName, userName) - } - } +// // Update COLLABORATOR for group repositories +// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => +// removeCollaborators(form.groupName, repositoryName) +// members.foreach { case (userName, isManager) => +// addCollaborator(form.groupName, repositoryName, userName) +// } +// } updateImage(form.groupName, form.fileId, form.clearImage) redirect(s"/${form.groupName}") @@ -402,13 +402,13 @@ trait AccountControllerBase extends AccountManagementControllerBase { parentUserName = Some(repository.owner) ) - // Add collaborators for group repository - val ownerAccount = getAccountByUserName(accountName).get - if(ownerAccount.isGroupAccount){ - getGroupMembers(accountName).foreach { member => - addCollaborator(accountName, repository.name, member.userName) - } - } +// // Add collaborators for group repository +// val ownerAccount = getAccountByUserName(accountName).get +// if(ownerAccount.isGroupAccount){ +// getGroupMembers(accountName).foreach { member => +// addCollaborator(accountName, repository.name, member.userName) +// } +// } // Insert default labels insertDefaultLabels(accountName, repository.name) diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala index 692a1f992..e3175add5 100644 --- a/src/main/scala/gitbucket/core/controller/ApiController.scala +++ b/src/main/scala/gitbucket/core/controller/ApiController.scala @@ -177,7 +177,8 @@ trait ApiControllerBase extends ControllerBase { * https://developer.github.com/v3/repos/collaborators/#list-collaborators */ get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository => - JsonFormat(getCollaborators(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get))) + // TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members. + JsonFormat(getCollaboratorUserNames(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u._1).get))) }) /** diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 6ddcd40e8..d97e31ea4 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -67,7 +67,7 @@ trait IssuesControllerBase extends ControllerBase { _, getComments(owner, name, issueId.toInt), getIssueLabels(owner, name, issueId.toInt), - (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted, + getAssignableUserNames(owner, name), getMilestonesWithIssueCount(owner, name), getLabels(owner, name), hasWritePermission(owner, name, context.loginAccount), @@ -79,11 +79,11 @@ trait IssuesControllerBase extends ControllerBase { get("/:owner/:repository/issues/new")(readableUsersOnly { repository => defining(repository.owner, repository.name){ case (owner, name) => html.create( - (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted, - getMilestones(owner, name), - getLabels(owner, name), - hasWritePermission(owner, name, context.loginAccount), - repository) + getAssignableUserNames(owner, name), + getMilestones(owner, name), + getLabels(owner, name), + hasWritePermission(owner, name, context.loginAccount), + repository) } }) @@ -369,11 +369,7 @@ trait IssuesControllerBase extends ControllerBase { "issues", searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName), page, - if(!getAccountByUserName(owner).exists(_.isGroupAccount)){ - (getCollaborators(owner, repoName) :+ owner).sorted - } else { - getCollaborators(owner, repoName) - }, + getAssignableUserNames(owner, repoName), getMilestones(owner, repoName), getLabels(owner, repoName), countIssue(condition.copy(state = "open" ), false, owner -> repoName), @@ -383,4 +379,10 @@ trait IssuesControllerBase extends ControllerBase { hasWritePermission(owner, repoName, context.loginAccount)) } } + + // TODO Move to IssuesService? + private def getAssignableUserNames(owner: String, repository: String): List[String] = + (getCollaboratorUserNames(owner, repository, Seq("ADMIN", "WRITE")).map(_._1) ::: + (if(getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).sorted + } diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index f3bd241ab..6d8fd7c19 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -18,7 +18,6 @@ import gitbucket.core.view.helpers import io.github.gitbucket.scalatra.forms._ import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.PersonIdent -import org.slf4j.LoggerFactory import scala.collection.JavaConverters._ @@ -34,8 +33,6 @@ trait PullRequestsControllerBase extends ControllerBase { with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator with CommitStatusService with MergeService with ProtectedBranchService => - private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase]) - val pullRequestForm = mapping( "title" -> trim(label("Title" , text(required, maxlength(100)))), "content" -> trim(label("Content", optional(text()))), @@ -94,7 +91,7 @@ trait PullRequestsControllerBase extends ControllerBase { (commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId)) .sortWith((a, b) => a.registeredDate before b.registeredDate), getIssueLabels(owner, name, issueId), - (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted, + getAssignableUserNames(owner, name), getMilestonesWithIssueCount(owner, name), getLabels(owner, name), commits, @@ -375,7 +372,7 @@ trait PullRequestsControllerBase extends ControllerBase { originRepository, forkedRepository, hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount), - (getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.isGroupAccount) Nil else List(originRepository.owner))).sorted, + getAssignableUserNames(originRepository.owner, originRepository.name), getMilestones(originRepository.owner, originRepository.name), getLabels(originRepository.owner, originRepository.name) ) @@ -526,11 +523,7 @@ trait PullRequestsControllerBase extends ControllerBase { "pulls", searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName), page, - if(!getAccountByUserName(owner).exists(_.isGroupAccount)){ - (getCollaborators(owner, repoName) :+ owner).sorted - } else { - getCollaborators(owner, repoName) - }, + getAssignableUserNames(owner, repoName), getMilestones(owner, repoName), getLabels(owner, repoName), countIssue(condition.copy(state = "open" ), true, owner -> repoName), @@ -540,4 +533,9 @@ trait PullRequestsControllerBase extends ControllerBase { hasWritePermission(owner, repoName, context.loginAccount)) } + // TODO Move to IssuesService? + private def getAssignableUserNames(owner: String, repository: String): List[String] = + (getCollaboratorUserNames(owner, repository, Seq("ADMIN", "WRITE")).map(_._1) ::: + (if(getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).sorted + } diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index cceb9a603..d6726eefa 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -183,7 +183,7 @@ trait RepositorySettingsControllerBase extends ControllerBase { */ post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => getAccountByUserName(repository.owner).foreach { _ => - addCollaborator(repository.owner, repository.name, form.userName) + addCollaborator(repository.owner, repository.name, form.userName, "ADMIN") // TODO } redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") }) diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index 7a6874612..bfca8582d 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -279,13 +279,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase { } else { // Update GROUP_MEMBER updateGroupMembers(form.groupName, members) - // Update COLLABORATOR for group repositories - getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => - removeCollaborators(form.groupName, repositoryName) - members.foreach { case (userName, isManager) => - addCollaborator(form.groupName, repositoryName, userName) - } - } +// // Update COLLABORATOR for group repositories +// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => +// removeCollaborators(form.groupName, repositoryName) +// members.foreach { case (userName, isManager) => +// addCollaborator(form.groupName, repositoryName, userName) +// } +// } } updateImage(form.groupName, form.fileId, form.clearImage) diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala index 90e0afd63..04aef5099 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala @@ -21,12 +21,12 @@ trait RepositoryCreationService { // Insert to the database at first insertRepository(name, owner, description, isPrivate) - // Add collaborators for group repository - if(ownerAccount.isGroupAccount){ - getGroupMembers(owner).foreach { member => - addCollaborator(owner, name, member.userName) - } - } +// // Add collaborators for group repository +// if(ownerAccount.isGroupAccount){ +// getGroupMembers(owner).foreach { member => +// addCollaborator(owner, name, member.userName) +// } +// } // Insert default labels insertDefaultLabels(owner, name) diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 093e87621..5391507f3 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -337,8 +337,8 @@ trait RepositoryService { self: AccountService => /** * Add collaborator (user or group) to the repository. */ - def addCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = - Collaborators insert Collaborator(userName, repositoryName, collaboratorName, "ADMIN") // TODO + def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, permission: String)(implicit s: Session): Unit = + Collaborators insert Collaborator(userName, repositoryName, collaboratorName, permission) /** * Remove collaborator (user or group) from the repository. @@ -355,8 +355,8 @@ trait RepositoryService { self: AccountService => /** * Returns the list of collaborators name (user name or group name) which is sorted with ascending order. */ - def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[String] = - Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list // TODO return with permission + def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[Collaborator] = + Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).list /** * Returns the list of all collaborator name and permission which is sorted with ascending order. diff --git a/src/main/scala/gitbucket/core/util/Notifier.scala b/src/main/scala/gitbucket/core/util/Notifier.scala index 36ec89d9d..de424835c 100644 --- a/src/main/scala/gitbucket/core/util/Notifier.scala +++ b/src/main/scala/gitbucket/core/util/Notifier.scala @@ -22,8 +22,10 @@ trait Notifier extends RepositoryService with AccountService with IssuesService ( // individual repository's owner issue.userName :: + // group members of group repository + getGroupMembers(issue.userName).map(_.userName) ::: // collaborators - getCollaborators(issue.userName, issue.repositoryName) ::: + getCollaboratorUserNames(issue.userName, issue.repositoryName).map(_._1) ::: // participants issue.openedUserName :: getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName) diff --git a/src/main/twirl/gitbucket/core/settings/collaborators.scala.html b/src/main/twirl/gitbucket/core/settings/collaborators.scala.html index 83116e805..715ea1c83 100644 --- a/src/main/twirl/gitbucket/core/settings/collaborators.scala.html +++ b/src/main/twirl/gitbucket/core/settings/collaborators.scala.html @@ -1,4 +1,4 @@ -@(collaborators: List[String], +@(collaborators: List[gitbucket.core.model.Collaborator], isGroupRepository: Boolean, repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @@ -7,10 +7,10 @@ @gitbucket.core.settings.html.menu("collaborators", repository){

    Manage Collaborators

    From 04567391180c3a7a91a970692ff0ec2299077777 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 1 Nov 2016 09:10:40 +0900 Subject: [PATCH 45/59] (refs #1286) Update collaborators setting form --- .../RepositorySettingsController.scala | 37 ++++-- .../core/service/RepositoryService.scala | 10 +- .../core/settings/collaborators.scala.html | 112 +++++++++++++++--- 3 files changed, 126 insertions(+), 33 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala index d6726eefa..31ebeb9ca 100644 --- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala @@ -178,23 +178,34 @@ trait RepositorySettingsControllerBase extends ControllerBase { repository) }) - /** - * Add the collaborator. - */ - post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => - getAccountByUserName(repository.owner).foreach { _ => - addCollaborator(repository.owner, repository.name, form.userName, "ADMIN") // TODO + post("/:owner/:repository/settings/collaborators")(ownerOnly { repository => + val collaborators = params("collaborators") + removeCollaborators(repository.owner, repository.name) + collaborators.split(",").map { collaborator => + val userName :: permission :: Nil = collaborator.split(":").toList + addCollaborator(repository.owner, repository.name, userName, permission) } redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") }) - /** - * Add the collaborator. - */ - get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository => - removeCollaborator(repository.owner, repository.name, params("name")) - redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") - }) + +// /** +// * Add the collaborator. +// */ +// post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) => +// getAccountByUserName(repository.owner).foreach { _ => +// addCollaborator(repository.owner, repository.name, form.userName, "ADMIN") // TODO +// } +// redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") +// }) +// +// /** +// * Add the collaborator. +// */ +// get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository => +// removeCollaborator(repository.owner, repository.name, params("name")) +// redirect(s"/${repository.owner}/${repository.name}/settings/collaborators") +// }) /** * Display the web hook page. diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 5391507f3..6fe62e2ca 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -340,11 +340,11 @@ trait RepositoryService { self: AccountService => def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, permission: String)(implicit s: Session): Unit = Collaborators insert Collaborator(userName, repositoryName, collaboratorName, permission) - /** - * Remove collaborator (user or group) from the repository. - */ - def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = - Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete +// /** +// * Remove collaborator (user or group) from the repository. +// */ +// def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit = +// Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete /** * Remove all collaborators from the repository. diff --git a/src/main/twirl/gitbucket/core/settings/collaborators.scala.html b/src/main/twirl/gitbucket/core/settings/collaborators.scala.html index 715ea1c83..3d2d60cc7 100644 --- a/src/main/twirl/gitbucket/core/settings/collaborators.scala.html +++ b/src/main/twirl/gitbucket/core/settings/collaborators.scala.html @@ -6,21 +6,103 @@ @gitbucket.core.html.menu("settings", repository){ @gitbucket.core.settings.html.menu("collaborators", repository){

    Manage Collaborators

    - -
    -
    - -
    - @gitbucket.core.helper.html.account("userName", 300, false) - -
    +
    +
      +
    + @gitbucket.core.helper.html.account("userName", 200, false) + + +
    + +
    +
    + +
    +
    } } } + \ No newline at end of file From a60c607fcb3a44d88922a5b8eaf4903f7f3b3abb Mon Sep 17 00:00:00 2001 From: Hiroaki Yamazoe Date: Sun, 30 Oct 2016 13:59:54 +0900 Subject: [PATCH 46/59] enable transaction for SSH access --- src/main/scala/gitbucket/core/ssh/GitCommand.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/gitbucket/core/ssh/GitCommand.scala b/src/main/scala/gitbucket/core/ssh/GitCommand.scala index a2f18e752..f1ebb094b 100644 --- a/src/main/scala/gitbucket/core/ssh/GitCommand.scala +++ b/src/main/scala/gitbucket/core/ssh/GitCommand.scala @@ -36,7 +36,7 @@ abstract class GitCommand extends Command with SessionAware { override def run(): Unit = { authUser match { case Some(authUser) => - Database() withSession { implicit session => + Database() withTransaction { implicit session => try { runTask(authUser) callback.onExit(0) From 0c3c6ea15a9f90762ec6c6d91dbc5ea42290638f Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 1 Nov 2016 16:03:02 +0900 Subject: [PATCH 47/59] (refs #1286) Show whether group account on the collaborators proposal --- .../core/controller/IndexController.scala | 6 ++--- .../core/service/RepositoryService.scala | 13 +++++++---- .../gitbucket/core/helper/account.scala.html | 7 ++++++ .../core/settings/collaborators.scala.html | 22 +++++++++---------- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala index c5a17bc2a..8d15b2e38 100644 --- a/src/main/scala/gitbucket/core/controller/IndexController.scala +++ b/src/main/scala/gitbucket/core/controller/IndexController.scala @@ -110,10 +110,10 @@ trait IndexControllerBase extends ControllerBase { contentType = formats("json") org.json4s.jackson.Serialization.write( Map("options" -> (if(params.get("userOnly").isDefined) { - getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray + getAllUsers(false).filter(!_.isGroupAccount).map { t => (t.userName, t.isGroupAccount) }.toArray } else { - getAllUsers(false).map(_.userName).toArray - })) + getAllUsers(false).map { t => (t.userName, t.isGroupAccount) }.toArray + }).map { case (userName, groupAccount) => userName + ":" + groupAccount }) ) }) diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 6fe62e2ca..41580b63d 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -355,8 +355,13 @@ trait RepositoryService { self: AccountService => /** * Returns the list of collaborators name (user name or group name) which is sorted with ascending order. */ - def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[Collaborator] = - Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).list + def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[(Collaborator, Boolean)] = + Collaborators + .innerJoin(Accounts).on(_.collaboratorName === _.userName) + .filter { case (t1, t2) => t1.byRepository(userName, repositoryName) } + .map { case (t1, t2) => (t1, t2.groupAccount) } + .sortBy { case (t1, t2) => t1.collaboratorName } + .list /** * Returns the list of all collaborator name and permission which is sorted with ascending order. @@ -364,8 +369,8 @@ trait RepositoryService { self: AccountService => */ def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[String] = Nil)(implicit s: Session): List[(String, String)] = { val q1 = Collaborators.filter(_.byRepository(userName, repositoryName)) - .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) } - .map { case (t1, t2) => (t1.collaboratorName, "ADMIN") } + .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) } + .map { case (t1, t2) => (t1.collaboratorName, "ADMIN") } val q2 = Collaborators.filter(_.byRepository(userName, repositoryName)) .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) } diff --git a/src/main/twirl/gitbucket/core/helper/account.scala.html b/src/main/twirl/gitbucket/core/helper/account.scala.html index 8fa8907ad..06964dc0c 100644 --- a/src/main/twirl/gitbucket/core/helper/account.scala.html +++ b/src/main/twirl/gitbucket/core/helper/account.scala.html @@ -5,6 +5,13 @@ diff --git a/src/main/twirl/gitbucket/core/wiki/compare.scala.html b/src/main/twirl/gitbucket/core/wiki/compare.scala.html index 3745cb731..e2fe16317 100644 --- a/src/main/twirl/gitbucket/core/wiki/compare.scala.html +++ b/src/main/twirl/gitbucket/core/wiki/compare.scala.html @@ -3,7 +3,7 @@ to: String, diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo], repository: gitbucket.core.service.RepositoryService.RepositoryInfo, - hasWritePermission: Boolean, + isEditable: Boolean, info: Option[Any])(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @gitbucket.core.html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){ @@ -27,7 +27,7 @@
    @gitbucket.core.helper.html.diff(diffs, repository, None, None, false, None, false, false)
    - @if(hasWritePermission){ + @if(isEditable){
    @if(pageName.isDefined){ Revert Changes diff --git a/src/main/twirl/gitbucket/core/wiki/page.scala.html b/src/main/twirl/gitbucket/core/wiki/page.scala.html index 611714051..ac201e575 100644 --- a/src/main/twirl/gitbucket/core/wiki/page.scala.html +++ b/src/main/twirl/gitbucket/core/wiki/page.scala.html @@ -2,7 +2,7 @@ page: gitbucket.core.service.WikiService.WikiPageInfo, pages: List[String], repository: gitbucket.core.service.RepositoryService.RepositoryInfo, - hasWritePermission: Boolean, + isEditable: Boolean, sidebar: Option[gitbucket.core.service.WikiService.WikiPageInfo], footer: Option[gitbucket.core.service.WikiService.WikiPageInfo])(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @@ -12,7 +12,7 @@
    Page History - @if(hasWritePermission){ + @if(isEditable){ Edit Page New Page } @@ -49,13 +49,13 @@ } @sidebar.map { sidebarPage =>
    - @if(hasWritePermission){ + @if(isEditable){ } @helpers.markdown(sidebarPage.content, repository, true, false, false, false, pages)
    }.getOrElse{ - @if(hasWritePermission){ + @if(isEditable){
    Add a custom sidebar
    @@ -88,13 +88,13 @@
    @footer.map { footerPage => }.getOrElse{ - @if(hasWritePermission){ + @if(isEditable){
    Add a custom footer
    diff --git a/src/main/twirl/gitbucket/core/wiki/pages.scala.html b/src/main/twirl/gitbucket/core/wiki/pages.scala.html index cf6fd5926..e947f2112 100644 --- a/src/main/twirl/gitbucket/core/wiki/pages.scala.html +++ b/src/main/twirl/gitbucket/core/wiki/pages.scala.html @@ -1,6 +1,6 @@ @(pages: List[String], repository: gitbucket.core.service.RepositoryService.RepositoryInfo, - hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context) + isEditable: Boolean)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @gitbucket.core.html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){ @gitbucket.core.html.menu("wiki", repository){ @@ -10,7 +10,7 @@
  • - @if(hasWritePermission){ + @if(isEditable){ New Page }
    From 132bb6bee4996471a400dbf03485a5719af1f285 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Fri, 4 Nov 2016 13:57:39 +0900 Subject: [PATCH 52/59] (refs #1286) Update controllers --- .../core/controller/IssuesController.scala | 114 ++++++++++------ .../controller/PullRequestsController.scala | 127 +++++++++++------- .../core/controller/WikiController.scala | 26 ++-- .../core/service/HandleCommentService.scala | 20 +-- .../core/service/RepositoryService.scala | 6 +- .../core/issues/commentform.scala.html | 9 +- .../core/issues/commentlist.scala.html | 12 +- .../gitbucket/core/issues/create.scala.html | 6 +- .../gitbucket/core/issues/issue.scala.html | 11 +- .../core/issues/issueinfo.scala.html | 8 +- .../gitbucket/core/issues/list.scala.html | 11 +- .../core/issues/listparts.scala.html | 6 +- .../core/pulls/conversation.scala.html | 13 +- .../gitbucket/core/pulls/pullreq.scala.html | 9 +- .../core/settings/options.scala.html | 10 -- .../twirl/gitbucket/core/wiki/edit.scala.html | 2 +- .../gitbucket/core/wiki/history.scala.html | 23 ++-- 17 files changed, 230 insertions(+), 183 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 6fbe8e6d4..703dbefbb 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -2,13 +2,13 @@ package gitbucket.core.controller import gitbucket.core.issues.html import gitbucket.core.service.IssuesService._ +import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service._ import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.Implicits._ import gitbucket.core.util._ import gitbucket.core.view import gitbucket.core.view.Markdown - import io.github.gitbucket.scalatra.forms._ import org.scalatra.Ok @@ -70,7 +70,8 @@ trait IssuesControllerBase extends ControllerBase { getAssignableUserNames(owner, name), getMilestonesWithIssueCount(owner, name), getLabels(owner, name), - hasWritePermission(owner, name, context.loginAccount), + isEditable(repository), + isManageable(repository), repository) } getOrElse NotFound() } @@ -89,50 +90,53 @@ trait IssuesControllerBase extends ControllerBase { post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) => defining(repository.owner, repository.name){ case (owner, name) => - val writable = hasWritePermission(owner, name, context.loginAccount) - val userName = context.loginAccount.get.userName + val manageable = isManageable(repository) + val editable = isEditable(repository) + if(editable) { + val userName = context.loginAccount.get.userName - // insert issue - val issueId = createIssue(owner, name, userName, form.title, form.content, - if(writable) form.assignedUserName else None, - if(writable) form.milestoneId else None) + // insert issue + val issueId = createIssue(owner, name, userName, form.title, form.content, + if (manageable) form.assignedUserName else None, + if (manageable) form.milestoneId else None) - // insert labels - if(writable){ - form.labelNames.map { value => - val labels = getLabels(owner, name) - value.split(",").foreach { labelName => - labels.find(_.labelName == labelName).map { label => - registerIssueLabel(owner, name, issueId, label.labelId) + // insert labels + if (manageable) { + form.labelNames.map { value => + val labels = getLabels(owner, name) + value.split(",").foreach { labelName => + labels.find(_.labelName == labelName).map { label => + registerIssueLabel(owner, name, issueId, label.labelId) + } } } } - } - // record activity - recordCreateIssueActivity(owner, name, userName, issueId, form.title) + // record activity + recordCreateIssueActivity(owner, name, userName, issueId, form.title) - getIssue(owner, name, issueId.toString).foreach { issue => - // extract references and create refer comment - createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) + getIssue(owner, name, issueId.toString).foreach { issue => + // extract references and create refer comment + createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) - // call web hooks - callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get) + // call web hooks + callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get) - // notifications - Notifier().toNotify(repository, issue, form.content.getOrElse("")){ - Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") + // notifications + Notifier().toNotify(repository, issue, form.content.getOrElse("")) { + Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}") + } } - } - redirect(s"/${owner}/${name}/issues/${issueId}") + redirect(s"/${owner}/${name}/issues/${issueId}") + } else Unauthorized() } }) ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) => defining(repository.owner, repository.name){ case (owner, name) => getIssue(owner, name, params("id")).map { issue => - if(isEditable(owner, name, issue.openedUserName)){ + if(isEditableContent(owner, name, issue.openedUserName)){ // update issue updateIssue(owner, name, issue.issueId, title, issue.content) // extract references and create refer comment @@ -147,7 +151,7 @@ trait IssuesControllerBase extends ControllerBase { ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) => defining(repository.owner, repository.name){ case (owner, name) => getIssue(owner, name, params("id")).map { issue => - if(isEditable(owner, name, issue.openedUserName)){ + if(isEditableContent(owner, name, issue.openedUserName)){ // update issue updateIssue(owner, name, issue.issueId, issue.title, content) // extract references and create refer comment @@ -161,7 +165,7 @@ trait IssuesControllerBase extends ControllerBase { post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => - val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName)) + val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) => redirect(s"/${repository.owner}/${repository.name}/${ if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") @@ -171,7 +175,7 @@ trait IssuesControllerBase extends ControllerBase { post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) => getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue => - val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName)) + val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName)) handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) => redirect(s"/${repository.owner}/${repository.name}/${ if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}") @@ -182,7 +186,7 @@ trait IssuesControllerBase extends ControllerBase { ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) => defining(repository.owner, repository.name){ case (owner, name) => getComment(owner, name, params("id")).map { comment => - if(isEditable(owner, name, comment.commentedUserName)){ + if(isEditableContent(owner, name, comment.commentedUserName)){ updateComment(comment.commentId, form.content) redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}") } else Unauthorized() @@ -193,7 +197,7 @@ trait IssuesControllerBase extends ControllerBase { ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository => defining(repository.owner, repository.name){ case (owner, name) => getComment(owner, name, params("id")).map { comment => - if(isEditable(owner, name, comment.commentedUserName)){ + if(isEditableContent(owner, name, comment.commentedUserName)){ Ok(deleteComment(comment.commentId)) } else Unauthorized() } getOrElse NotFound() @@ -202,7 +206,7 @@ trait IssuesControllerBase extends ControllerBase { ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository => getIssue(repository.owner, repository.name, params("id")) map { x => - if(isEditable(x.userName, x.repositoryName, x.openedUserName)){ + if(isEditableContent(x.userName, x.repositoryName, x.openedUserName)){ params.get("dataType") collect { case t if t == "html" => html.editissue(x.content, x.issueId, repository) } getOrElse { @@ -218,7 +222,7 @@ trait IssuesControllerBase extends ControllerBase { enableAnchor = true, enableLineBreaks = true, enableTaskList = true, - hasWritePermission = isEditable(x.userName, x.repositoryName, x.openedUserName) + hasWritePermission = true ) ) ) @@ -229,7 +233,7 @@ trait IssuesControllerBase extends ControllerBase { ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository => getComment(repository.owner, repository.name, params("id")) map { x => - if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){ + if(isEditableContent(x.userName, x.repositoryName, x.commentedUserName)){ params.get("dataType") collect { case t if t == "html" => html.editcomment(x.content, x.commentId, repository) } getOrElse { @@ -244,7 +248,7 @@ trait IssuesControllerBase extends ControllerBase { enableAnchor = true, enableLineBreaks = true, enableTaskList = true, - hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName) + hasWritePermission = true ) ) ) @@ -346,9 +350,6 @@ trait IssuesControllerBase extends ControllerBase { val assignedUserName = (key: String) => params.get(key) filter (_.trim != "") val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt) - private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean = - hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName - private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = { params("checked").split(',') map(_.toInt) foreach execute params("from") match { @@ -359,8 +360,7 @@ trait IssuesControllerBase extends ControllerBase { private def searchIssues(repository: RepositoryService.RepositoryInfo) = { defining(repository.owner, repository.name){ case (owner, repoName) => - val page = IssueSearchCondition.page(request) - val sessionKey = Keys.Session.Issues(owner, repoName) + val page = IssueSearchCondition.page(request) // retrieve search condition val condition = IssueSearchCondition(request) @@ -376,8 +376,34 @@ trait IssuesControllerBase extends ControllerBase { countIssue(condition.copy(state = "closed"), false, owner -> repoName), condition, repository, - hasWritePermission(owner, repoName, context.loginAccount)) + isEditable(repository), + isManageable(repository)) } } + /** + * Tests whether an logged-in user can manage issues. + */ + private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = { + hasWritePermission(repository.owner, repository.name, context.loginAccount) + } + + /** + * Tests whether an logged-in user can post issues. + */ + private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { + repository.repository.options.issuesOption match { + case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) + case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) + case "DISABLE" => false + } + } + + /** + * Tests whether an issue or a comment is editable by a logged-in user. + */ + private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = { + hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName + } + } diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala index 4a97ae0ce..ae26bb009 100644 --- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala +++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala @@ -6,6 +6,7 @@ import gitbucket.core.service.CommitStatusService import gitbucket.core.service.MergeService import gitbucket.core.service.IssuesService._ import gitbucket.core.service.PullRequestService._ +import gitbucket.core.service.RepositoryService.RepositoryInfo import gitbucket.core.service._ import gitbucket.core.util.ControlUtil._ import gitbucket.core.util.Directory._ @@ -14,7 +15,6 @@ import gitbucket.core.util.JGitUtil._ import gitbucket.core.util._ import gitbucket.core.view import gitbucket.core.view.helpers - import io.github.gitbucket.scalatra.forms._ import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.PersonIdent @@ -24,13 +24,15 @@ import scala.collection.JavaConverters._ class PullRequestsController extends PullRequestsControllerBase with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService - with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator + with CommitsService with ActivityService with WebHookPullRequestService + with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with CommitStatusService with MergeService with ProtectedBranchService trait PullRequestsControllerBase extends ControllerBase { self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService - with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator + with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService + with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with CommitStatusService with MergeService with ProtectedBranchService => val pullRequestForm = mapping( @@ -96,7 +98,8 @@ trait PullRequestsControllerBase extends ControllerBase { getLabels(owner, name), commits, diffs, - hasWritePermission(owner, name, context.loginAccount), + isEditable(repository), + isManageable(repository), repository, flash.toMap.map(f => f._1 -> f._2.toString)) } @@ -416,64 +419,68 @@ trait PullRequestsControllerBase extends ControllerBase { }) getOrElse NotFound() }) - post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) => + post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) => defining(repository.owner, repository.name){ case (owner, name) => - val writable = hasWritePermission(owner, name, context.loginAccount) - val loginUserName = context.loginAccount.get.userName + val manageable = isManageable(repository) + val editable = isEditable(repository) - val issueId = createIssue( - owner = repository.owner, - repository = repository.name, - loginUser = loginUserName, - title = form.title, - content = form.content, - assignedUserName = if(writable) form.assignedUserName else None, - milestoneId = if(writable) form.milestoneId else None, - isPullRequest = true) + if(editable) { + val loginUserName = context.loginAccount.get.userName - createPullRequest( - originUserName = repository.owner, - originRepositoryName = repository.name, - issueId = issueId, - originBranch = form.targetBranch, - requestUserName = form.requestUserName, - requestRepositoryName = form.requestRepositoryName, - requestBranch = form.requestBranch, - commitIdFrom = form.commitIdFrom, - commitIdTo = form.commitIdTo) + val issueId = createIssue( + owner = repository.owner, + repository = repository.name, + loginUser = loginUserName, + title = form.title, + content = form.content, + assignedUserName = if (manageable) form.assignedUserName else None, + milestoneId = if (manageable) form.milestoneId else None, + isPullRequest = true) - // insert labels - if(writable){ - form.labelNames.map { value => - val labels = getLabels(owner, name) - value.split(",").foreach { labelName => - labels.find(_.labelName == labelName).map { label => - registerIssueLabel(repository.owner, repository.name, issueId, label.labelId) + createPullRequest( + originUserName = repository.owner, + originRepositoryName = repository.name, + issueId = issueId, + originBranch = form.targetBranch, + requestUserName = form.requestUserName, + requestRepositoryName = form.requestRepositoryName, + requestBranch = form.requestBranch, + commitIdFrom = form.commitIdFrom, + commitIdTo = form.commitIdTo) + + // insert labels + if (manageable) { + form.labelNames.map { value => + val labels = getLabels(owner, name) + value.split(",").foreach { labelName => + labels.find(_.labelName == labelName).map { label => + registerIssueLabel(repository.owner, repository.name, issueId, label.labelId) + } } } } - } - // fetch requested branch - fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId) + // fetch requested branch + fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId) - // record activity - recordPullRequestActivity(owner, name, loginUserName, issueId, form.title) + // record activity + recordPullRequestActivity(owner, name, loginUserName, issueId, form.title) - // call web hook - callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get) + // call web hook + callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get) - getIssue(owner, name, issueId.toString) foreach { issue => - // extract references and create refer comment - createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) + getIssue(owner, name, issueId.toString) foreach { issue => + // extract references and create refer comment + createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get) - // notifications - Notifier().toNotify(repository, issue, form.content.getOrElse("")){ - Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") + // notifications + Notifier().toNotify(repository, issue, form.content.getOrElse("")) { + Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}") + } } - } - redirect(s"/${owner}/${name}/pull/${issueId}") + redirect(s"/${owner}/${name}/pull/${issueId}") + } else Unauthorized() } }) @@ -513,8 +520,7 @@ trait PullRequestsControllerBase extends ControllerBase { private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = defining(repository.owner, repository.name){ case (owner, repoName) => - val page = IssueSearchCondition.page(request) - val sessionKey = Keys.Session.Pulls(owner, repoName) + val page = IssueSearchCondition.page(request) // retrieve search condition val condition = IssueSearchCondition(request) @@ -530,7 +536,26 @@ trait PullRequestsControllerBase extends ControllerBase { countIssue(condition.copy(state = "closed"), true, owner -> repoName), condition, repository, - hasWritePermission(owner, repoName, context.loginAccount)) + isEditable(repository), + isManageable(repository)) } + /** + * Tests whether an logged-in user can manage pull requests. + */ + private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = { + hasWritePermission(repository.owner, repository.name, context.loginAccount) + } + + /** + * Tests whether an logged-in user can post pull requests. + */ + private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { + repository.repository.options.issuesOption match { + case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) + case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) + case "DISABLE" => false + } + } + } diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala index 07153c1d7..beebfc28a 100644 --- a/src/main/scala/gitbucket/core/controller/WikiController.scala +++ b/src/main/scala/gitbucket/core/controller/WikiController.scala @@ -14,10 +14,10 @@ import org.scalatra.i18n.Messages class WikiController extends WikiControllerBase with WikiService with RepositoryService with AccountService with ActivityService - with CollaboratorsAuthenticator with ReferrerAuthenticator + with ReadableUsersAuthenticator with CollaboratorsAuthenticator with ReferrerAuthenticator trait WikiControllerBase extends ControllerBase { - self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator => + self: WikiService with RepositoryService with ActivityService with ReadableUsersAuthenticator with CollaboratorsAuthenticator with ReferrerAuthenticator => case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String) @@ -62,7 +62,7 @@ trait WikiControllerBase extends ControllerBase { using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match { - case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository) + case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository)) case Left(_) => NotFound() } } @@ -87,7 +87,7 @@ trait WikiControllerBase extends ControllerBase { } }) - get("/:owner/:repository/wiki/:page/_revert/:commitId")(referrersOnly { repository => + get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository => if(isEditable(repository)){ val pageName = StringUtil.urlDecode(params("page")) val Array(from, to) = params("commitId").split("\\.\\.\\.") @@ -101,7 +101,7 @@ trait WikiControllerBase extends ControllerBase { } else Unauthorized() }) - get("/:owner/:repository/wiki/_revert/:commitId")(referrersOnly { repository => + get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository => if(isEditable(repository)){ val Array(from, to) = params("commitId").split("\\.\\.\\.") @@ -114,14 +114,14 @@ trait WikiControllerBase extends ControllerBase { } else Unauthorized() }) - get("/:owner/:repository/wiki/:page/_edit")(referrersOnly { repository => + get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository => if(isEditable(repository)){ val pageName = StringUtil.urlDecode(params("page")) html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository) } else Unauthorized() }) - post("/:owner/:repository/wiki/_edit", editForm)(referrersOnly { (form, repository) => + post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) => if(isEditable(repository)){ defining(context.loginAccount.get){ loginAccount => saveWikiPage( @@ -146,13 +146,13 @@ trait WikiControllerBase extends ControllerBase { } else Unauthorized() }) - get("/:owner/:repository/wiki/_new")(referrersOnly { repository => + get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository => if(isEditable(repository)){ html.edit("", None, repository) } else Unauthorized() }) - post("/:owner/:repository/wiki/_new", newForm)(referrersOnly { (form, repository) => + post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) => if(isEditable(repository)){ defining(context.loginAccount.get){ loginAccount => saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName, @@ -170,7 +170,7 @@ trait WikiControllerBase extends ControllerBase { } else Unauthorized() }) - get("/:owner/:repository/wiki/:page/_delete")(referrersOnly { repository => + get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository => if(isEditable(repository)){ val pageName = StringUtil.urlDecode(params("page")) @@ -182,7 +182,7 @@ trait WikiControllerBase extends ControllerBase { } } else Unauthorized() }) - + get("/:owner/:repository/wiki/_pages")(referrersOnly { repository => html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository)) }) @@ -190,7 +190,7 @@ trait WikiControllerBase extends ControllerBase { get("/:owner/:repository/wiki/_history")(referrersOnly { repository => using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git => JGitUtil.getCommitLog(git, "master") match { - case Right((logs, hasNext)) => html.history(None, logs, repository) + case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository)) case Left(_) => NotFound() } } @@ -242,7 +242,7 @@ trait WikiControllerBase extends ControllerBase { private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = { repository.repository.options.wikiOption match { - case "ALL" => repository.repository.isPrivate == false || hasReadPermission(repository.owner, repository.name, context.loginAccount) +// case "ALL" => repository.repository.isPrivate == false || hasReadPermission(repository.owner, repository.name, context.loginAccount) case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount) case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount) case "DISABLE" => false diff --git a/src/main/scala/gitbucket/core/service/HandleCommentService.scala b/src/main/scala/gitbucket/core/service/HandleCommentService.scala index 6a1b06869..22cfc8801 100644 --- a/src/main/scala/gitbucket/core/service/HandleCommentService.scala +++ b/src/main/scala/gitbucket/core/service/HandleCommentService.scala @@ -13,7 +13,7 @@ trait HandleCommentService { with WebHookService with WebHookIssueCommentService with WebHookPullRequestService => /** - * @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]] + * @see [[https://github.com/gitbucket/gitbucket/wiki/CommentAction]] */ def handleComment(issue: Issue, content: Option[String], repository: RepositoryService.RepositoryInfo, actionOpt: Option[String]) (implicit context: Context, s: Session) = { @@ -54,18 +54,20 @@ trait HandleCommentService { // call web hooks action match { - case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) } - case Some(act) => val webHookAction = act match { - case "open" => "opened" - case "reopen" => "reopened" - case "close" => "closed" - case _ => act - } - if(issue.isPullRequest){ + case None => commentId.map { commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) } + case Some(act) => { + val webHookAction = act match { + case "open" => "opened" + case "reopen" => "reopened" + case "close" => "closed" + case _ => act + } + if (issue.isPullRequest) { callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get) } else { callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get) } + } } // notifications diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 96bbe8106..9fa8552bb 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -363,15 +363,15 @@ trait RepositoryService { self: AccountService => val q1 = Collaborators .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) } .filter { case (t1, t2) => t1.byRepository(userName, repositoryName) } - .map { case (t1, t2) => t1.collaboratorName } + .map { case (t1, t2) => (t1.collaboratorName, t1.permission) } val q2 = Collaborators .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) } .innerJoin(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName } .filter { case ((t1, t2), t3) => t1.byRepository(userName, repositoryName) } - .map { case ((t1, t2), t3) => t3.userName } + .map { case ((t1, t2), t3) => (t3.userName, t1.permission) } - q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x) } + q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1) } diff --git a/src/main/twirl/gitbucket/core/issues/commentform.scala.html b/src/main/twirl/gitbucket/core/issues/commentform.scala.html index 9f11990b0..32316c781 100644 --- a/src/main/twirl/gitbucket/core/issues/commentform.scala.html +++ b/src/main/twirl/gitbucket/core/issues/commentform.scala.html @@ -1,9 +1,10 @@ @(issue: gitbucket.core.model.Issue, reopenable: Boolean, - hasWritePermission: Boolean, + isEditable: Boolean, + isManageable: Boolean, repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers -@if(context.loginAccount.isDefined){ +@if(isEditable){

    @helpers.avatarLink(context.loginAccount.get.userName, 48)
    @@ -16,7 +17,7 @@ enableRefsLink = true, enableLineBreaks = true, enableTaskList = true, - hasWritePermission = hasWritePermission, + hasWritePermission = isEditable, completionContext = "issues", style = "", elastic = true, @@ -24,7 +25,7 @@ )
    - @if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == context.loginAccount.get.userName)){ + @if((reopenable || !issue.closed) && (isManageable || issue.openedUserName == context.loginAccount.get.userName)){ } diff --git a/src/main/twirl/gitbucket/core/issues/commentlist.scala.html b/src/main/twirl/gitbucket/core/issues/commentlist.scala.html index 268c7a58d..a2172c95f 100644 --- a/src/main/twirl/gitbucket/core/issues/commentlist.scala.html +++ b/src/main/twirl/gitbucket/core/issues/commentlist.scala.html @@ -1,6 +1,6 @@ @(issue: Option[gitbucket.core.model.Issue], comments: List[gitbucket.core.model.Comment], - hasWritePermission: Boolean, + isManageable: Boolean, repository: gitbucket.core.service.RepositoryService.RepositoryInfo, pullreq: Option[gitbucket.core.model.PullRequest] = None)(implicit context: gitbucket.core.controller.Context) @import gitbucket.core.view.helpers @@ -11,7 +11,7 @@
    @helpers.user(issue.get.openedUserName, styleClass="username strong") commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate) - @if(hasWritePermission || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){ + @if(isManageable || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){ } @@ -24,7 +24,7 @@ enableRefsLink = true, enableLineBreaks = true, enableTaskList = true, - hasWritePermission = hasWritePermission + hasWritePermission = isManageable )
    @@ -48,7 +48,7 @@ @gitbucket.core.helper.html.datetimeago(comment.registeredDate) @if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer" - && (hasWritePermission || context.loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){ + && (isManageable || context.loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){   @@ -63,7 +63,7 @@ enableRefsLink = true, enableLineBreaks = true, enableTaskList = true, - hasWritePermission = hasWritePermission + hasWritePermission = isManageable )
  • @@ -166,7 +166,7 @@ } } case comment: CommitComment => { - @gitbucket.core.helper.html.commitcomment(comment, hasWritePermission, repository, pullreq.map(_.commitIdTo)) + @gitbucket.core.helper.html.commitcomment(comment, isManageable, repository, pullreq.map(_.commitIdTo)) } } \ No newline at end of file diff --git a/src/main/twirl/gitbucket/core/settings/danger.scala.html b/src/main/twirl/gitbucket/core/settings/danger.scala.html index 60b400758..7df117034 100644 --- a/src/main/twirl/gitbucket/core/settings/danger.scala.html +++ b/src/main/twirl/gitbucket/core/settings/danger.scala.html @@ -13,7 +13,7 @@
    Transfer this repo to another user or to group.
    - @gitbucket.core.helper.html.account("newOwner", 200) + @gitbucket.core.helper.html.account("newOwner", 200, true, true)
    From d70c6cece7a6fe8bc59decb926e036d9ed7a4819 Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Sun, 6 Nov 2016 16:37:44 +0900 Subject: [PATCH 54/59] (refs #1286) Fix testcase --- .../scala/gitbucket/core/service/PullRequestServiceSpec.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala b/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala index ca55903e5..d6ddff9e7 100644 --- a/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala +++ b/src/test/scala/gitbucket/core/service/PullRequestServiceSpec.scala @@ -3,7 +3,8 @@ package gitbucket.core.service import gitbucket.core.model._ import org.scalatest.FunSpec -class PullRequestServiceSpec extends FunSpec with ServiceSpecBase with PullRequestService with IssuesService with AccountService { +class PullRequestServiceSpec extends FunSpec with ServiceSpecBase + with PullRequestService with IssuesService with AccountService with RepositoryService { def swap(r: (Issue, PullRequest)) = (r._2 -> r._1) From 9eb9fc666c05685f9b20638661f76fb89403a44d Mon Sep 17 00:00:00 2001 From: Naoki Takezoe Date: Tue, 8 Nov 2016 02:28:50 +0900 Subject: [PATCH 55/59] (refs #1286) Bugfix --- .../core/controller/IssuesController.scala | 29 ++++++++++--------- .../core/service/RepositoryService.scala | 2 +- .../gitbucket/core/util/Authenticator.scala | 2 ++ .../gitbucket/core/issues/issue.scala.html | 6 ++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala index 703dbefbb..a4b01f293 100644 --- a/src/main/scala/gitbucket/core/controller/IssuesController.scala +++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala @@ -78,21 +78,22 @@ trait IssuesControllerBase extends ControllerBase { }) get("/:owner/:repository/issues/new")(readableUsersOnly { repository => - defining(repository.owner, repository.name){ case (owner, name) => - html.create( - getAssignableUserNames(owner, name), - getMilestones(owner, name), - getLabels(owner, name), - hasWritePermission(owner, name, context.loginAccount), - repository) - } + if(isEditable(repository)){ // TODO Should this check is provided by authenticator? + defining(repository.owner, repository.name){ case (owner, name) => + html.create( + getAssignableUserNames(owner, name), + getMilestones(owner, name), + getLabels(owner, name), + hasWritePermission(owner, name, context.loginAccount), + repository) + } + } else Unauthorized() }) post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) => - defining(repository.owner, repository.name){ case (owner, name) => - val manageable = isManageable(repository) - val editable = isEditable(repository) - if(editable) { + if(isEditable(repository)){ // TODO Should this check is provided by authenticator? + defining(repository.owner, repository.name){ case (owner, name) => + val manageable = isManageable(repository) val userName = context.loginAccount.get.userName // insert issue @@ -129,8 +130,8 @@ trait IssuesControllerBase extends ControllerBase { } redirect(s"/${owner}/${name}/issues/${issueId}") - } else Unauthorized() - } + } + } else Unauthorized() }) ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) => diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala index 9fa8552bb..26fa1599a 100644 --- a/src/main/scala/gitbucket/core/service/RepositoryService.scala +++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala @@ -38,7 +38,7 @@ trait RepositoryService { self: AccountService => parentUserName = parentUserName, parentRepositoryName = parentRepositoryName, options = RepositoryOptions( - issuesOption = "PRIVATE", // TODO DISABLE for the forked repository? + issuesOption = "PUBLIC", // TODO DISABLE for the forked repository? externalIssuesUrl = None, wikiOption = "PUBLIC", // TODO DISABLE for the forked repository? externalWikiUrl = None, diff --git a/src/main/scala/gitbucket/core/util/Authenticator.scala b/src/main/scala/gitbucket/core/util/Authenticator.scala index 57f39421b..6d82508b2 100644 --- a/src/main/scala/gitbucket/core/util/Authenticator.scala +++ b/src/main/scala/gitbucket/core/util/Authenticator.scala @@ -90,6 +90,8 @@ trait AdminAuthenticator { self: ControllerBase => /** * Allows only collaborators and administrators. + * + * TODO This authenticator should be renamed. */ trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService with AccountService => protected def collaboratorsOnly(action: (RepositoryInfo) => Any) = { authenticate(action) } diff --git a/src/main/twirl/gitbucket/core/issues/issue.scala.html b/src/main/twirl/gitbucket/core/issues/issue.scala.html index 4691b83a6..4b494adc4 100644 --- a/src/main/twirl/gitbucket/core/issues/issue.scala.html +++ b/src/main/twirl/gitbucket/core/issues/issue.scala.html @@ -15,7 +15,9 @@ @if(isManageable || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){ Edit } - New issue + @if(isEditable){ + New issue + }
    } } - +} \ No newline at end of file diff --git a/src/main/twirl/gitbucket/core/issues/issue.scala.html b/src/main/twirl/gitbucket/core/issues/issue.scala.html index 4b494adc4..e42ffbfd1 100644 --- a/src/main/twirl/gitbucket/core/issues/issue.scala.html +++ b/src/main/twirl/gitbucket/core/issues/issue.scala.html @@ -59,7 +59,7 @@
    } } -